Azure IoT Edge: využívání Edge modulů na příkladu Azure Function a Stream Analytics

Podívejme se dnes jak můžeme s využitím Visual Studio Code a Azure vytvořit a poslat do IoT Edge zařízení kód s Azure Function, konfiguraci i proudové zpracování dat s Azure Stream Analytics modulem pro IoT Edge.

Napojení IoT Edge zařízení

Dnes nám bude stačit nejjednodušší zprovoznění IoT Edge. Zkoušel jsem to s Ubuntu na Linux amd64 zařízení, ale i s Raspberry na armv7 s Rasbianem. Pro tento článek jsem použil Ubuntu verzi. Postupujte podle návodu v dokumentaci: https://docs.microsoft.com/en-us/azure/iot-edge/quickstart-linux

Někdy příště se na proces chci podívat trochu detailněji. V zásadě jde o IoT Edge daemona, který zajišťuje bezpečnost a integritu systému (včetně možnosti napojení na HSM čipy, využití trusted execution prostředí typu SGX na Intelu nebo TrustZone na ARMu, napojení na secure boot a tak podobně). Ten následně nahodí Docker a v něm základní systémové moduly, tedy IoT agenta zajišťujícího komunikaci s Azure a místní IoT Hub, který slouží pro lokální sběr a routing zpráv stejně tak jako pro příjem zpráv z jiných zařízení (gateway režim). Ale o tom všem někdy jindy.

Kde vzít moduly

Moduly jsou Docker kontejnery, které v sobě nejčastěji mají SDK, které jim umožňuje napojit se na IoT Edge platformu (například Hub v zařízení, který jim umožňuje přijímat a odesílat zprávy do dalších modulů nebo do Azure, číst si konfiguraci z Device Twin apod.). Můžete si vytvořit svoje vlastní (to uděláme s konfiguračním modulem), využít programátorského rámce Azure Functions (i to si vyzkoušíme), vytvořit modul se Stream Analytics nebo si sáhnout pro AI modul vyexportovaný třeba z Azure Machine Learning.

Do budoucna se dá očekávat, že se začnou objevovat hotové moduly aplikačních partnerů. V Azure portálu je na to vytvořena kategorie (jedním z modulů je SQL v Linux kontejneru).

Naše custom moduly budeme umisťovat do vlastního kontejnerového repozitáře. Nasaďte si tedy Azure Container Registry, budeme ho potřebovat.

První demo modul se simulátorem senzoru

Zařízení mám připojeno do Azure a pro vytváření modulů použiji extension do Visual Studio Code.

Když zmáčkneme CTRL + SHIFT + P a v paletě napíšeme edge objeví se nám řada užitečných příkazů.

Začneme vytvářet obsah svého zařízení tím, že zvolíme New IoT Edge Solution.

Odpovím na název adresáře se svou solution a vyberu si první modul. Pro začátek budu chtít custom modul psaný v C#.

Dáme mu název configModule.

Dále zadáme cestu k Azure Container Registry a název, pod kterým bude obraz vyzvedáván.

Pokud se vše podařilo a máte na počítači instlaci .NET Core vytvoří se vám skeleton celé solution včetně jednoho vlastního modulu. Ten zatím nijak měnit nebudeme, to přijde v další kapitolce. My teď budeme chtít tohle prostudovat a poslat do IoT Edge zařízení.

Zásadní je pro nás deployment šablona. Všimněte si, že obsahuje cestu a login do Azure Container Registry (heslo je ale uloženo v env souboru, který je v .gitignore seznamu) a definici systémových modulů, které nejsou nic jiného, než Docker kontejnery. Kromě systémových tam najdeme dva další moduly. Jeden je simulátor senzoru (ten se nám bude hodit) a tím druhým je náš nový modul configModule, který zatím nic nedělá.

Dole pak najdeme routing:

"routes": {
          "configModuleToIoTHub": "FROM /messages/modules/configModule/outputs/* INTO $upstream",
          "sensorToconfigModule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/configModule/inputs/input1\")"
        },

Zprávy ze simulovaného senzoru posíláme do configModulu a výstup configModulu jde do IoT Hubu v Azure. To se mi teď nehodí, protože s configModulem nemám v plánu zprácovávat zprávy, to uvidíme až později. Změním tedy routing takhle – nechám zprávy posílat do configModulu (byť je to teď celkem zbytečné, ale za chvilku se mi ten záznam bude hodit pro úpravu) a ještě napřímo do upstream, což je Azure IoT Hub.

