Generowanie alertów pogodowych SMS na podstawie odczytów z API HTTP przy pomocy płytki Micromis Base V1

Wykorzystanie szeroko dostępnych interfejsów API na stronach WWW pozwala na wygodne pozyskiwanie wielu rodzajów danych, jednym z przykładów takich danych są informacje pogodowe na stronie OpenWeatherMap. W tym poradniku zaprezentujemy w jaki sposób można je pobierać a następnie przesyłać na wskazany numer telefonu przy pomocy wiadomości SMS.
autor: Nikola Pająk
17 marca 2023
czas czytania: 22 min.

Podzespoły

Micromis Base V1
x1
Karta SIM w formacie nanoSIM
x1
Antena GSM ze złączem U.FL
x1

Opis projektu

Podczas tego projektu stworzymy oprogramowanie dla płytek Micromis Base V1, które umożliwi pobieranie danych przez protokół HTTP. Do komunikacji z wbudowanym modemem użyjemy komend AT dzięki czemu w łatwy sposób będziemy mogli obsługiwać polecenia takie jak łączenie się z siecią GSM, wysyłanie wiadomości SMS czy odczytywanie danych z serwera. Ostatecznym efektem stworzonego oprogramowania będzie wysyłany cyklicznie na wskazany numer telefon alert informujący o warunkach pogodowych we wskazanym przez nas miejscu, które system będzie pobierał ze strony OpenWeatherMap.

Wykonanie poradnika wymaga posiadania płytki Micromis Base V1, anteny GSM ze złączem U.FL oraz aktywnej karty SIM.

Oprogramowanie stworzone w ramach tego projektu może służyć jako baza do tworzenia innych aplikacji wykorzystujących komunikację HTTP oraz wysyłanie wiadomości tekstowych przy pomocy płytki Micromis Base V1.

Zadania w projekcie

Krok 1 - Konfiguracja sprzętowej części projektu

Pierwszym krokiem do rozpoczęcia prac nad projektem jest podłączenie anteny GSM do złącza U.FL w płytce Micromis Base V1 oraz włożenie karty nanoSIM do dedykowanego slotu. Upewnij się, że karta SIM jest aktywna i ma wystarczające środki na wysyłanie wiadomości SMS oraz nawiązanie połączenia z internetem.

Krok 2 - Konfigurowanie środowiska Arduino IDE

Do uruchomienia programu tworzonego w ramach tego poradnika niezbędne będzie środowisko Arduino IDE wraz z zainstalowanym zestawem zewnętrznych płytek ESP32. Zalecamy korzystanie z Arduino IDE nie starszego niż wersja 2.0.0. Aby program mógł być prawidłowo wgrany na płytkę Micromis Base V1 należy wybrać płytkę ESP32 Dev Module w „Narzędzia” > „Płytka” i wybrać port, do którego została podłączona płytka Micromis Base V1.

Krok 3 - Analiza kodu

Do uruchomienia opracowanego oprogramowania należy umieścić przedstawiony w poradniku kod w środowisku Arduino IDE. Aby zapewnić prawidłowe działanie kodu należy zmodyfikować wartości zmiennych znajdujących się w początkowych linijkach. Niezbędne są wartości zmiennych dla numeru telefonu na który będzie przesyłany alert oraz adres APN wykorzystywanej sieci. W zależności od potrzeb należy dodać kod PIN do karty SIM. Warto pamiętać aby modyfikować jedynie wartości zmiennych, a nie ich nazwy, w przeciwnym wypadku oprogramowanie nie będzie działać prawidłowo.

Szczegółowy opis oprogramowania wraz z wykorzystaniem poszczególnych funkcji i poleceń znajduje się w sekcji “Kod - krok po kroku

Krok 4 - Test

Następnie kliknij przycisk „wgraj”, żeby przetestować program!

Uwaga! Jeśli podczas wgrywania programu pojawi się błąd: 

“A fatal error occurred: Failed to connect to ESP32: Wrong boot mode detected (0x13)! The chip needs to be in download mode”. 

Wystarczy przytrzymać przycisk “boot” do momentu, kiedy pokaże się wiadomość “Connecting… ” w oknie wyjściowym. To powinno rozwiązać problem.

Krok 5 - Efekt projektu

Prawidłowo wgrane i skonfigurowane oprogramowanie sprawi, że platforma Micromis Base V1 będzie przesyłać na nasz telefon wiadomości SMS z cyklicznymi raportami pogodowymi z dowolnego miejsca na świecie.

