Сборка и развертывание модуля для IoT Edge. Часть 3.

Прежде чем начинать разрабатывать свои модули для IoT Edge откомпилируем и развернем модуль датчика температуры и влажности разработанный Microsoft. С ним проводили эксперименты ранее.

В Интернете есть два варианта модуля:

  1. https://github.com/Azure/iotedge/tree/master/edge-modules/SimulatedTemperatureSensor
  2. https://github.com/Azure/iot-edge-v1/tree/master/v2/samples/azureiotedge-simulated-temperature-sensor

Код первого примера на момент написания статьи менялся 21 день назад, т.е. более свежий. Код находится в папке относящейся к коду сервиса IoT Edge.

Второй код более сложный, но читабельнее, хотя изменения по нему проводились более 9 месяцев назад. На него есть ссылка с Microsoft Market:

https://azuremarketplace.microsoft.com/en-us/marketplace/apps/microsoft.edge-simulated-temperature-sensor-ga?tab=Overview

С него есть ссылка на сайт Microsoft по сборке модуля https://docs.microsoft.com/en-us/azure/iot-edge/how-to-develop-csharp-module.

На странице https://docs.microsoft.com/en-us/azure/iot-edge/tutorial-csharp-module  приведена информация по разработке модуля. Куски кода там из второго примера. Так что в статье используем эти исходники.

В части 4 повествования я вынес находки, которые будут полезны при отладке модуля. В идеале, не понадобится.

Termo sensor IoT Edge module

Прежде чем разрабатывать что-то своё, модифицируем Microsoft модуль Termo Sensor.

С созданием контейнера Azure все понятно. На облачном ресурсе выделяется виртуальное хранилище в котром лежат образы модулей, пригодные для развертывании на IoT Edge устройствах. Образ модуля в любом месте где есть Интернет можно накатить на устройство из облачного репозитория.

Есть два варианта собрать из исходников с GitHub рабочий модуль:

  1. Добавить в исходники с Git недостающий файл .env и поправить deployment.template.json.
  2. Создать новый проект IoT модуля и скопировать в него исходники.

Я разберу второй подход, поскольку структура папок исходников отличается от варианта, формируемого при создании проекта «с чистого листа». Например, нет файла modules.json. Вместо него папка Docker. Нет файла deployment.template.json и т.п. В общем, в таком случае быстрее скопировать *.cs файлы.