"routes": {
  "sensorToIoTHub": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO $upstream",
  "sensorToconfigModule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/configModule/inputs/input1\")"
},

Provedeme tedy build a push configModule. Tento příkaz vezme vytvořený Dockerfile a protože mám na svém Windows PC nainstalovaný Docker for Windows s podporou Linux kontejnerů, provede se build, vytvoření kontejnerového obrazu a jeho odeslání do ACR (pokud nebudete přihlášeni, zadejte v terminálu Visual Studio Code příkaz az acr login).

Abychom mohli řešení nasadit musíme udělat jeho build, což v zásadě vezme náš soubor deployment.template.json a vytvoří z něj plný deployment soubor, který už obsahuje konkrétní údaje jako jsou verze kontejnerového obrazu nebo hesla pro login do registru (proto je adresář /config také v .gitignore).

Protože dnes jen testujeme, stačí nám deployment poslat do jednoho konkrétního zařízení.

Připojíme se do samotného zařízení a tam najdeme naše moduly krásně běžící.

tomas@edgedevice:~$ sudo iotedge list
NAME             STATUS           DESCRIPTION      CONFIG
configModule     running          Up a minute      iotedgemojemoduly.azurecr.io/configmodule:0.0.1-amd64
edgeHub          running          Up 2 minutes     mcr.microsoft.com/azureiotedge-hub:1.0
tempSensor       running          Up 2 minutes     mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0
edgeAgent        running          Up 37 minutes    mcr.microsoft.com/azureiotedge-agent:1.0

Jde v podstatě o stejný příkaz, jako přímo z dockeru:

tomas@edgedevice:~$ sudo docker ps
CONTAINER ID        IMAGE                                                             COMMAND                   CREATED              STATUS              PORTS
                                                      NAMES
432c7785d42e        iotedgemojemoduly.azurecr.io/configmodule:0.0.1-amd64             "dotnet configModule…"    About a minute ago   Up About a minute
                                                      configModule
99d2499d37a1        mcr.microsoft.com/azureiotedge-hub:1.0                            "/bin/sh -c 'echo \"$…"   2 minutes ago        Up About a minute   0.0.0.0:443->443/tcp, 0.0.0.0:5671->5671/tcp, 0.0.0.0:8883->8883/tcp   edgeHub
bc469d62444c        mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0   "/bin/sh -c 'echo \"$…"   2 minutes ago        Up 2 minutes
                                                      tempSensor
9241629762a5        mcr.microsoft.com/azureiotedge-agent:1.0                          "/bin/sh -c 'echo \"$…"   37 minutes ago       Up 37 minutes
                                                      edgeAgent

Totéž uvidíme v IoT Hub extension pro Visual Studio Code.

A také v Azure portálu.

Co píše tempSensor modul do logů? To si mohu prohlédnout přímo v zařízení. Jsou to klasické Docker logy a dostanu se k nim jak přes docker logs tak iotedge logs.

tomas@edgedevice:~$ sudo iotedge logs tempSensor --tail 5
        08/26/2018 18:39:02> Sending message: 42, Body: [{"machine":{"temperature":40.50488028768676,"pressure":3.2220749694833017},"ambient":{"temperature":20.682128805286311,"humidity":24},"timeCreated":"2018-08-26T18:39:02.4643474Z"}]
        08/26/2018 18:39:07> Sending message: 43, Body: [{"machine":{"temperature":41.52165692754631,"pressure":3.3379102828850225},"ambient":{"temperature":20.612556701578459,"humidity":24},"timeCreated":"2018-08-26T18:39:07.4844849Z"}]
        08/26/2018 18:39:12> Sending message: 44, Body: [{"machine":{"temperature":41.535087533777137,"pressure":3.3394403519492939},"ambient":{"temperature":20.5304835983694,"humidity":26},"timeCreated":"2018-08-26T18:39:12.5069744Z"}]
        08/26/2018 18:39:17> Sending message: 45, Body: [{"machine":{"temperature":42.222475937554819,"pressure":3.4177504232657387},"ambient":{"temperature":21.32000797373243,"humidity":26},"timeCreated":"2018-08-26T18:39:17.529278Z"}]
        08/26/2018 18:39:22> Sending message: 46, Body: [{"machine":{"temperature":42.941102311453349,"pressure":3.4996192506719006},"ambient":{"temperature":20.838227400248044,"humidity":24},"timeCreated":"2018-08-26T18:39:22.5516129Z"}]

A přichází mi něco do IoT Hubu v Azure? Využiji IoT Hub extension pro Visual Studio Code a začnu monitorovat Device-to-Cloud zprávy.