Kod - krok po kroku

Pierwsze linijki kodu służą do konfiguracji stałych oraz zmiennych wykorzystywanych przez kod, zawierają kilka opcji konfiguracyjnych pozwalających na dostosowanie oprogramowania do konkretnych potrzeb.

Ze względu na wykorzystywanie w kodzie parsowania danych w formacie JSON do działania programu wymagane jest dodanie biblioteki ArduinoJson. Zasoby z tej biblioteki pozwolą nam na przetwarzanie danych pobieranych przy pomocy protokołów HTTP.

C++
//Include libraries
#include <ArduinoJson.h>

W kodzie zdefiniowane są również piny RX i TX wbudowanego modemu, ze względu na trwałe połączenie interfejsu UART modemu wraz z pinami 16 i 17 układu ESP32 nie powinniśmy zmieniać wartości tych zmiennych, w przeciwnym wypadku nawiązanie komunikacji pomiędzy tymi dwoma układami będzie niemożliwe.

C++
//Defines the RX and TX pins for the cellular modem
#define RXD2 16
#define TXD2 17

Pobieranie danych z serwisu OpenWeatherMap, którego będziemy używać do uzyskiwania informacji na temat aktualnej pogody wymaga od nas precyzyjnego określenia kraju oraz miasta, którego będą dotyczyć odczyty. Wybrany przez nas kraj oraz miasto definiujemy w zmiennych CITY oraz COUNTRY_CODE przy pomocy kodów krajów oraz nazw miast, które możemy znaleźć na stronie:
https://openweathermap.org/weathermap

Przykładowo aby uzyskać dane na temat pogody w Warszawie musimy do zmiennych wpisać wartości takie jak:

C++
#define CITY = “Warszawa”
#define COUNTRY_CODE = “PL”

Dla Londynu zmienne te będą wynosić:

C++
#define CITY = “City of London”
#define COUNTRY_CODE = “GB” 

Po ustawieniu prawidłowych dla konkretnego regionu wartości zmiennych CITY i COUNTRY_CODE należy ustawić prawidłowe wartości dla zmiennej API_KEY. Wartość, która powinna znaleźć się w tej zmiennej to nic innego jak indywidualny klucz API, który umożliwia autoryzację użytkownika na stronie OpenWeatherMap. Uzyskanie indywidualnego klucza API jest bardzo proste i wymaga od nas przejścia tylko kilku kroków:

  • Rejestracja nowego konta na stronie https://openweathermap.org/
  • Otwarcie podstrony konta użytkownika
  • Przejście do karty “API keys”
  • Skopiowanie dostępnego tam klucza API i wklejenie go do zmiennej API_KEY
C++
//Define the API key and URL for OpenWeatherMap
#define API_KEY "PLACE FOR API KEY"

W następnej linijce kodu znajduje się zmienna o nazwie URL, nie należy jej jednak modyfikować, określa ona budowę adresu URL, który zostanie wykorzystany do połączenia się z API serwera OpenWeatherMap.

C++
#define URL "http://api.openweathermap.org/data/2.5/weather?q=" CITY "," COUNTRY_CODE "&units=metric&appid=" API_KEY

Układ wartości w tej zmiennej prezentuje się następująco:

C++
Stała wartość adresu URL - dane o mieście oraz kraju - wskazanie jednostek - zawarcie klucza API

Do nawiązania udanego połączenia z siecią komórkową oraz wysłania wiadomości SMS z alertem pogodowym musimy skonfigurować jeszcze trzy zmienne. Wykorzystywana w kodzie zmienna TEST_PHONE_NUMBER służy do przechowywania numeru telefonu na który modem będzie cyklicznie przesyłał wiadomość SMS z alertem pogodowym, aby wiadomość została wysłana poprawnie należy upewnić się, że numer telefonu został zapisany poprawnie - w numerze powinien być zawarty prefix kierunkowy dla danego kraju, a sama zmienna powinna być jednym ciągiem numerycznym, bez spacji czy dodatkowych znaków pomiędzy liczbami. Dla przykładowego numeru telefonu z Wielkiej Brytanii zmienna będzie wynosić:

C++
String TEST_PHONE_NUMBER =+442076350000

A dla numeru telefonu z Polski:

C++
String TEST_PHONE_NUMBER =+48505505505

