W trakcie tego poradnika wykonamy projekt urządzenia, które przy pomocy płytki Micromis Base V1 będzie przesyłać cykliczne raporty o temperaturze bezpośrednio do Arkuszy Google. Komunikację nawiążemy z Arkuszami Google przez wbudowany modem GSM, którym będziemy sterować przy pomocy komend AT. Aby całkowicie zredukować użycie zewnętrznych komponentów do pomiarów temperatury użyjemy wbudowany w płytkę Micromis Base V1 czujnik temperatury LM75.
Wykonanie poradnika wymaga posiadania płytki Micromis Base V1, anteny GSM ze złączem U.FL oraz aktywnej karty SIM.
Środowisko stworzone w tym projekcie może zostać wykorzystane do generowania ciekawych wykresów i raportów tworzonych na podstawie danych przesyłanych do arkuszy kalkulacyjnych.
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 połączenie 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.
Aby stworzyć projekt przedstawiony w poradniku niezbędne jest posiadanie konta Google oraz dostęp do Dysku Google. Stworzenia arkusza do którego będziemy wysyłać dane jest bardzo proste i wymaga jedynie kilku kroków, które zostały omówione w sekcji “Konfiguracja Arkuszy Google”
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 jest ustawienie odpowiedniego adres APN dla 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.
Po stworzeniu miejsca na dane oraz dodaniu skryptu do Arkuszy Google zyskamy punkt dostępu na który przy pomocy płytki Micromis Base V1 oraz omówionego w tym poradniku oprogramowania będziemy mogli przesyłać dane z czujników.
Arkusze Google umożliwiają tworzenie darmowych punktów dostępu, które pozwalają na dodawanie do pól arkusza dowolnych danych. Do utworzenia punktu dostępu potrzebujemy konta Google oraz dostępu do Dysku Google. Na samym początku tworzymy dokument Arkuszy Google, możemy nadać mu dowolną nazwę.
Już teraz w miejscu adresu URL w przeglądarce powinien pojawić się ciąg znaków, który wykorzystamy do przesyłania danych. Najważniejszym elementem całego adresu URL jest zawarte w nim ID naszego arkusza. W naszym przypadku adres URL to:
https://docs.google.com/spreadsheets/d/1e6NvGfexudk5Ta_zua-NnewG8E28TEEAe75JnFdRt1k/edit#gid=0
A samo ID to:
1e6NvGfexudk5Ta_zua-NnewG8E28TEEAe75JnFdRt1k
Po uzyskaniu ID dokumentu możemy przejść do nadania nazwy naszego arkusza, element ten może okazać się przydatny gdy do jednego dokumentu będziemy chcieli przesyłać kilka rodzajów danych, w naszym przypadku nazwa arkusza to “Temp_log”.
W zależności od preferencji oraz potrzeb w dokumencie możemy utworzyć nagłówki dla określonych kolumn. W naszym przypadku chcemy przesyłać dane takie jak temperatura, data i godzina oraz ID konkretnego sensora w przypadku gdy te same dane będzie wysyłać kilka urządzeń.
Oczywiście nic nie stoi na przeszkodzie aby przesyłać do arkusza zdecydowanie większą ilość danych, podane tutaj nazwy nagłówków kolumn są dostosowane do przykładu omawianego w poradniku
Po nadaniu nazwy dla arkusza w którym będą przechowywane dane z czujników oraz utworzeniu nagłówków możemy przejść do utworzenia punktu dostępu, aby to zrobić musimy wejść kolejno w Rozszerzenia > Apps Script
Po kliknięciu w Apps Script w przeglądarce powinna otworzyć się nowa karta w której możemy dodać własne skrypty, które będą przetwarzane i dodawane do arkuszy. Do utworzenia skryptu niezbędne będzie nam ID dokumentu oraz nazwa arkusza, w naszym przypadku:
ID = 1e6NvGfexudk5Ta_zua-NnewG8E28TEEAe75JnFdRt1k
Nazwa arkusza = Temp_log
Sam kod służący do umieszczania danych w arkuszu jest niezwykle prosty i składa się tylko z kilku linijek kodu. Na samym początku do zmiennych przypisywane są wartości takie jak ID dokumentu i nazwa arkusza, następnie program analizuje zmienne, które są przesyłane, a na samym końcu znajduje się polecenie, które dodaje przesłane wartości do arkusza. Poniższy kod należy wkleić do miejsca na program, w którym domyślnie znajduje się pusta funkcja.
var sheet_id = "1e6NvGfexudk5Ta_zua-NnewG8E28TEEAe75JnFdRt1k";
var sheet_name = "Temp_log";
function doGet(e){
var ss = SpreadsheetApp.openById(sheet_id);
var sheet = ss.getSheetByName(sheet_name);
var temperature = e.parameter.temperature;
var datetime = e.parameter.datetime;
var id = e.parameter.id;
sheet.appendRow([temperature,datetime,id]);
}
W przypadku użycia identycznego układu kolumn w kodzie należy zmienić tylko ID dokumentu oraz nazwę arkusza. ID arkusza zmieniamy w pierwszej linii, która obecnie ma wartość:
var sheet_id = "1e6NvGfexudk5Ta_zua-NnewG8E28TEEAe75JnFdRt1k";
Przykładowo dla ID “1111aaaa1111” wynosiłaby:
var sheet_id = "1111aaaa1111";
Nazwa arkusza umieszczona jest w zmiennej sheet_name, w drugiej linii kodu:
var sheet_name = "Temp_log";
Dla arkusza o nazwie “Temperature_reader” wynosiłaby:
var sheet_name = "Temperature_reader";
Prawidłowo dodany kod powinien prezentować się jak na grafice poniżej:
Po dodaniu prawidłowych wartości do zmiennych możemy przejść do generowania punktu dostępu. Aby to zrobić musimy kliknąć na niebieski przycisk “Wdróż” w prawym górnym rogu strony. Po kliknięciu wyświetlą nam się trzy warianty:
Ze względu na to, że nie mamy jeszcze utworzonego punktu dostępu klikamy “Nowe wdrożenie”, po chwili powinno otworzyć się okno dialogowe w którym klikamy na koło zębate i wybieramy “Aplikacja internetowa”.
Po wybraniu aplikacji internetowej w panelu konfiguracyjnym pojawi się kilka pól:
Opis wdrożenia jest kwestią indywidualną i nie trzeba go wypełniać. Dla pola “Wykonaj jako” zaznaczamy “Ja”, dzięki czemu wszystkie dane wysyłane przez urządzenia będą identyfikowane jako dane dodane z naszego konta. W polu “Kto ma dostęp” zaznaczamy “Każdy” dzięki czemu dane będą mogły trafiać do arkusza w prosty sposób, bez konieczności wcześniejszego logowania się na konto Google. Prawidłowo skonfigurowany pola powinny wyglądać jak na grafice poniżej:
Po skonfigurowaniu punktu dostępu klikamy “Wdróż”, po załadowaniu się wszystkich danych Google poprosi nas o wyrażenie zgody na dostęp utworzonego przez nas projektu do naszego konta Google. Aby projekt mógł prawidłowo funkcjonować musimy wyrazić zgodę.
Po chwili powinno pojawić się okno “Nowe wdrożenie”, a w nim ID naszego wdrożenia oraz bezpośredni URL do punktu dostępu. Obie te wartości są przypisywane indywidualnie do konta Google i nie powinniśmy ich nigdzie udostępniać, konto na którym prezentujemy działanie poradnika zostało stworzone wyłącznie w tym celu – dlatego zachowane są wszystkie dane.
Adres URL naszego punktu dostępu należy skopiować, będzie nam potrzebne do stworzenia kodu na płytce Micromis Base V1.
Obecnie adres URL, który będziemy wykorzystywać w poradniku to:
https://script.google.com/macros/s/AKfycbxNgg50yw9BFUv2CP464vh1-IawVx9Km2JSQ7fC6lSAdk1WtRSR19PxPHaeVrZQRPOa/exec
Działanie punktu dostępu możemy sprawdzić przy pomocy przeglądarki. Aby to zrobić musimy wkleić w miejsce na adres URL skopiowany adres naszego punktu dostępu. Do testu potrzebne będą nam również wartości przesyłanych zmiennych, które ustaliliśmy w kodzie dla punktu dostępu:
var temperature = e.parameter.temperature;
var datetime = e.parameter.datetime;
var id = e.parameter.id;
Aby wartość zmiennej znalazła się w arkuszu musi zostać prawidłowo przesłana przy pomocy wywoływanego adresu URL. Przykładowo jeśli chcemy przesłać informacje o danych z czujnika posiadającego ID 11, który 1 czerwca 2023 roku o godzinie 15:15 pobrał temperaturę wynoszącą 25,5 C to musimy stworzyć zakończenie adresu URL który będzie wynosić:
?temperature=25.5&datetime=23-06-01%2015:15:00&id=11
A dla danych odczytanych z czujnika o ID 5, który 3 listopada 2015 roku o godzinie 9:00 pobrał temperaturę wynoszącą -1.0 C musimy stworzyć zakończenie adresu URL, które będzie wynosić:
?temperature=-1.0&datetime=15-11-03%2009:00:00&id=5
Pełen adres URL dla pierwszego odczytu powinien w naszym przypadku wynosi:
https://script.google.com/macros/s/AKfycbxNgg50yw9BFUv2CP464vh1-IawVx9Km2JSQ7fC6lSAdk1WtRSR19PxPHaeVrZQRPOa/exec?temperature=25.5&datetime=23-06-01%2015:15:00&id=11
A dla drugiego odczytu:
https://script.google.com/macros/s/AKfycbxNgg50yw9BFUv2CP464vh1-IawVx9Km2JSQ7fC6lSAdk1WtRSR19PxPHaeVrZQRPOa/exec?temperature=-1.0&datetime=15-11-03%2009:00:00&id=5
W ciągach znaków wystąpiły trzy symbole, które mogą na początku wydawać się niejasne, jednak po krótkiej analizie łatwo zrozumieć ich zadanie:
23-06-01 15:15:00
A nie:
23-06-0115:15:00
Gdy skopiujemy jeden z adresów URL z przypisanymi danymi możemy wkleić go w pasek adresowy przeglądarki i kliknąć enter, po chwili otrzymamy informację, że skrypt został wykonany ale nie zostały zwrócone żadne dane:
Dodatkowo gdy spojrzymy na nasz arkusz kalkulacyjny to powinniśmy zobaczyć dane, które przed chwilą wysłaliśmy:
Aby sprawdzić czy kolejne dane dopisywane są w kolejnych wierszach możemy wysłać jeszcze jedną paczkę danych. Po chwili pojawi się ona w naszym arkuszu.
Gdy mamy pewność, że punkt dostępu oraz arkusz pracują prawidłowo możemy przejść do omówienia oprogramowania układowego dla płytki Micromis Base V1.
Pierwsze wiersze oprogramowania zawierają konfigurację stałych oraz zmiennych, które zarządzają pracą oprogramowania. Druga oraz trzecia linijka kodu służy do przypisania biblioteki Temperature_LM75_Derived oraz utworzenia obiektu dla niej. Biblioteka ta została użyta ze względu na wykorzystanie wbudowanego w płytkę Micromis Base V1 czujnika temperatury LM75.
//Include library of temperature sensor
#include <Temperature_LM75_Derived.h>
Generic_LM75 temperature; //Creating object for library
W kodzie zdefiniowane są również piny RX i TX wbudowanego w płytkę deweloperską Micromis Base V1 modemu Quectel M65, 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
Zmienne odpowiedzialne za prawidłową komunikację z Arkuszami Google to zmienne URL oraz Sensor_ID. Zmienna Sensor_ID przechowuje identyfikator czujnika, który pozwala rozpoznać źródło pomiarów w przypadku tworzenia systemu zbudowanego z większej ilości urządzeń. Ze względu na to, że w skrycie stworzonym w Apps Script nie określiliśmy typu danych, które będą określać identyfikator urządzenia możemy jako ID ustawić dowolną wartość tekstową lub liczbową. Domyślnie zmienna Sensor_ID ma wartość “1” i wynosi:
String Sensor_ID = "1";
W przypadku identyfikatora “Sensor_greenhouse” zmienna będzie wynosić:
String Sensor_ID = "Sensor_greenhouse";
Warto pamiętać o tym, że zmienne te będą dodawane w późniejszym etapie kodu do adresu URL, więc spacja w wartości zmiennej musi zostać zastąpiona znakiem “%20” tak więc ID czujnika o identyfikatorze “Sensor Garden” powinno zostać zapisane w zmiennej jako:
String Sensor_ID = "Sensor%20Garden";
A nie:
String Sensor_ID = "Sensor Garden";
Kolejna zmienna to URL, która przechowuje adres URL punktu dostępu, jest to ten sam adres, który skopiowaliśmy wcześniej z Apps Script, w naszym przypadku zmienna URL wynosi:
String URL = "https://script.google.com/macros/s/AKfycbxNgg50yw9BFUv2CP464vh1-IawVx9Km2JSQ7fC6lSAdk1WtRSR19PxPHaeVrZQRPOa/exec";
Warto pamiętać o tym aby w zmiennej znalazł się tylko skopiowany adres, bez dodatkowych znaków, w innym wypadku urządzenie nie będzie mogło połączyć się ze stworzonym wcześniej punktem dostępu.
Po wprowadzeniu odpowiedniego adresu URL oraz utworzeniu identyfikatora urządzenia musimy sprawdzić czy umieszczona w płytce Micromis Base V1 karta SIM, wymaga wprowadzenia kod PIN podczas rozpoczęcia pracy modemu. W przypadku gdy karta SIM wymaga kodu to należy go wprowadzić 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 operator sieci komórkowej z którego usług korzystamy. 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 T-mobile w Czechach adres APN wynosi “internet.t-mobile.cz”, więc wartość zmiennej będzie wynosić:
String APN_ADDRESS = “internet.t-mobile.cz”;
Adres APN konkretnego operatora możemy łatwo sprawdzić przy pomocy strony apn.how.
apn.how
W kolejnych zmiennych program ustala pin pozwalający na uruchomienie modemu oraz zmienne operacyjne wykorzystywane w trakcie działania kodu.
Funkcja setupModem ma za zadanie uruchomić modem. Aby upewnić się, czy modem jest już włączony, program wysyła komendę AT i następnie sprawdza, czy odpowiedź zawiera słowo “OK”. To sygnalizuje, że modem jest gotowy do komunikacji.
Jeśli modem jest wyłączony lub nie odpowiada, funkcja uruchamia go poprzez ustawienie pinu MODEM_PWRKEY na stan wysoki przez 1000 ms. Gdy modem zostanie włączony, program wysyła komendę ATE1, aby włączyć tryb echa. Tryb ten powoduje, że modem będzie przesyłał wszystkie generowane dane na interfejs UART.
Funkcja następnie odczytuje dane z interfejsu UART i szuka słowa “Call Ready“, które oznacza, że modem jest gotowy do komunikacji.
Jeśli po 10 próbach modem nie uruchomi się poprawnie, funkcja ponownie uruchamia go przy pomocy pinu PWR_KEY i zeruje licznik prób.
//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
}
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
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 konfiguruje modem przez wysłanie szeregu poleceń AT do niego za pośrednictwem interfejsu szeregowego (Serial2), który został wcześniej skonfigurowany.
Polecenia AT, które zostaną wysłane w trakcie funkcji konfiguracyjnej, umożliwiają prawidłowe funkcjonowanie modemu. Wśród tych poleceń znajdują się m.in.:
ATV1 – włącza tryb opisowy, który umożliwia modemu wysyłanie szczegółowych odpowiedzi na wysyłane do niego polecenia.
AT+CMEE=2 – włącza rozszerzone raportowanie błędów, które skutkuje wyświetlaniem bardziej szczegółowych komunikatów o błędach w przypadku niepowodzenia polecenia.
AT+IPR=115200 – ustawia szybkość transmisji na 115200 bits/s, czyli szybkość przesyłania danych przez interfejs szeregowy.
ATI – pobiera informacje o modemie, takie jak producent, model i wersja oprogramowania układowego.
AT+QNITZ=1 – aktywuje synchronizację czasu z siecią komórkową.
AT+CTZU=3 – konfiguruje zapisywanie czasu pobranego z sieci komórkowej w pamięci RTC modemu.
AT+CPIN=“Kod PIN” – wprowadza numer PIN, jeśli karta SIM wymaga jego wprowadzenia w celu odblokowania karty SIM.
AT+GSN – pobiera numer IMEI modemu, który jest jego unikalnym identyfikatorem.
AT+CIMI – pobiera numer IMSI modemu, który jest również jego unikalnym identyfikatorem.
AT+QCCID – pobiera numer ICCID modemu, który również jest jego unikalnym identyfikatorem.
AT+QICSGP=1,”Adres APN” – ustawia adres APN, który jest nazwą punktu dostępu.
Odpowiedzi modemu na wysłane polecenia AT są odczytywane i zapisywane w zmiennej download_data oraz wyświetlane w Serial Monitorze. Po zakończeniu konfiguracji funkcja wyświetla w Serial Monitorze informacje o zakończeniu swojego działania.
//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
Serial2.println("AT+QNITZ=1"); //Synchronize time from GSM network
Serial2.println("AT+CTZU=3"); //Update network synchronized time to RTC
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 została stworzona w celu sprawdzania stanu połączenia z siecią komórkową.
Aby sprawdzić status rejestracji w sieci, funkcja cyklicznie wysyła polecenie AT+CREG? do modemu. Odpowiedź zwracana przez modem pozwala na rozpoznanie, czy połączenie z siecią krajową lub roamingową zostało nawiązane. W przypadku wykrycia problemów z połączeniem, funkcja ponownie wykonuje konfigurację modemu. W przypadku połączenia z siecią roamingową modem zwróci odpowiedź “+CREG 0,1”, a przypadku połączenia w roamingu “+CREG 0,5”.
Ponadto, funkcja może pomóc w wykryciu problemów z kartą SIM. Jeśli podczas testowania programu pojawi się komunikat “SIM card error“, warto sprawdzić, czy karta SIM znajdująca się 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 getModemTime służy do pobierania aktualnej daty i godziny z modemu Quectel. Funkcja najpierw wysyła polecenie “AT+CCLK?” do modemu, aby pobrać lokalny czas. Następnie funkcja oczekuje na odpowiedź modemu i parsuje otrzymane dane, aby uzyskać datę i godzinę. Funkcja usuwa również niepotrzebne dane z odpowiedzi modemu, takie jak “AT+CCLK?” i “OK”, a także usuwa cudzysłowy i białe znaki. Ostatecznie funkcja zwraca datę i godzinę w postaci łańcucha znaków, który jest gotowy do dalszego przetwarzania.
//Function to get time from GSM network
String getModemTime()
{
Serial2.println("AT+CCLK?"); //Get local time
download_data = ""; //Clearing variable
String date_and_time = ""; //Create variable for storage infomation about date and time
while(download_data == "") //While download_data variable is empty
{
download_data = Serial2.readString(); //Reading data from modem UART
if(download_data.indexOf("AT+CCLK?") > -1) //Waiting for "AT+CCLK?" response from modem
{
download_data.replace("AT+CCLK?"," "); //Replacing unnecessary data from modem response
download_data.replace("+CCLK: "," "); //Replacing unnecessary data from modem response
download_data.replace("OK"," "); //Replacing unnecessary data from modem response
download_data.replace("\""," "); //Replacing unnecessary data from modem response
download_data.trim(); //Removing whitespace characters
date_and_time.concat(download_data.substring(0,2)); //Substract year
date_and_time.concat("/"); //Put / symbol into variable
date_and_time.concat(download_data.substring(3,5)); //Substract month
date_and_time.concat("/"); //Put / symbol into variable
date_and_time.concat(download_data.substring(6,8)); //Substract day
date_and_time.concat("%20"); //Put URL space symbol into variable
date_and_time.concat(download_data.substring(9,11)); //Substract hour
date_and_time.concat(":"); //Put : symbol into variable
date_and_time.concat(download_data.substring(12,14)); //Substract minutes
date_and_time.concat(":"); //Put : symbol into variable
date_and_time.concat(download_data.substring(15,17)); //Substract seconds
}
}
return date_and_time; //Return date and time data
}
Funkcja sendUpdate służy do przesyłania danych do punktu dostępu Arkuszy Google za pomocą modemu Quectel M65. Funkcja przyjmuje dwa argumenty: wartość temperatury oraz datę i czas. Na początku funkcja ustawia zmienną connection_established na false, a następnie w pętli while funkcja próbuje nawiązać połączenie z siecią komórkową za pomocą komend AT wysyłanych do modemu. Jeśli połączenie zostanie nawiązane, zmienna connection_established zostanie ustawiona na true, a pętla while zostanie przerwana.
//Function to uplad data to Google Sheets endpoint
void sendUpdate(float temperature, String datetime)
{
connection_established = false; //Set connection_estabilished variable as false
while (!connection_established) //While connection_estabilished variable is false
{
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
}
}
}
Następnie funkcja tworzy poprawny URL zawierający informacje o temperaturze, dacie/czasie oraz identyfikatorze czujnika. Po utworzeniu URL funkcja nawiązuje połączenie z punktem dostępu Arkuszy Google i przesyła dane poprzez połączenie HTTP.
Serial.print("Temperature: "); //Printing information in Serial Monitor
Serial.println(temperature); //Printing temperature in Serial Monitor
Serial.print("Datetime: "); //Printing information in Serial Monitor
Serial.println(datetime); //Printing time and date in Serial Monitor
String current_URL = String(URL+"?temperature="+temperature+"&datetime="+datetime+"&id="+Sensor_ID); //Creating a valid URL including temperature, date, time and sensor ID
Serial.print("Google Sheet URL: "); //Printing information in Serial Monitor
Serial.println(current_URL); //Printing URL for connection in Serial Monitor
int URL_length = current_URL.length(); //Measure URL length
Serial2.println("AT+QHTTPCFG=\"requestheader\",0"); //Disable header requesting in HTTP connection
delay(50); //50 ms pause
Serial2.println("AT+QHTTPURL=" + String(URL_length) + ",80"); //Send information about URL address and timeout of this connection
delay(1000); //1000 ms pause
Serial2.println(current_URL); //Send URL to modem via UART
delay(1000); //1000 ms pause
Serial2.println("AT+QHTTPGET=80"); //Create HTTP GET request
delay(1000); //1000 ms pause
download_data = Serial2.readString(); //Reading data from modem UART
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
}
download_data = Serial2.readString(); //Reading data from modem UART
Serial.println(download_data); //Print data from modem UART
}
W funkcji używane są następujące komendy AT:
AT+QIFGCNT=0 – ustawia funkcję MUXIP na GPRS
AT+QICSGP=1,”adres APN” – ustawia adres APN dla operatora komórkowego
AT+QIREGAPP – uruchamia zadanie TCPIP
AT+QIACT – uruchamia połączenie GPRS
AT+QIDEACT – deaktywuje bieżące połączenie GPRS
AT+QHTTPCFG=”requestheader”,0 – wyłącza żądanie nagłówka w połączeniu HTTP
AT+QHTTPURL=”długość adresu URL”,80 – wysyła informacje o adresie URL i timeout dla tego połączenia
AT+QHTTPGET=80 – tworzy żądanie HTTP GET
AT+QHTTPREAD=80 – odczytuje dane z serwera
sendUpdate sprawdza również, czy połączenie z punktem dostępu Arkuszy Google zostało nawiązane poprawnie, a w przypadku niepowodzenia wyświetla stosowne komunikaty na monitorze szeregowym. Funkcja również wyświetla informacje o przesyłanych danych oraz URL na monitorze szeregowym.
To funkcja konfiguracyjna, która jest wywoływana tylko raz podczas uruchamiania urządzenia. Funkcja konfiguruje porty szeregowe i urządzenia I2C, ustawia pin MODEM_PWRKEY jako wyjście i uruchamia modem GSM. Po włączeniu modemu wywołuje funkcję konfigurujące ustawienia modułu GSM oraz sprawdza jego połączenie z siecią.
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
Wire.begin(); //Run resources for I2C temperature sensor
Serial2.setTimeout(100); //Set timeout for Serial2 UART interface
setupModem(); //Run GSM modem
configurationModem(); //configuration GSM modem
networkCheck(); //Check status of cellular connection
}
Składnia funkcji loop opiera się tylko na cyklicznym wywołaniu funkcji sendUpdate, która przesyła dane do punktu dostępu Arkuszy Google, po czym czeka minutę na ponowne wykonanie tego zadania.
void loop()
{
sendUpdate(temperature.readTemperatureC(), getModemTime()); //Send data to Google Sheet endpoint
delay(60000); //Wait a one minute
}
Wykorzystanie Arkuszy Google może okazać się przydatne w wielu projektach, szczególnie gdy wiecie już w jaki sposób przesłać do nich wybrane dane. Macie już pomysły na własne projekty z wykorzystaniem przesyłania danych do arkuszy?