Przesyłanie odczytów z czujników do Arkuszy Google przy pomocy płytki Micromis Base V1

Arkusze Google to niezwykle popularne narzędzie pozwalające na tworzenie arkuszy kalkulacyjnych, które dzięki możliwości tworzenia indywidualnych punktów dostępu daje ogromne możliwości w zakresie tworzenia prostych baz danych, które mogą przechowywać na przykład odczyty z czujników czy też dane lokalizacyjne. W tym poradniku zaprezentujemy w jaki sposób wykonać integrację z Arkuszami Google i przesłać do nich dane bezpośrednio z płytki Micromis Base V1.

Podzespoły

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

Opis projektu

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.

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 połączenie 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 - Utworzenie Arkusza Google oraz dodanie do niego punktu dostępu

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

Krok 4 - 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 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

Krok 5 - 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 6 - Efekt projektu

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.

Konfigurowanie Arkuszy Google

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:

HTTP
https://docs.google.com/spreadsheets/d/1e6NvGfexudk5Ta_zua-NnewG8E28TEEAe75JnFdRt1k/edit#gid=0

A samo ID to:

HTTP
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:

JavaScript
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.

JavaScript
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ść:

JavaScript
var sheet_id = "1e6NvGfexudk5Ta_zua-NnewG8E28TEEAe75JnFdRt1k";

Przykładowo dla ID “1111aaaa1111” wynosiłaby:

JavaScript
var sheet_id = "1111aaaa1111";

Nazwa arkusza umieszczona jest w zmiennej sheet_name, w drugiej linii kodu:

JavaScript
var sheet_name = "Temp_log";

Dla arkusza o nazwie “Temperature_reader” wynosiłaby:

JavaScript
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:

  • Nowe wdrożenie
  • Zarządzaj wdrożeniami
  • Testuj wdrożenia

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
  • Wykonaj jako
  • Kto ma dostęp

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:

HTTP
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:

JavaScript
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ć:

HTTP
?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ć:

HTTP
?temperature=-1.0&datetime=15-11-03%2009:00:00&id=5

Pełen adres URL dla pierwszego odczytu powinien w naszym przypadku wynosi:

HTTP
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:

HTTP
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:

  • ? – znak zapytania oddziela ciągi zapytań, sygnalizuje w którym miejscu rozpoczyna się miejsce na dane, które przesyłamy
  • & – znak oddzielający przesyłane atrybuty
  • %20 – ten symbol to nic innego jak spacja w kodowaniu adresów URL, dzięki niej data oraz godzina w naszym dokumencie będzie mieć formę:
C++
23-06-01 15:15:00

A nie:

C++
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.

Kod – krok po kroku

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.

C++
//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.

C++
//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:

C++
String Sensor_ID = "1";

W przypadku identyfikatora “Sensor_greenhouse” zmienna będzie wynosić:

C++
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:

C++
String Sensor_ID = "Sensor%20Garden";

A nie:

C++
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:

C++
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ć:

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 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ć:

C++
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ć:

C++
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

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.

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
  } 
  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
      }
    }
  }
}
Expand

Funkcja configurationModem

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.

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
  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
}
Expand

Funkcja networkCheck

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.

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 getModemTime

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.

C++
//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
}
Expand

Funkcja sendUpdate

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.

C++
//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
      }
    }
  }
Expand

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.

C++
  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
}
Expand

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.

Funkcja setup

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ą.

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
  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
}
Expand

Funkcja loop

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.

C++
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?

O Autorze

Nikola Pająk

Absolwentka technologii informatycznych na uniwersytecie w Danii, pasjonuje się elektroniką, kognitywistyką i grą w szachy. Lubi stale poszerzać swoją wiedzę. Miłośniczka sportu, gotowania i spędzania czasu na świeżym powietrzu.

Dodaj komentarz

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

Label
Copyright © 2023 Device Prototype 
|
Polityka prywatności