Первый способ будет продемонстрирован в следующей статье при сборке другого модуля.

  1. В Visual Studio Code создаем с чистого листа IoT Edge модуль, используя последовательность из документации и мои рекомендации ниже и из tips & tricks.
  2. На Azure portal, путь для создания своего контейнера: Create a resource > Containers > Azure Container Registry.
  3. Имя для контейнера общее для всех участников, соотвественно, нужно придумать что-то достаточно уникальное.  Например, я выбрал название WarlibRegistry, поэтому путь к серверу: warlibregistry.azurecr.io.
  4. Ключи доступа к созданному container repository можно найти на Azure portal: All resources -> [WarlibRegistry] -> Access Keys. Если Admin users в Enable, то отобразятся имя пользователя и пароль.
  5. При выполнении команды Visual Studio Code для создания нового модуля View -> Command Palette.. -> New IoT Edge Solution:
    • Запрос на выбора папки — это корневая папка на локальном диске в котором будет создан solution.
    • Solution name — в выбранной папке будет создана подпапка с заданным именем решения. Например, iot_edge_engine_simulator. Допускаются только символы нижнего подчеркивания.
    • Module name — имя модуля. Например, iot_edge_engine_simulator. Допускаются только символы нижнего подчеркивания.
    • Docker image repository — репозиторий Azure/Docker. Например, warlibregistry.azurecr.io/iot-edge-engine-simulator.
  6. Скачиваем с Git Hub https://github.com/Azure/iot-edge-v1 исходники модуля. Команда:
    git clone https://github.com/Azure/iot-edge-v1.git
  7. Копируем все *.cs файлы из папки …\iot-edge-v1\v2\samples\azureiotedge-simulated-temperature-sensor\*.cs в папку вновь созданного модуля.
  8. В Visual Studio Code отобразятся все добавленные *.cs файлы.
  9. Правим (копируем нижеприведенный вариант) deployment.template.json в созданном модуле, убрав лишнее. В результате получится:
    {
      "modulesContent": {
        "$edgeAgent": {
          "properties.desired": {
            "schemaVersion": "1.0",
            "runtime": {
              "type": "docker",
              "settings": {
                "minDockerVersion": "v1.25",
                "loggingOptions": "",
                "registryCredentials": {
                  "warlibregistry": {
                    "username": "$CONTAINER_REGISTRY_USERNAME_warlibregistry",
                    "password": "$CONTAINER_REGISTRY_PASSWORD_warlibregistry",
                    "address": "warlibregistry.azurecr.io"
                  }
                }
              }
            },
            "systemModules": {
              "edgeAgent": {
                "type": "docker",
                "settings": {
                  "image": "mcr.microsoft.com/azureiotedge-agent:1.0",
                  "createOptions": "{}"
                }
              },
              "edgeHub": {
                "type": "docker",
                "status": "running",
                "restartPolicy": "always",
                "settings": {
                  "image": "mcr.microsoft.com/azureiotedge-hub:1.0",
                  "createOptions": ""
                }
              }
            },
            "modules": {
              "iot_edge_engine_simulator": {
                "version": "1.0",
                "type": "docker",
                "status": "running",
                "restartPolicy": "always",
                "settings": {
                  "image": "${MODULES.iot_edge_engine_simulator.amd64}",
                  "createOptions": "{}"
                }
              }
            }
          }
        },
        "$edgeHub": {
          "properties.desired": {
            "schemaVersion": "1.0",
            "routes": {
               "route": "FROM /* INTO $upstream"
            },
            "storeAndForwardConfiguration": {
              "timeToLiveSecs": 7200
            }
          }
        }
      }
    }
  10. В командной строке или терминале Visual Studio Code авторизуем docker для доступа к Azure Container Registry:
    docker login -u <ACR username> -p <ACR password> <ACR login server>
  11. В Program.cs нужно внести ряд изменений, чтобы сборка произошла корректно:
    1. В *.csproj указан <LangVersion>7.1</LangVersion>. В файле проект с «чистого листа» этого указания нет, поэтому при сборке возникнет ошибка метода Main.
    2. Для компиляции нужно либо добавить версию,
        <PropertyGroup>
          <OutputType>Exe</OutputType>
          <TargetFramework>netcoreapp2.1</TargetFramework>
          <LangVersion>7.3</LangVersion>
        </PropertyGroup>

      либо изменить исходник Program.cs следующим образом:

      //Andrey Fedorov Lang version 7.1 needed if
      //Andrey Fedorov static async Task Main(string[] args)
      static void Main(string[] args)
      {
                  // The Edge runtime gives us the connection string we need -- it is injected as an environment variable
                  var connectionString = Environment.GetEnvironmentVariable("EdgeHubConnectionString");
      
                  // Cert verification is not yet fully functional when using Windows OS for the container
                  var bypassCertVerification = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
                  bypassCertVerification = true; //Andrey Fedorov new line
                  if (!bypassCertVerification) InstallCert();
                  //await Init(connectionString, bypassCertVerification); //Andrey Fedorov Lang version 7.1 needed
                  Init(connectionString, bypassCertVerification).Wait(); //Andrey Fedorov change
                  // Wait until the app unloads or is cancelled
                  var cts = new CancellationTokenSource();
                  AssemblyLoadContext.Default.Unloading += (ctx) =&gt; cts.Cancel();
                  Console.CancelKeyPress += (sender, cpe) =&gt; cts.Cancel();
                  //await WhenCancelled(cts.Token); //Andrey Fedorov Lang version 7.1 needed
      
                  // Wait until the app unloads or is cancelled
                  WhenCancelled(cts.Token).Wait(); //Andrey Fedorov My addition
      }
    3. Пока в исходниках убрал проверку сертификата, указав bypassCertVerification = true.
    4. Заменил DeviceClient на ModuleClient. В исходниках Microsoft используется старая сборка 1.6 preview DeviceClient. Возможно, тогда не было класса ModuleClient. Для разработки модулей используется ModuleClient, а для разработки устройств с прямым доступом к IoT Hub — DeviceClient.
    5. Добавил проверку на Connection string, поскольку при запуске исходника от Microsoft передается пустая строка. В «модуле с чистого листа» Connection string не используется.
                  // Open a connection to the Edge runtime
                  //var ioTHubModuleClient = DeviceClient.CreateFromConnectionString(connectionString, settings); //Andrey Fedorov
                  ModuleClient ioTHubModuleClient = null; //Andrey Fedorov
                  if (string.IsNullOrEmpty(connectionString)) //Andrey Fedorov
                  {
                      ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings); //Andrey Fedorov
                  }
                  else //Andrey Fedorov
                      ioTHubModuleClient = ModuleClient.CreateFromConnectionString(connectionString, settings);
  12. В Visual Studio Code встаем на deployment.template.json, нажимаем правую клавишу мыши и выбираем Build and Push IoT Edge solution.  Запустится процесс сборки модуля, упаковки в контейнер Docker и отправки его в репозиторий Azure.
  13. Если сборка прошла удачно, то в Azure portal в Containers repository появится образ с именем указанным в deployment.json.
  14. В IoT Hub:
    1. Создаем новый IoT Edge device (All resources -> [Ваш IoT Hub] -> IoT Edge -> Add an IoT Edge Device -> Задаем имя устройства, больше ничего менять не нужно.
    2. Выбираем созданное устройство.
    3. Добавляем модуль, выбрав пункт Set Module.
    4. ОБЯЗАТЕЛЬНО заполняем параметры «Container Registry Settings» (см. лайфаки).
    5. В разделе «Deployment modules» ->   Add -> IoT Edge module:
      1. В «Name» задаем имя модуля. Например, у меня engineSimulator.
      2. В «Image URI» указываем путь к образу в репозтитории. Например, у меня warlibregistry.azurecr.io/iot-edge-engine-simulator:0.0.1-amd64.
    6. Другие настройки можно оставить без изменений.
    7. Жмем кнопку Next для заполнения роутинга. В deployment.json прописан простой путь
      "route": "FROM /* INTO $upstream"
    8.   Затем снова Next и Submit.
    9. Модуль для устройства создан. Можно развертывать его на устройстве.
  15. На ПК, выполняющем функции IoT Edge устройства:
    1.  В PowerShell запущенном под Администратором выполняем команду:
      . {Invoke-WebRequest -useb aka.ms/iotedge-win} | Invoke-Expression; `
      Install-SecurityDaemon -Manual -ContainerOs Linux
    2. Установка и развертывание IoT Edge runtime может занять несколько минут в зависимости от скорости Интернет.
    3. После успешного развертывания ждем немного, поскольку модули появляются не сразу. Проверяем, что все модули «приехали» командой:
      iotedge list

      Если собранный модуль в списке, то уже хорошо.

    4. Запускаем команду
      iotedge logs [engineSimulator] -f

      чтобы посмотреть какие данные выдает в консоль собранный модуль.

    5. Если в консоли появляются строки с данными по температуре и влажности, развертывание прошло успешно, все работает.
    6. Если в консоль валятся сообщения об ошибках — используем команды из раздела tips & tricks для выявления причин.
  16. Если в All resources ->  [Iot Hub] -> IoT Edge -> [Your IoT device] -> [Your IoT module] -> Module Identity Twin вписать в секцию properties -> desired:
    ...
    "properties": {
        "desired": {
          "SendData": true,
          "SendInterval": 7,
    ...

    то на «родном» модуле из контейнера Microsoft на это не будет никакой реакции. В модуле, который откомпилирован из исходников с github после перезапуска iotedge появятся строчки:

    Updating desired properties {
      "SendData": true,
      "SendInterval": 7,
      "$version": 2
    }
    Value for SendData = True
    Value for SendInterval = 7

    Это говорит о том, что код исходника полноценнее реализует функционал IoT Hub/Edge, чем готовый модуль Microsoft.

  17. Если в All resources ->  [Iot Hub] -> IoT Edge -> [Your IoT device] -> [Your IoT module] ->  Direct method -> Methiod name -> Ввести «reset» без кавычек -> Нажать «Invoke method», то в логе на IoT Edge device появляется запись:
    Received reset command via direct method invocation
    Resetting temperature sensor...

    После чего записи начинается с единицы. Т.е. функционал вызова direct метода с клиента отрабатывает нормально.

Рабочие исходники.

Spread the love
Запись опубликована в рубрике IT рецепты, IT решения для бизнеса с метками , . Добавьте в закладки постоянную ссылку.