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.
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.
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.
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”
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.
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.
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.
//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.
//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:
#define CITY = “Warszawa”
#define COUNTRY_CODE = “PL”
Dla Londynu zmienne te będą wynosić:
#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:
//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.
#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:
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ć:
String TEST_PHONE_NUMBER = “+442076350000”
A dla numeru telefonu z Polski:
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ć:
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ć:
String APN_ADDRESS = “internet”
Przykładowo dla sieci Deutsche Telekom adres APN wynosi “internet.telekom”, więc wartość zmiennej będzie wynosić:
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 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ę.
//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
}
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.
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
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.
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
}
}
}
}
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.
//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
}
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.
//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
}
}
}
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.
//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
}
}
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.
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
}
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.
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
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.
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
}
}
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.
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
}
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.
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
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.
//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
}
}
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.
(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?