Po wprowadzeniu prawidłowego numeru telefonu do zmiennej TEST_PHONE_NUMBER należy sprawdzić czy karta SIM, którą wykorzystujemy w projekcie wymaga wprowadzenia kod PIN podczas uruchomienia połączenia, jeśli tak to należy wprowadzić kod PIN do zmiennej SIM_CARD_PIN_NUMBER. Dla kodu PIN wynoszącego “1234” wartość zmiennej powinna wynosić:

C++
String SIM_CARD_PIN_NUMBER =1234

Jeśli karta SIM nie wymaga podania kodu PIN podczas uruchomienia to wartość zmiennej powinna być pusta.

Ostatnim elementem do skonfigurowania jest adres punktu dostępu (APN), który wykorzystuje dana sieć komórkowa. Adres ten należy umieścić w zmiennej APN_ADDRESS. Najpopularniejszym adresem punktu dostępu w wielu krajach jest adres “internet”, dla tej wartości zmienna będzie wynosić:

C++
String APN_ADDRESS = “internet”

Przykładowo dla sieci Deutsche Telekom adres APN wynosi “internet.telekom”, więc wartość zmiennej będzie wynosić:

C++
String APN_ADDRESS =internet.telekom

Adres APN sieci do której należy karta SIM użyta do projektu łatwo można sprawdzić na stronie apn.how.

W kolejnych zmiennych kod definiuje między innymi pin pozwalający na uruchomienie modemu, zmienne dla maksymalnej liczby prób połączenia i odstępy czasowe pomiędzy alertami.

Funkcja setupModem

Funkcja setupModem służy do uruchomienia modemu. Aby sprawdzić czy modem jest już włączony zostaje wysłane polecenie AT, a następnie sprawdza, czy otrzymano odpowiedź, która zawiera „OK” - sygnalizuje ona, że modem jest uruchomiony i można nawiązać z nim komunikację. 

C++
//Function to turn on a modem
void setupModem()
{
  //Read any available data from the modem's serial interface
  download_data = Serial2.readString(); //Reading data from modem UART

  //Check if the modem is already on or not
  Serial2.println("AT");  //Send command to check if modem is on
  download_data = Serial2.readString(); //Reading data from modem UART
  if(download_data.indexOf("OK") > -1)  //Waiting for "OK" response from modem
  {
    // Modem is already on
    Serial.println(); //Print blank line in Serial Monitor
    Serial.println(download_data); //Print data from modem UART
    Serial.println(); //Print blank line in Serial Monitor
    Serial.println("Set modem startup:"); //Printing information in Serial Monitor
    Serial.println("Modem is aleady on"); //Printing information in Serial Monitor
    Serial.println(); //Print blank line in Serial Monitor
    download_data = ""; //Clearing variable
  } 
Expand

Jeśli modem nie jest włączony, funkcja uruchamia go, przez ustawienie pinu określonego jako MODEM_PWRKEY w stanie wysokim na 1000 ms. Po uruchomieniu modemu przy pomocy pinu PWR_KEY zostaje wysłane polecenie ATE1, aby ustawić tryb echa w modemie. Ustawienie trybu echa wywołuje przesyłanie przez modem wszystkich generowanych w trakcie pracy informacji na interfejs UART.

C++
  else 
  {
    //Modem was off, turn it on
    Serial.println(); //Print blank line in Serial Monitor
    Serial.println(download_data); //Print data from modem UART
    Serial.println("Set modem startup:"); //Printing information in Serial Monitor
    Serial.println("The modem has been switched on"); //Printing information in Serial Monitor
    Serial.println(); //Print blank line in Serial Monitor
    digitalWrite(MODEM_PWRKEY, HIGH); //Turning ON modem by set power on PWR_KEY pin through 1000 ms
    delay(1000); //1000 ms pause
    digitalWrite(MODEM_PWRKEY, LOW); //Turning OFF PWR_KEY pin
    Serial2.println("ATE1"); //Set echo text mode on modem
    download_data = ""; //Clearing variable
    download_data = Serial2.readString(); //Reading data from modem UART
    int initial_counter = 0; //Blank initial counter
    delay(1000); //1000 ms pause
Expand

Funkcja następne odczytuje dane trafiające na interfejs UART i wyszukuje ciąg “Call Ready”, który informuje, że modem prawidłowo się uruchomił i można rozpocząć komunikację. W przypadku braku informacji o prawidłowym uruchomieniu modemu po 10 próbach funkcja ponownie uruchamia modem przy pomocy pinu PWR_KEY oraz zeruje licznik prób.

C++
    while(download_data.indexOf("Call Ready") == -1) //Waiting for "Call Ready" response from modem
    {
      Serial.println("Waiting for modem init"); //Printing information in Serial Monitor
      delay(500); //500 ms pause
      download_data = Serial2.readString(); //Reading data from modem UART
      Serial.println(download_data); //Print data from modem UART
      initial_counter++; //Increase the counter value by 1
      if(initial_counter > 10) //Trying to turn up modem again
      {
        digitalWrite(MODEM_PWRKEY, HIGH); //Turning ON modem by set power on PWR_KEY pin through 1000 ms
        delay(1000); //1000 ms pause
        digitalWrite(MODEM_PWRKEY, LOW); //Turning OFF PWR_KEY pin
        initial_counter = 0; //Blank initial counter
      }
    }
  }
}
Expand