Konfigurační modul čtoucí Device Twin

configModule nám nebude pracovat se zprávama, ale ukážeme si na něm konfigurační možnosti přes Twin. Každý device stejně jako každý nasazený modul má své dvojče v cloudu, ve kterém je jeho desired state konfigurace. Pokud se vaše zařízení odpojí a vy mezitím změníte konfiguraci v cloudu, pozná IoT SDK po připojení diskrepanci a vaše zařízení na to může reagovat. Jinak řečeno cloud se stává místem centrální konfigurace vyšich zařízení. Můžete si tam ukládat metainformace o zařízení (lokalita apod.), nastavení snímání (flagy které hodnoty sbírat, rate odečtů, míru agregace), různé limity, jednotky a cokoli dalšího vás napadne v rámci velikostního limitu.

Náš modul si při startu načte desired konfiguraci ze svého dvojčete.

Twin twin = await ioTHubModuleClient.GetTwinAsync().ConfigureAwait(false);

Kromě toho si zaregistruje Task, který se spustí pokaždé, když Azure IoT Hub aktivně informuje zařízení o tom, že v jeho dvojčeti došlo ke změně.

await ioTHubModuleClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertyChanged, null).ConfigureAwait(false);

Celý kód si prohlédněte na mém GitHubu: https://github.com/tkubica12/iot-edge-demo/tree/master/ubuntuSolution/modules/configModule

Pojďme provést build, nový kontejnerový obraz a nový deployment. Protože se jedná o novou verzi modulu, potřebujeme jej uložit pod jiným označením v kontejnerovém registru (jméno necháme, ale změníme tag) a to reflektovat v deployment.json.

Půjdeme tedy do module.json v adresáři modulu a povýšíme číslo verze.

Teď provedeme Build and Push Edge Solution a následně deployment do zařízení. V něm si potom všimněte jiného tagu Docker image.

tomas@edgedevice:~$ sudo iotedge list
NAME             STATUS           DESCRIPTION      CONFIG
configModule     running          Up 2 seconds     iotedgemojemoduly.azurecr.io/configmodule:0.0.2-amd64
edgeHub          running          Up 21 minutes    mcr.microsoft.com/azureiotedge-hub:1.0
tempSensor       running          Up 21 minutes    mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0
edgeAgent        running          Up an hour       mcr.microsoft.com/azureiotedge-agent:1.0

Podívejme se na log z configModule.

tomas@edgedevice:~$ sudo iotedge logs configModule
IoT Hub module client initialized.
Initial twin value received:
{"deviceId":null,"etag":null,"version":null,"properties":{"desired":{"$version":1},"reported":{"$version":1}}}

Možná tam uvidíte i další zprávy (protože do modulu posíláme zprávy ze senzoru, což hned v další části změníme). Můžeme teď buď v Azure portálu nebo přes Visual Studio Code jít do modulu configModule a do jeho bratříčka přidat nějakou konfiguraci.

Náš configModule o změně dostane zprávu.

Desired property changed:
{"mojekonfigurace":"mojehodnota","$version":2}

A kdyby byl náhodou zrovna odpojen, nevadí. Dozví se to při nejbližší možné příležitosti, protože údaje jsou verzované.

Převod jednotek nebo filtrování s Azure Function

Senzor nám dává tlak v barech, ale dejme tomu, že já ho potřebuji v atmosférách. Jak lokálně přímo v IoT Edge zařízení něco takového udělat, aniž bych musel složitě programovat a udržel jednoduchost, čistotu a modulárnost? Na nasazení kódu je ideální Azure Function. Je to samozřejmě opět Docker kontejner, ale díky Azure Function runtime v něm nemusím řešit napojení na IoT SDK a podobné věci, soustředím se pouze na svůj kód. Při příjmu zprávy bude moje funkce zavolána a na výstupu vrátí zpracovanou zprávu. Jednoduché a účinné.

Vytvořme tedy další modul.

Tentokrát chceme Azure Function.

Podívejte se ná kód run.csx v GitHubu: https://github.com/tkubica12/iot-edge-demo/blob/master/ubuntuSolution/modules/transformModule/EdgeHubTrigger-Csharp/run.csx

V zásadě jednoduše přijímám zprávu, deserializuji jí a s hodnou tlaku provádím přepočet z barů na atmosféry. Následně to zase smotnuju zpět do zprávy a ze své funkce vracím (a průběžně si pro demo účely loguji). To je všechno.

