13 KiB
Podłącz swoje urządzenie IoT do chmury - Wio Terminal
W tej części lekcji podłączysz swój Wio Terminal do IoT Hub, aby wysyłać dane telemetryczne i odbierać polecenia.
Podłącz swoje urządzenie do IoT Hub
Kolejnym krokiem jest podłączenie urządzenia do IoT Hub.
Zadanie - podłącz do IoT Hub
-
Otwórz projekt
soil-moisture-sensor
w VS Code. -
Otwórz plik
platformio.ini
. Usuń zależność od bibliotekiknolleary/PubSubClient
. Była ona używana do połączenia z publicznym brokerem MQTT, ale nie jest potrzebna do połączenia z IoT Hub. -
Dodaj następujące zależności bibliotek:
seeed-studio/Seeed Arduino RTC @ 2.0.0 arduino-libraries/AzureIoTHub @ 1.6.0 azure/AzureIoTUtility @ 1.6.1 azure/AzureIoTProtocol_MQTT @ 1.6.0 azure/AzureIoTProtocol_HTTP @ 1.6.0 azure/AzureIoTSocket_WiFi @ 1.0.2
Biblioteka
Seeed Arduino RTC
dostarcza kod do obsługi zegara czasu rzeczywistego w Wio Terminal, który jest używany do śledzenia czasu. Pozostałe biblioteki umożliwiają Twojemu urządzeniu IoT połączenie z IoT Hub. -
Dodaj następujący wpis na końcu pliku
platformio.ini
:build_flags = -DDONT_USE_UPLOADTOBLOB
Ustawia to flagę kompilatora, która jest wymagana podczas kompilacji kodu Arduino IoT Hub.
-
Otwórz plik nagłówkowy
config.h
. Usuń wszystkie ustawienia MQTT i dodaj następującą stałą dla ciągu połączenia urządzenia:// IoT Hub settings const char *CONNECTION_STRING = "<connection string>";
Zamień
<connection string>
na ciąg połączenia dla Twojego urządzenia, który skopiowałeś wcześniej. -
Połączenie z IoT Hub wykorzystuje token oparty na czasie. Oznacza to, że urządzenie IoT musi znać aktualny czas. W przeciwieństwie do systemów operacyjnych takich jak Windows, macOS czy Linux, mikrokontrolery nie synchronizują automatycznie aktualnego czasu przez Internet. Oznacza to, że musisz dodać kod do pobrania aktualnego czasu z serwera NTP. Po pobraniu czasu można go zapisać w zegarze czasu rzeczywistego w Wio Terminal, co pozwala na uzyskanie poprawnego czasu w późniejszym terminie, o ile urządzenie nie straci zasilania. Dodaj nowy plik o nazwie
ntp.h
z następującym kodem:#pragma once #include "DateTime.h" #include <time.h> #include "samd/NTPClientAz.h" #include <sys/time.h> static void initTime() { WiFiUDP _udp; time_t epochTime = (time_t)-1; NTPClientAz ntpClient; ntpClient.begin(); while (true) { epochTime = ntpClient.getEpochTime("0.pool.ntp.org"); if (epochTime == (time_t)-1) { Serial.println("Fetching NTP epoch time failed! Waiting 2 seconds to retry."); delay(2000); } else { Serial.print("Fetched NTP epoch time is: "); char buff[32]; sprintf(buff, "%.f", difftime(epochTime, (time_t)0)); Serial.println(buff); break; } } ntpClient.end(); struct timeval tv; tv.tv_sec = epochTime; tv.tv_usec = 0; settimeofday(&tv, NULL); }
Szczegóły tego kodu wykraczają poza zakres tej lekcji. Definiuje on funkcję
initTime
, która pobiera aktualny czas z serwera NTP i ustawia zegar w Wio Terminal. -
Otwórz plik
main.cpp
i usuń cały kod MQTT, w tym nagłówekPubSubClient.h
, deklarację zmiennejPubSubClient
, metodyreconnectMQTTClient
icreateMQTTClient
, oraz wszelkie wywołania tych zmiennych i metod. Plik ten powinien zawierać jedynie kod do połączenia z WiFi, pobrania wilgotności gleby i utworzenia dokumentu JSON z tymi danymi. -
Dodaj następujące dyrektywy
#include
na początku plikumain.cpp
, aby dołączyć nagłówki bibliotek IoT Hub oraz ustawiania czasu:#include <AzureIoTHub.h> #include <AzureIoTProtocol_MQTT.h> #include <iothubtransportmqtt.h> #include "ntp.h"
-
Dodaj następujące wywołanie na końcu funkcji
setup
, aby ustawić aktualny czas:initTime();
-
Dodaj następującą deklarację zmiennej na początku pliku, tuż poniżej dyrektyw include:
IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle;
Deklaruje to
IOTHUB_DEVICE_CLIENT_LL_HANDLE
, uchwyt do połączenia z IoT Hub. -
Poniżej tego dodaj następujący kod:
static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *user_context) { if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED) { Serial.println("The device client is connected to iothub"); } else { Serial.println("The device client has been disconnected"); } }
Deklaruje to funkcję zwrotną, która będzie wywoływana, gdy status połączenia z IoT Hub ulegnie zmianie, np. po połączeniu lub rozłączeniu. Status jest wysyłany na port szeregowy.
-
Poniżej tego dodaj funkcję do połączenia z IoT Hub:
void connectIoTHub() { IoTHub_Init(); _device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol); if (_device_ll_handle == NULL) { Serial.println("Failure creating Iothub device. Hint: Check your connection string."); return; } IoTHubDeviceClient_LL_SetConnectionStatusCallback(_device_ll_handle, connectionStatusCallback, NULL); }
Kod ten inicjalizuje bibliotekę IoT Hub, a następnie tworzy połączenie przy użyciu ciągu połączenia zdefiniowanego w pliku nagłówkowym
config.h
. Połączenie to opiera się na MQTT. Jeśli połączenie się nie powiedzie, informacja o tym zostanie wysłana na port szeregowy - jeśli zobaczysz to w wyjściu, sprawdź ciąg połączenia. Na końcu ustawiana jest funkcja zwrotna statusu połączenia. -
Wywołaj tę funkcję w funkcji
setup
poniżej wywołaniainitTime
:connectIoTHub();
-
Podobnie jak w przypadku klienta MQTT, ten kod działa na jednym wątku, więc potrzebuje czasu na przetwarzanie wiadomości wysyłanych przez hub i do huba. Dodaj następujący kod na początku funkcji
loop
, aby to zrobić:IoTHubDeviceClient_LL_DoWork(_device_ll_handle);
-
Zbuduj i wgraj ten kod. Zobaczysz połączenie w monitorze szeregowym:
Connecting to WiFi.. Connected! Fetched NTP epoch time is: 1619983687 Sending telemetry {"soil_moisture":391} The device client is connected to iothub
W wyjściu zobaczysz pobieranie czasu NTP, a następnie łączenie klienta urządzenia. Połączenie może zająć kilka sekund, więc możesz zobaczyć wilgotność gleby w wyjściu, podczas gdy urządzenie się łączy.
💁 Możesz przekonwertować czas UNIX z NTP na bardziej czytelną wersję, korzystając z witryny internetowej, takiej jak unixtimestamp.com
Wysyłanie telemetrii
Teraz, gdy Twoje urządzenie jest podłączone, możesz wysyłać dane telemetryczne do IoT Hub zamiast do brokera MQTT.
Zadanie - wysyłanie telemetrii
-
Dodaj następującą funkcję powyżej funkcji
setup
:void sendTelemetry(const char *telemetry) { IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(telemetry); IoTHubDeviceClient_LL_SendEventAsync(_device_ll_handle, message_handle, NULL, NULL); IoTHubMessage_Destroy(message_handle); }
Kod ten tworzy wiadomość IoT Hub z ciągu przekazanego jako parametr, wysyła ją do huba, a następnie czyści obiekt wiadomości.
-
Wywołaj ten kod w funkcji
loop
, zaraz po linii, w której telemetria jest wysyłana na port szeregowy:sendTelemetry(telemetry.c_str());
Obsługa poleceń
Twoje urządzenie musi obsługiwać polecenie z kodu serwera do sterowania przekaźnikiem. Jest ono wysyłane jako żądanie metody bezpośredniej.
Zadanie - obsługa żądania metody bezpośredniej
-
Dodaj następujący kod przed funkcją
connectIoTHub
:int directMethodCallback(const char *method_name, const unsigned char *payload, size_t size, unsigned char **response, size_t *response_size, void *userContextCallback) { Serial.printf("Direct method received %s\r\n", method_name); if (strcmp(method_name, "relay_on") == 0) { digitalWrite(PIN_WIRE_SCL, HIGH); } else if (strcmp(method_name, "relay_off") == 0) { digitalWrite(PIN_WIRE_SCL, LOW); } }
Kod ten definiuje metodę zwrotną, którą biblioteka IoT Hub może wywołać, gdy otrzyma żądanie metody bezpośredniej. Metoda, która jest żądana, jest przesyłana w parametrze
method_name
. Funkcja ta wypisuje wywołaną metodę na port szeregowy, a następnie włącza lub wyłącza przekaźnik w zależności od nazwy metody.💁 Można to również zaimplementować w ramach jednej metody bezpośredniej, przekazując pożądany stan przekaźnika w ładunku, który można przesłać z żądaniem metody i który jest dostępny w parametrze
payload
. -
Dodaj następujący kod na końcu funkcji
directMethodCallback
:char resultBuff[16]; sprintf(resultBuff, "{\"Result\":\"\"}"); *response_size = strlen(resultBuff); *response = (unsigned char *)malloc(*response_size); memcpy(*response, resultBuff, *response_size); return IOTHUB_CLIENT_OK;
Żądania metod bezpośrednich wymagają odpowiedzi, która składa się z dwóch części - odpowiedzi w formie tekstu oraz kodu zwrotnego. Kod ten utworzy wynik w postaci następującego dokumentu JSON:
{ "Result": "" }
Następnie jest on kopiowany do parametru
response
, a rozmiar tej odpowiedzi jest ustawiany w parametrzeresponse_size
. Kod ten zwracaIOTHUB_CLIENT_OK
, aby pokazać, że metoda została obsłużona poprawnie. -
Podłącz funkcję zwrotną, dodając następujący kod na końcu funkcji
connectIoTHub
:IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL);
-
Funkcja
loop
wywoła funkcjęIoTHubDeviceClient_LL_DoWork
, aby przetwarzać zdarzenia wysyłane przez IoT Hub. Funkcja ta jest wywoływana co 10 sekund z powodudelay
, co oznacza, że metody bezpośrednie są przetwarzane tylko co 10 sekund. Aby to usprawnić, 10-sekundowe opóźnienie można zaimplementować jako wiele krótszych opóźnień, wywołującIoTHubDeviceClient_LL_DoWork
za każdym razem. Aby to zrobić, dodaj następujący kod powyżej funkcjiloop
:void work_delay(int delay_time) { int current = 0; do { IoTHubDeviceClient_LL_DoWork(_device_ll_handle); delay(100); current += 100; } while (current < delay_time); }
Kod ten będzie wielokrotnie wywoływał
IoTHubDeviceClient_LL_DoWork
i opóźniał o 100 ms za każdym razem. Będzie to robił tyle razy, ile potrzeba, aby opóźnić o czas podany w parametrzedelay_time
. Oznacza to, że urządzenie czeka maksymalnie 100 ms na przetworzenie żądań metod bezpośrednich. -
W funkcji
loop
usuń wywołanieIoTHubDeviceClient_LL_DoWork
i zastąp wywołaniedelay(10000)
następującym kodem, aby wywołać nową funkcję:work_delay(10000);
💁 Kod ten znajdziesz w folderze code/wio-terminal.
😀 Twój program czujnika wilgotności gleby jest podłączony do IoT Hub!
Zastrzeżenie:
Ten dokument został przetłumaczony za pomocą usługi tłumaczenia AI Co-op Translator. Chociaż dokładamy wszelkich starań, aby tłumaczenie było precyzyjne, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w jego języku źródłowym powinien być uznawany za autorytatywne źródło. W przypadku informacji o kluczowym znaczeniu zaleca się skorzystanie z profesjonalnego tłumaczenia przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.