Funkcja configurationModem

Funkcja configurationModem wykonuje konfigurację modemu poprzez wysłanie serii odpowiednich poleceń AT do modemu przez skonfigurowany wcześniej interfejs szeregowy (Serial2).

Polecenia AT wysłane w trakcie funkcji konfiguracyjnej umożliwiają prawidłową pracę modemu.

ATV1 - włącza tryb opisowy. Gdy włączony jest tryb opisowy, modem wysyła szczegółowe odpowiedzi na wysyłane do niego polecenia.
AT+CMEE=2 - włącza rozszerzone raportowanie błędów. Oznacza to, że modem będzie wyświetlał bardziej szczegółowe komunikaty o błędach w przypadku niepowodzenia polecenia.
AT+IPR=115200 - ustawia szybkość transmisji na 115200 bits/s. Szybkość transmisji to szybkość przesyłania danych przez interfejs szeregowy.
ATI - pobiera informacje o modemie, takie jak producent, model i wersja oprogramowania układowego.
AT+CPIN=“Kod PIN” - jeśli karta SIM wymaga numeru PIN, to polecenie wprowadza numer PIN w celu odblokowania karty SIM.
AT+GSN - pobiera numer IMEI (International Mobile Equipment Identity) modemu. IMEI to unikalny identyfikator przypisany do każdego telefonu komórkowego lub modemu.
AT+CIMI - pobiera numer IMSI (International Mobile Subscriber Identity) modemu. IMSI to unikalny identyfikator przypisany do każdej karty SIM.
AT+QCCID - pobiera numer ICCID (Integrated Circuit Card Identifier) modemu. ICCID to unikalny identyfikator przypisany do każdej karty SIM.
AT+QICSGP=1,”Adres APN” - ustawia adres APN (nazwa punktu dostępu).

Po wysłaniu poleceń AT odpowiedzi modemu są odczytywane i zapisywane w zmiennej download_data oraz wyświetlane w Serial Monitorze. Po zakończonej konfiguracji program wyświetla w Serial Monitorze informacje o zakończeniu działania funkcji.

C++
//Function to configuration the modem
void configurationModem() 
{
  Serial2.println("ATV1"); //Set verbose mode on
  Serial2.println("AT+CMEE=2"); //Enable extended error reporting
  Serial2.println("AT+IPR=115200"); //Set baud rate to 115200
  Serial2.println("ATI"); //Get modem info
  if(SIM_CARD_PIN_NUMBER != "") 
  {   //If SIM card requires a PIN, enter it
    Serial2.println((String) "AT+CPIN=" + SIM_CARD_PIN_NUMBER);
  }
  Serial2.println("AT+GSN"); //Get IMEI number
  Serial2.println("AT+CIMI"); //Get IMSI number
  Serial2.println("AT+QCCID"); //Get ICCID number
  Serial2.println((String) "AT+QICSGP=1,\"" + APN_ADDRESS + "\""); //Set APN address
  delay(100); //100 ms pause
  download_data = Serial2.readString(); //Reading data from modem UART
  Serial.println(); //Print blank line in Serial Monitor
  Serial.println(download_data); //Print data from modem UART
  Serial.println(); //Print blank line in Serial Monitor
  Serial.println("MODEM HAS BEEN CONFIGURED"); //Printing information in Serial Monitor
  download_data = ""; //Clearing variable
}
Expand

Funkcja networkCheck

Funkcja networkCheck, która służy do sprawdzania stanu połączenia z siecią komórkową.

Funkcja cyklicznie odpytuje modem przy pomocy polecenia AT+CREG?, które służy do sprawdzenia statusu rejestracji w sieci. Gdy modem połączy się z siecią krajową, otrzymamy odpowiedź "+CREG 0,1", a w przypadku połączenia się z siecią roamingową "+CREG 0,5". W przypadku problemów z połączeniem z siecią program wykona ponownie konfigurację modemu.