Před nasazením ale potřebuji změnit routování zpráv. Potřebuji to udělat tak, že zprávy z tempSensor půjdou do transformModule a odtud do cloudu (Azure IoT Hub). To změním v deployment.template.json:

"routes": {
  "sensorTotransformModule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/transformModule/inputs/input1\")",
  "transformModuleToIoTHub": "FROM /messages/modules/transformModule/outputs/* INTO $upstream"
},

Provedeme build, push a nasazení do zařízení.

tomas@edgedevice:~$ sudo iotedge list
NAME             STATUS           DESCRIPTION      CONFIG
transformModule  running          Up 13 seconds    iotedgemojemoduly.azurecr.io/transformmodule:0.0.1-amd64
configModule     running          Up 23 minutes    iotedgemojemoduly.azurecr.io/configmodule:0.0.2-amd64
edgeHub          running          Up an hour       mcr.microsoft.com/azureiotedge-hub:1.0
tempSensor       running          Up an hour       mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0
edgeAgent        running          Up an hour       mcr.microsoft.com/azureiotedge-agent:1.0

Podívejme se do logů. Všimněte si, že transformModule dostává surová data ze senzoru a hodnotu pressure převádí z bar na atm (číslo je jiné):

tomas@edgedevice:~$ sudo iotedge logs transformModule --tail 10
      Executing 'Functions.EdgeHubTrigger-Csharp' (Reason='EdgeHub trigger fired at 2018-08-26T19:20:46.7389601+00:00', Id=d662b4da-e2b8-4c1e-943b-bc124237e516)
info: Function.EdgeHubTrigger-Csharp.User[0]
      => System.Collections.Generic.Dictionary`2[System.String,System.Object]
      Info: Received message: {"machine":{"temperature":101.68487164712734,"pressure":10.191947402837291},"ambient":{"temperature":20.790689416365087,"humidity":25},"timeCreated":"2018-08-26T19:20:46.7372456Z"}
info: Function.EdgeHubTrigger-Csharp.User[0]
      => System.Collections.Generic.Dictionary`2[System.String,System.Object]
      Info: Sending message {"machine":{"temperature":101.68487164712734,"pressure":10.058670025005934},"ambient":{"temperature":20.790689416365087,"humidity":25},"timeCreated":"2018-08-26T19:20:46.7372456Z"}

Právě jsme tedy použili Azure Function pro transformaci nebo filtrování dat přímo v IoT Edge zařízení.

Agregace na minutové průměry

Vteřinová granularita je možná důležitá pro detekci anomálií a vytvoření alertů, ale pro dlouhodobější hlubokou analýzu v cloudu mi možná stačí jen minutové průměry a dramaticky bych ušetřil přenosové pásmo. Když potřebuji v Azure zpracovávat proudová data v reálném čase, je tu výborná PaaS služba Azure Stream Analytics. Ta mi umožňuje psát agregační a detekční pravidla jazykem, který je velmi podobný klasickému SQL, akorát tentokrát nekouká do statické tabulky, ale do proudu dat. Umožňuje to provádět agregace nad plovoucím oknem, transformovat hodnoty (ano, převod z bar na atm jsem mohl klidně dělat v tom) a má i zabudované základní Machine Learning funkce (jako je detekce anomálie v číselné řadě) a umí odskakovat do pokročilých Azure ML. Je to vymakané, jednoduché a skvěle funkční. Kdybych tak něco takového mohl dělat přímo v IoT Edge …

No a já právě můžu. Nejprve jsem si zachytil pár zpráv (přes extension do Visual Studio Code) tak, jak mi teď přicházejí do IoT Hub (tedy z výstupu transformModule) a uložil si je do souboru. Následně jsem si vytvořil Stream Analytics v Azure, ale při zakládání jsem zvolil IoT Edge.

Ve vytvořeném Stream Analytics založím input a output, což budou názvy, které pak použijeme při routování zpráv v IoT Edge.

Můžeme se pustit do vytvoření query. Abychom si mohli vyzkoušet, jestli to dělá co potřebujeme, nahrajeme si vzorek dat do input.

Použiji následující query, které vezme data ze vstupu a spočítá průměry na minutová okna. Tím je krásně vidět síla Stream Analytics. Vytvářím dotaz podobně, jako kdybych montoval SQL dotaz do statické tabulky, ale ve skutečnosti se jedná o průtokový dotaz nad přicházejícími daty.

SELECT
    avg(machine.temperature) AS machine_temperature,
    avg(machine.pressure) AS machine_pressure,
    avg(ambient.temperature) AS ambient_temperature,
    avg(ambient.humidity) AS abmient_humidity
