Jak przekonwertować istniejące urządzenie na urządzenie IoT Plug and Play
W tym artykule opisano kroki, które należy wykonać, aby przekonwertować istniejące urządzenie na urządzenie IoT Plug and Play. W tym artykule opisano sposób tworzenia modelu wymaganego przez każde urządzenie IoT Plug and Play oraz niezbędne zmiany kodu umożliwiające działanie urządzenia jako urządzenie IoT Plug and Play.
W przypadku przykładów kodu w tym artykule przedstawiono kod języka C, który używa biblioteki MQTT do nawiązywania połączenia z centrum IoT. Zmiany opisane w tym artykule można zastosować do urządzeń zaimplementowanych w innych językach i zestawach SDK.
Aby przekonwertować istniejące urządzenie na urządzenie IoT Plug and Play:
- Przejrzyj kod urządzenia, aby poznać dane telemetryczne, właściwości i polecenia, które implementuje.
- Utwórz model opisujący dane telemetryczne, właściwości i polecenia implementujące urządzenie.
- Zmodyfikuj kod urządzenia, aby ogłosić identyfikator modelu podczas nawiązywania połączenia z usługą.
Przeglądanie kodu urządzenia
Przed utworzeniem modelu dla urządzenia należy zrozumieć istniejące możliwości urządzenia:
- Dane telemetryczne, które urządzenie wysyła regularnie.
- Właściwości tylko do odczytu i zapisu, które urządzenie synchronizuje z usługą.
- Polecenia wywoływane z usługi, na którą odpowiada urządzenie.
Zapoznaj się na przykład z poniższymi fragmentami kodu urządzenia, które implementują różne możliwości urządzeń.
Poniższy fragment kodu przedstawia urządzenie wysyłające dane telemetryczne temperatury:
#define TOPIC "devices/" DEVICEID "/messages/events/"
// ...
void Thermostat_SendCurrentTemperature()
{
char msg[] = "{\"temperature\":25.6}";
int msgId = rand();
int rc = mosquitto_publish(mosq, &msgId, TOPIC, sizeof(msg) - 1, msg, 1, true);
if (rc != MOSQ_ERR_SUCCESS)
{
printf("Error: %s\r\n", mosquitto_strerror(rc));
}
}
Nazwa pola telemetrii to temperature
i jego typ jest zmiennoprzecinkowy lub podwójny. Definicja modelu dla tego typu telemetrii wygląda następująco: JSON. Aby dowiedzieć się, jak poznać tryb, zobacz Projektowanie modelu poniżej:
{
"@type": [
"Telemetry"
],
"name": "temperature",
"displayName": "Temperature",
"description": "Temperature in degrees Celsius.",
"schema": "double"
}
Poniższy fragment kodu przedstawia wartość właściwości raportowania przez urządzenie:
#define DEVICETWIN_MESSAGE_PATCH "$iothub/twin/PATCH/properties/reported/?$rid=patch_temp"
static void SendMaxTemperatureSinceReboot()
{
char msg[] = "{\"maxTempSinceLastReboot\": 42.500}";
int msgId = rand();
int rc = mosquitto_publish(mosq, &msgId, DEVICETWIN_MESSAGE_PATCH, sizeof(msg) - 1, msg, 1, true);
if (rc != MOSQ_ERR_SUCCESS)
{
printf("Error: %s\r\n", mosquitto_strerror(rc));
}
}
Nazwa właściwości to maxTempSinceLastReboot
i jej typ jest zmiennoprzecinkowy lub podwójny. Ta właściwość jest zgłaszana przez urządzenie. Urządzenie nigdy nie otrzymuje aktualizacji tej wartości z usługi. Definicja modelu dla tej właściwości wygląda następująco: JSON. Aby dowiedzieć się, jak poznać tryb, zobacz Projektowanie modelu poniżej:
{
"@type": [
"Property"
],
"name": "maxTempSinceLastReboot",
"schema": "double",
"displayName": "Max temperature since last reboot.",
"description": "Returns the max temperature since last device reboot."
}
Poniższy fragment kodu przedstawia urządzenie, które odpowiada na komunikaty z usługi:
void message_callback(struct mosquitto* mosq, void* obj, const struct mosquitto_message* message)
{
printf("Message received: %s payload: %s \r\n", message->topic, (char*)message->payload);
if (strncmp(message->topic, "$iothub/methods/POST/getMaxMinReport/?$rid=1",37) == 0)
{
char* pch;
char* context;
int msgId = 0;
pch = strtok_s((char*)message->topic, "=",&context);
while (pch != NULL)
{
pch = strtok_s(NULL, "=", &context);
if (pch != NULL) {
char * pEnd;
msgId = strtol(pch,&pEnd,16 );
}
}
char topic[64];
sprintf_s(topic, "$iothub/methods/res/200/?$rid=%d", msgId);
char msg[] = "{\"maxTemp\":83.51,\"minTemp\":77.68}";
int rc = mosquitto_publish(mosq, &msgId, topic, sizeof(msg) - 1, msg, 1, true);
if (rc != MOSQ_ERR_SUCCESS)
{
printf("Error: %s\r\n", mosquitto_strerror(rc));
}
delete pch;
}
if (strncmp(message->topic, "$iothub/twin/PATCH/properties/desired/?$version=1", 38) == 0)
{
char* pch;
char* context;
int version = 0;
pch = strtok_s((char*)message->topic, "=", &context);
while (pch != NULL)
{
pch = strtok_s(NULL, "=", &context);
if (pch != NULL) {
char* pEnd;
version = strtol(pch, &pEnd, 10);
}
}
// To do: Parse payload and extract target value
char msg[128];
int value = 46;
sprintf_s(msg, "{\"targetTemperature\":{\"value\":%d,\"ac\":200,\"av\":%d,\"ad\":\"success\"}}", value, version);
int rc = mosquitto_publish(mosq, &version, DEVICETWIN_MESSAGE_PATCH, strlen(msg), msg, 1, true);
if (rc != MOSQ_ERR_SUCCESS)
{
printf("Error: %s\r\n", mosquitto_strerror(rc));
}
delete pch;
}
}
Temat odbiera żądanie polecenia wywoływanego getMaxMinReport
z usługi. To $iothub/methods/POST/getMaxMinReport/
żądanie może zawierać ładunek z parametrami polecenia. Urządzenie wysyła odpowiedź z ładunkiem zawierającym maxTemp
wartości i minTemp
.
Temat $iothub/twin/PATCH/properties/desired/
odbiera aktualizacje właściwości z usługi. W tym przykładzie przyjęto założenie, że aktualizacja właściwości dotyczy targetTemperature
właściwości . Odpowiada za pomocą potwierdzenia, które wygląda następująco: {\"targetTemperature\":{\"value\":46,\"ac\":200,\"av\":12,\"ad\":\"success\"}}
.
Podsumowując, przykład implementuje następujące możliwości:
Nazwisko | Typ możliwości | Szczegóły |
---|---|---|
temperature | Telemetria | Załóżmy, że typ danych jest podwójny |
maxTempSinceLastReboot | Właściwości | Załóżmy, że typ danych jest podwójny |
targetTemperature | Właściwość zapisywalna | Typ danych to liczba całkowita |
getMaxMinReport | Polecenie | Zwraca kod JSON z polami maxTemp typu i minTemp |
Projektowanie modelu
Każde urządzenie IoT Plug and Play ma model opisujący funkcje i możliwości urządzenia. Model używa języka Digital Twin Definition Language (DTDL) do opisania możliwości urządzenia.
Aby uzyskać prosty model, który mapuje istniejące możliwości urządzenia, użyj elementów Telemetria, Właściwość i Command DTDL.
Model DTDL dla przykładu opisanego w poprzedniej sekcji wygląda podobnie do następującego przykładu:
{
"@context": "dtmi:dtdl:context;2",
"@id": "dtmi:com:example:ConvertSample;1",
"@type": "Interface",
"displayName": "Simple device",
"description": "Example that shows model for simple device converted to act as an IoT Plug and Play device.",
"contents": [
{
"@type": [
"Telemetry",
"Temperature"
],
"name": "temperature",
"displayName": "Temperature",
"description": "Temperature in degrees Celsius.",
"schema": "double",
"unit": "degreeCelsius"
},
{
"@type": [
"Property",
"Temperature"
],
"name": "targetTemperature",
"schema": "double",
"displayName": "Target Temperature",
"description": "Allows to remotely specify the desired target temperature.",
"unit": "degreeCelsius",
"writable": true
},
{
"@type": [
"Property",
"Temperature"
],
"name": "maxTempSinceLastReboot",
"schema": "double",
"unit": "degreeCelsius",
"displayName": "Max temperature since last reboot.",
"description": "Returns the max temperature since last device reboot."
},
{
"@type": "Command",
"name": "getMaxMinReport",
"displayName": "Get Max-Min report.",
"description": "This command returns the max and min temperature.",
"request": {
},
"response": {
"name": "tempReport",
"displayName": "Temperature Report",
"schema": {
"@type": "Object",
"fields": [
{
"name": "maxTemp",
"displayName": "Max temperature",
"schema": "double"
},
{
"name": "minTemp",
"displayName": "Min temperature",
"schema": "double"
}
]
}
}
}
]
}
W tym modelu:
- Wartości
name
ischema
są mapowe na dane wysyłane i odbierane przez urządzenie. - Wszystkie możliwości są pogrupowane w jednym interfejsie.
- Pola
@type
identyfikują typy DTDL, takie jak Właściwość i Polecenie. - Pola, takie jak
unit
,displayName
idescription
zawierają dodatkowe informacje dotyczące usługi do użycia. Na przykład usługa IoT Central używa tych wartości podczas wyświetlania danych na pulpitach nawigacyjnych urządzeń.
Aby dowiedzieć się więcej, zobacz IoT Plug and Play conventions and IoT Plug and Play modeling guide (Konwencje IoT Plug and Play) i IoT Plug and Play modeling guide (Przewodnik modelowania technologii IoT Plug and Play).
Aktualizacja kodu
Jeśli urządzenie już współpracuje z usługą IoT Hub lub usługą IoT Central, nie musisz wprowadzać żadnych zmian w implementacji jego danych telemetrycznych, właściwości i możliwości poleceń. Aby urządzenie było zgodne z konwencjami IoT Plug and Play, zmodyfikuj sposób, w jaki urządzenie łączy się z usługą, aby ogłosił identyfikator utworzonego modelu. Usługa może następnie użyć modelu, aby zrozumieć możliwości urządzenia. Na przykład usługa IoT Central może użyć identyfikatora modelu, aby automatycznie pobrać model z repozytorium i wygenerować szablon urządzenia dla urządzenia.
Urządzenia IoT łączą się z usługą IoT za pośrednictwem usługi Device Provisioning Service (DPS) lub bezpośrednio z parametry połączenia.
Jeśli urządzenie używa usługi DPS do nawiązania połączenia, dołącz identyfikator modelu do ładunku wysyłanego podczas rejestrowania urządzenia. Na przykładowy model pokazany wcześniej ładunek wygląda następująco:
{
"modelId" : "dtmi:com:example:ConvertSample;1"
}
Aby dowiedzieć się więcej, zobacz Rejestracja środowiska uruchomieniowego — rejestrowanie urządzenia.
Jeśli urządzenie używa usługi DPS do nawiązywania połączenia lub łączenia się bezpośrednio z parametry połączenia, dołącz identyfikator modelu, gdy kod łączy się z usługą IoT Hub. Na przykład:
#define USERNAME IOTHUBNAME ".azure-devices.net/" DEVICEID "/?api-version=2020-09-30&model-id=dtmi:com:example:ConvertSample;1"
// ...
mosquitto_username_pw_set(mosq, USERNAME, PWD);
// ...
rc = mosquitto_connect(mosq, HOST, PORT, 10);
Następne kroki
Teraz, gdy wiesz, jak przekonwertować istniejące urządzenie na urządzenie IoT Plug and Play, sugerowanym następnym krokiem jest przeczytanie przewodnika modelowania technologii IoT Plug and Play.