Funkcja umożliwia również rozpoznanie awarii karty SIM, gdy podczas testowania programu zobaczymy komunikat "SIM card error" to znak, że powinniśmy sprawdzić, czy karta sim umieszczona w urządzeniu Micromis Base V1 jest sprawna.

C++
//Function to check status of cellular connection
void networkCheck() 
{
  download_data = ""; //Clearing variable
  Serial.println("Network test"); //Printing information in Serial Monitor
  int connecting_count = 0; //Blank initial counter
  while (download_data == "") //While download_data variable is empty
  {
    Serial2.println("AT+CREG?");  //Send command to get network registration status
    download_data = Serial2.readString(); //Reading data from modem UART
    if(download_data.indexOf("+CREG: 0,1") != -1) //Check if device is registered on home network
    {
      Serial.println("Network registered - home"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      delay(50); //50 ms pause
      break; //Quit from while loop
    } 
    else if(download_data.indexOf("+CREG: 0,5") != -1) //Check if device is registered on roaming network
    {
      Serial.println("Network registered - roaming"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      delay(50); //50 ms pause
      break; //Quit from while loop
    } 
    else if(download_data.indexOf("+CME ERROR: SIM failure") != -1) //Check if there is a SIM failure error
    {
      Serial.println("SIM card error"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      download_data = Serial2.readString(); //Reading data from modem UART
      download_data = ""; //Clearing variable
      delay(50); //50 ms pause
    } 
    else if(download_data.indexOf("+CME ERROR: 13") != -1) //Check if there is a SIM failure error in CME numeric form
    {
      Serial.println("SIM card error"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      download_data = Serial2.readString(); //Reading data from modem UART
      download_data = ""; //Clearing variable
      delay(50); //50 ms pause
    }
    else
    {
      Serial.println("Connecting to network"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      download_data = ""; //Clearing variable
      delay(1000); //1000 ms pause
      connecting_count++; //Increase the counter value by 1
    }
    if(connecting_count > 75)
    {
      setupModem(); //Check that the modem is working properly
      configurationModem(); //Make configuration again
      Serial.println("Some network connectivity issues... trying to connect again"); //Printing information in Serial Monitor
      delay(5000); //5000 ms pause
      connecting_count = 0; //Blank initial counter
    }
  }
}
Expand

Funkcja sendSMS

Funkcja sendSMS odpowiada za wysłanie wiadomości tekstowej (SMS). Przyjmuje dwa argumenty: numer telefonu i treść wiadomości SMS.

Funkcja wysyła na samym początku konfiguruje modem do wysyłania wiadomości SMS w trybie tekstowym. Polecenie AT+CMGF=1 ustawia format wiadomości tekstowej, natomiast polecenie AT+CSCS="GSM" wybiera zestaw znaków, który ma być użyty w wiadomości.

Następnie funkcja wysyła właściwą wiadomość tekstową za pomocą polecenia AT+CMGS, po której zostaje wysłany numer telefonu odbiorcy oraz wiadomość. Polecenie Serial2.write(26) wysyła znak „Ctrl-Z”, który służy do wskazania końca wiadomości.
Po wysłaniu wiadomości funkcja oczekuje na odpowiedź modemu za pomocą funkcji Serial2.readString(). Jeśli odpowiedź zawiera ciąg znaków +CMGS:, oznacza to, że wiadomość została wysłana pomyślnie.

C++
//Function to send an SMS message
void sendSMS(String phone_number, String message) 
{
  Serial2.println("AT+CMGF=1"); //Setup type of text message
  Serial2.println("AT+CSCS=\"GSM\""); //Select character set
  Serial2.println((String) "AT+CMGS=\"" + phone_number + "\""); //Send SMS in text mode
  Serial2.println(message); //Printing message in Serial Monitor
  Serial2.write(26); //Send CTRL+Z in Ascii
  Serial2.println(); //Print blank line in Serial Monitor
  //Wait for a response from the modem
  delay(2000); //2000 ms pause
    String response = Serial2.readString(); //Reading data from modem UART
    Serial.println(response); //Printing information in Serial Monitor
    if(response.indexOf("+CMGS:") != -1) //Waiting for "+CMGS:" response from modem
    {
      Serial.println("SMS message sent successfully!"); //Printing information in Serial Monitor
      Serial.println("Modem sent SMS message to:"); //Printing information in Serial Monitor
      Serial.println(TEST_PHONE_NUMBER); //Printing phone number in Serial Monitor
      Serial.println("SMS content:"); //Printing information in Serial Monitor
      Serial.println(message); //Printing message in Serial Monitor
      Serial.println(); //Print blank line in Serial Monitor
    }
}
Expand

Funkcja setup

Funkcja setup uruchamia komunikację szeregową między mikrokontrolerem ESP32, która zostaje skonfigurowana na prędkość 115200 bits/s, konfiguruje drugi port szeregowy do komunikacji z modemem o tej samej prędkości, konfiguruje pin włączania/wyłączania modemu jako wyjście, ustawia limity czasu dla portu szeregowego z obu interfejsów UART. Na końcu wywołuje omówione wcześniej funkcje setupModem, configurationModem i networkCheck.

C++
void setup() 
{
  Serial.begin(115200); //Start main UART interface
  Serial.setTimeout(100); //Set timeout for main UART interface
  Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); //Configure and start Serial2 UART interface
  pinMode(MODEM_PWRKEY, OUTPUT); //Set MODEM_PWRKEY as output
  Serial2.setTimeout(100); //Set timeout for Serial2 UART interface
  setupModem(); //Run GSM modem
  configurationModem(); //Configuration GSM modem
  networkCheck();  //Check status of cellular connection
}
Expand

Funkcja loop

Kilka pierwszych wierszy funkcji loop definiuje trzy zmienne, które są używane w dalszej części funkcji:

connection_established -  flaga logiczna używana do śledzenia, czy połączenie modemowe zostało pomyślnie nawiązane.
http_request_successful - flaga logiczna używana do śledzenia, czy żądanie HTTP zostało pomyślnie wysłane.
retry_count - jest zmienną całkowitą używaną do śledzenia liczby ponownych prób nawiązania połączenia lub wysłania zapytania HTTP przez kod.

Następna linia kodu odczytuje ciąg znaków z interfejsu UART Serial2 i przypisuje go do zmiennej download_data. Zawartość zmiennej download_data jest następnie usuwana, aby upewnić się, że jest pusta.

Powyższy kod zawiera pętlę while, która próbuje nawiązać połączenie z siecią komórkową. Pętla będzie działać do momentu nawiązania połączenia lub osiągnięcia maksymalnej liczby ponownych prób.

C++
void loop() 
{
  bool connection_established = false; //Flag to track if modem connection was established
  bool http_request_successful = false; //Flag to track if HTTP request was successful
  int retry_count = 0; //Counter to keep track of number of retries

  download_data = Serial2.readString(); //Reading data from modem UART
  download_data = ""; //Clearing variable
Expand

Polecenia AT wysyłane do modemu obejmują:

AT+QIFGCNT=0 -  służy do ustalenia trybu funkcji MUXIP na GPRS
AT+QICSGP=1,"Adres APN" - ustawia APN (nazwę punktu dostępu) dla operatora telefonii komórkowe.
AT+QIREGAPP - uruchamia zadanie TCP/IP (Transmission Control Protocol/Internet Protocol).
AT+QIACT - nawiązuje połączenie z GPRS (General Packet Radio Service).

Po wysłaniu każdego polecenia AT odpowiedź z modemu jest odczytywana i zapisywana 

w zmiennej download_data. Jeśli odpowiedzią jest „OK”, połączenie zostało nawiązane prawidłowo, a w przypadku gdy otrzymamy “ERROR” to deaktywuje aktywny wątek GPRS i uruchamia nowy.

Jeśli połączenie nie zostanie nawiązane po maksymalnej liczbie ponownych prób, na monitorze szeregowym zostanie wydrukowany komunikat o błędzie.

C++
  while (!connection_established && retry_count < MAX_RETRY_COUNT) //if connection_estabilished is false and retry count is less than MAX_RETRY_COUNT
  {
    Serial2.println("AT+QIFGCNT=0"); //Set MUXIP function to GPRS
    delay(50); //50 ms pause
    Serial2.println((String) "AT+QICSGP=1,\"" + APN_ADDRESS + "\""); //Set APN for your cellular provider
    delay(50); //50 ms pause
    Serial2.println("AT+QIREGAPP"); //Start TCPIP task
    delay(1000); //1000 ms pause
    download_data = Serial2.readString(); //Reading data from modem UART
    if(download_data.indexOf("OK") != -1) //Waiting for "OK" response from modem
    {
      Serial2.println("AT+QIACT"); //Bring up connection with GPRS
      delay(1000); //1000 ms pause
      download_data = Serial2.readString(); //Reading data from modem UART
      Serial.println(download_data); //Print data from modem UART
      if(download_data.indexOf("OK") != -1) //Waiting for "OK" response from modem
      {
        Serial.println("Modem is connected to cellular network."); //Printing information in Serial Monitor
        connection_established = true; //Changing variable value to exit from while loop
      }
      else
      {
        Serial.println("Modem not connected to cellular network."); //Printing information in Serial Monitor
        Serial2.println("AT+QIDEACT"); //Deactivate current GPRS context
        delay(5000); //5000 ms pause
      }
    }

    if(!connection_established) //if connection_estabilished variable is false
    {
      retry_count++; //Increase the counter value by 1
      Serial.println("Retrying connection establishment..."); //Printing information in Serial Monitor
      delay(1000); //1000 ms pause
    }
  }
Expand

Następna część kodu służy do wysyłania zapytania HTTP GET do serwera WWW przy użyciu polecenia AT ustawionego przez interfejs Serial2. Adres URL, do którego należy uzyskać dostęp, jest dostarczany jako ciąg znaków, a jego długość jest uzyskiwana za pomocą funkcji strlen().
Do nawiązania połączenia przez protokół HTTP oprogramowanie wykorzystuje kilka poleceń pozwalających na konfigurację połączenia oraz uzyskanie pobranych danych.

AT+QHTTPCFG="requestheader",0 - służy do wyłączenia odpytywania nagłówka zapytania.
AT+QHTTPURL=”długość adresu URL”,80 - służy do wskazania adresu URL do nawiązania połączenia. Liczba 80 wskazuje maksymalny czas oczekiwania podany w sekundach na połączenie z hostem z którym będzie łączyć się modem.
AT+QHTTPGET=80 - służy do wysłania zapytania typu GET na wskazany w poleceniu AT+QHTTPURL adres. Liczba 80 wskazuje maksymalny czas podany w sekundach na otrzymanie odpowiedzi z serwera.
AT+QHTTPREAD=80 - służy do odczytania danych z serwera. Liczba 80 wskazuje maksymalny czas podany w sekundach na otrzymanie odpowiedzi z serwera. 

Program cyklicznie odpytuje modem przy pomocy polecenia AT+QHTTPREAD aby sprawdzić czy dane zostały pobrane, po prawidłowym pobraniu danych program wykonuje parsowanie ciągu w formacie JSON.

C++
  if(connection_established) //if connection_estabilished variable is true
  {
    int URL_length = strlen(url_cstr); //Get the length of the URL
    Serial2.println("AT+QHTTPCFG=\"requestheader\",0"); //Disable request header
    delay(50); //50 ms pause
    Serial2.println("AT+QHTTPURL=" + String(strlen(url_cstr)) + ",80"); //Send the HTTP URL through AT command and Serial2 interface
    delay(1000); //1000 ms pause
    Serial2.println(url_cstr); //Send the URL string through Serial2 interface
    delay(1000); //1000 ms pause
    Serial2.println("AT+QHTTPGET=80"); //Send an HTTP GET request
    delay(500); //500 ms pause
    download_data = Serial2.readString(); //Reading data from modem UART

    retry_count = 0; //Blank initial counter
    while(!http_request_successful && retry_count < MAX_RETRY_COUNT) //if http_request_successful is false and retry count is less than MAX_RETRY_COUNT
    {
      download_data = ""; //Clearing variable
      while(download_data.indexOf("CONNECT") == -1) //Waiting for "CONNECT" response from modem
      {
        Serial2.println("AT+QHTTPREAD=80"); //Reading data from server
        download_data = Serial2.readString(); //Reading data from modem UART
        Serial.println(download_data); //Print data from modem UART
        delay(500); //500 ms pause
      }  
Expand

Jeśli parsowanie ciągu w formacie JSON zakończy się pomyślnie, wartości temperatury, ciśnienia, wilgotności i prędkości wiatru są wyodrębniane z danych JSON i wyświetlane na monitorze szeregowym.

C++
download_data.replace("OK", ""); //Replacing unnecessary data from modem response
      download_data.replace("AT+QHTTPGET=80", "");//Replacing unnecessary data from modem response
      download_data.replace("AT+QHTTPREAD=80", "");//Replacing unnecessary data from modem response
      download_data.replace("CONNECT", ""); //Replacing unnecessary data from modem response
      download_data.trim(); //Whitespaces delete
      if(download_data.indexOf("ERROR") == -1)  //Waiting for response without "ERROR" from modem
      {
        http_request_successful = true; //Changing variable value to check HTTP request status
        Serial.println(download_data); //Print data from modem UART

        //Parse the JSON response
        StaticJsonDocument<1000> doc; //Managing memory for JSON object
        DeserializationError error = deserializeJson(doc, download_data); // Deserialization of JSON data

        if(error) //If is error with JSON object deserialization
        {
          Serial.println("JSON parsing failed!"); //Printing information in Serial Monitor
          Serial.println(error.c_str());  //Display type of the error
          return; //Return error information
        } 
        else 
        {
          Serial.println("JSON parsing successful!"); //Printing information in Serial Monitor
          float temperature = doc["main"]["temp"]; //Parsing JSON temperature data
          float pressure = doc["main"]["pressure"]; //Parsing JSON pressure data
          float humidity = doc["main"]["humidity"]; //Parsing JSON humidity data
          float wind_speed = doc["wind"]["speed"]; //Parsing JSON wind speed data
          Serial.print("Temperature: "); //Printing information in Serial Monitor
          Serial.println(temperature); //Printing temperature in Serial Monitor
          Serial.print("Pressure: "); //Printing information in Serial Monitor
          Serial.println(pressure); //Printing pressure in Serial Monitor
          Serial.print("Humidity: "); //Printing information in Serial Monitor
          Serial.println(humidity); //Printing humidity in Serial Monitor
          Serial.print("Wind speed: "); //Printing information in Serial Monitor
          Serial.println(wind_speed); //Printing wind speed in Serial Monitor
Expand

Jeśli temperatura jest większa lub równa -1, kod wysyła wiadomość SMS, poprzednio jednak sprawdzając czas, jaki upłynął od wysłania ostatniej wiadomości oraz wartość zmiennej logicznej FIRST_MESSAGE_SENT. Jeśli którykolwiek z tych warunków jest spełniony, tworzona jest treść wiadomości zawierająca aktualną temperaturę, wilgotność, prędkość wiatru i wartości ciśnienia. Następnie wywołuje funkcję sendSMS z numerem telefonu i treścią wiadomości jako parametrami. Bieżący czas jest następnie przechowywany w zmiennej last_message_sent_time, a flaga FIRST_MESSAGE_SENT jest ustawiana na wartość true.

C++
//Send an SMS message
          if (temperature >= -1) //if temperature is less than -1 C
          {
            unsigned long current_time = millis(); //create variable for millis time function
            if ((current_time - last_message_sent_time >= TWELVE_HOURS) || !FIRST_MESSAGE_SENT) {
              //Parsing message data
              String message = "Alert! You may expecting temperature of " + String(temperature) + " degrees with humidity of: " + String(humidity) + "%" + ", wind speed of: " + String(wind_speed) + "km/h and pressure " + String(pressure) + "Pa";
              sendSMS(TEST_PHONE_NUMBER, message);     //Call the SMS function and pass parameters 
              last_message_sent_time = current_time;  //Update the time of the last message sent
              FIRST_MESSAGE_SENT = true; //Set FIRST_MESSAGE_SENT variable as true
            } 
            else 
            {
              Serial.print("SMS sending is currently restricted due to a previous message sent within the last 12 hours."); //Printing information in Serial Monitor
            }
          }
Expand

Poniższy warunek gwarantuje, że wiadomość zostanie wysłana tylko wtedy, gdy od wysłania ostatniej wiadomości minęło 12 godzin lub żadna wiadomość nie została jeszcze wysłana.

C++
(current_time - last_message_sent_time >= TWELVE_HOURS) || !FIRST_MESSAGE_SENT

Końcowe wiersze funkcji loop() zawierają opóźnienie 300 000 milisekund (czyli 5 minut) przed ponownym wykonaniem funkcji.

Wszelkiego rodzaju powiadomienia są obecne w naszym życiu od bardzo dawna, jednak teraz wiecie w jaki sposób je generować i dostosowywać do swoich potrzeb. Macie już pomysł jak wykorzystać wiedzę z tego poradnika?

O Autorze

Nikola Pająk

A graduate of IT Technology at a university in Denmark, she is passionate about electronics, cognitive science and playing chess. Enjoying constantly expanding her knowledge. A lover of sports, cooking and spending time outdoors.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Label
Copyright © 2023 Device Prototype 
|
Polityka prywatności