INTO
    [output]
FROM
    [input]
TIMESTAMP BY 
    timeCreated
GROUP BY 
    TumblingWindow( second , 60 )

Abychom vyzkoušeli, že query dělá co má, využijeme nahraných sample dat a stiskneme talačítko Test.

Vypadá to dobře, přidejme modul do našeho IoT Edge zařízení. Můžeme to udělat z Azure portálu, já ale využiji extension ve Visual Studio Code, ať to máme všechno pohromadě a můžeme deployment.json opakovatelně používat třeba na tisíce či miliony zařízení.

Do deployment template se mi přidá nový modul.

Také tam najdeme konfiguraci obsahující informace o mém Stream Analytics jobu.

Poslední co potřebujeme udělat je pozměnit routing informací, který chceme takhle:

tempSensor -> transformModule -> aggregateModule -> IoT Hub

"routes": {
  "sensorTotransformModule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/transformModule/inputs/input1\")",
  "transformModuleToaggregateModule": "FROM /messages/modules/transformModule/outputs/* INTO BrokeredEndpoint(\"/modules/aggregateModule/inputs/input\")",
  "aggregateModuleToIoTHub": "FROM /messages/modules/aggregateModule/outputs/output INTO $upstream"
},

Výborně. Poskládáme a pošleme deployment do zařízení. Moduly by měly být nahoře.

tomas@edgedevice:~$ sudo iotedge list
NAME             STATUS           DESCRIPTION      CONFIG
aggregateModule  running          Up 2 minutes     microsoft/azureiotedge-azure-stream-analytics:1.0.0-preview010
transformModule  running          Up 2 minutes     iotedgemojemoduly.azurecr.io/transformmodule:0.0.1-amd64
configModule     running          Up 2 minutes     iotedgemojemoduly.azurecr.io/configmodule:0.0.2-amd64
edgeHub          running          Up 2 minutes     mcr.microsoft.com/azureiotedge-hub:1.0
tempSensor       running          Up 12 seconds    mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0
edgeAgent        running          Up 2 minutes     mcr.microsoft.com/azureiotedge-agent:1.0

Podívejme se na logy ze Stream Analytics. Měli bychom vidět přijímané zprávy a každou minutu také agregovanou informaci.

tomas@edgedevice:~$ sudo iotedge logs aggregateModule
...
08/27/2018 03:59:36 - ASA module registered message endpoint : [input]
        08/27/2018 04:00:56> Received message: [{"machine":{"temperature":22.191332403310263,"pressure":1.1208698870321669},"ambient":{"temperature":21.181929299925422,"humidity":24},"timeCreated":"2018-08-27T04:00:56.2599801Z"}]
        08/27/2018 04:01:01> Received message: [{"machine":{"temperature":23.24579347774656,"pressure":1.239427475815305},"ambient":{"temperature":21.138817007485226,"humidity":25},"timeCreated":"2018-08-27T04:01:01.5126033Z"}]
        08/27/2018 04:01:01> Output generated: output, Content: {"machine_temperature":22.191332403310263,"machine_pressure":1.1208698870321669,"ambient_temperature":21.181929299925422,"abmient_humidity":24.0}

Pojďme si ověřit, že dostáváme do Azure pouze agregované informace po minutách.

Výborně! Děla to co potřebujeme.

 

Co se nám dnes podařilo? Ukázali jsme si jak vyvíjet a používat moduly distribuované do IoT Edge zařízení pro lokální zpracování. Použili jsme demo modul se simulací senzorů, který by v praxi obsahoval kód pro odečty z reálných senzorů (na to se podíváme někdy příště – například pro Raspberry, kde modulu spustíme v privilegovaném režimu, aby měl přístup k hardware) a také příklad konfiguračního modulu, který je schopen získávat konfigurační data z Twin v Azure. Následně jsme se rozhodli data před odesláním do Azure lokálně zpracovávat. Vytvořili jsme Azure Function modul, díky kterému můžeme jednoduše filtrovat nebo provádět konverzi zpráv (v našem případě převod z barů na atmosféry) a také použili Stream Analytics pro agregaci údajů po minutě před jejich odesláním do Azure IoT Hub. Zprávy mezi moduly jsme provázali tak, že ze senzoru jdeme do Azure Function pro transformaci, odtud do agregačního modulu a následně do cloudu.

IoT Edge umožňuje distribuovat logiku do krajních zařízení velmi efektivním a perfektně škálovatelným způsobem. Vyzkoušejte si to.

Podobné příspěvky: