Управление устройствами по Modbus с IoT Hub. Module Identity Twin. Урок 10.

В предыдущей статье рассматривается передача телеметрический информации в Microsoft IoT Hub. Рассмотрим, каким образом облако Microsoft  IoT Hub после анализа телеметрии может повлиять на работу устройства. В качестве оборудования используем управлемый по Modbus RTU блок из 4-х реле, рассмотренный в предыдущем уроке.

В описании модуля Modbus есть фраза: «Currently IoT Edge only supports send messages into one module from another module, direct C2D messages doesn’t work.» Получается, что на данный момент, нет способа напрямую отправить из облака сообщение на вход модуля. Это странно, поскольку данные анализируются в т.ч. для принятия автоматических решений и внесения изменений в настройки оборудования для корректировки.

Каким образом IoT Hub может доносить информацию до модуля IoT Edge:

  1. Изменение настроек «Module Identity Twin» properties.desired.
  2. Вызов прямого метода «Direct method».

Других опций IoT Hub как передать модулю данные для изменения настроек я не знаю. Мне удалось найти единственную статью на эту тему. К сожалению, в описании пропущено ряд деталей, поэтому дам свою интерпретацию, взяв большую часть кода за основу.

Для начала создадим код модуля «с чистого листа», как описано в статье.

Module Identity Twin

Рассмотрим вариант управления оборудованием изменением параметров properties.desired. В созданном с нуля модуле уже есть метод PipeMessage принимаюший на вход JSON и отправляющий в стандартный output.

Добавим метод onDesiredPropertiesUpdate для обработки изменений в properties.desired.

В коде отдельно вынесен метод BuildWriteCommand создающий команду на запись из JSON файла с данными и отправляющий его на стандартный  output.

    static async void BuildWriteCommand(string jsonMessage, ModuleClient moduleClient)
    {
        try
        {
            var bytes = System.Text.Encoding.UTF8.GetBytes(jsonMessage);
            var pipeMessage = new Message(bytes);
            pipeMessage.Properties.Add("command-type", "ModbusWrite");
            await PipeMessage(pipeMessage, moduleClient);
        }
        catch (Exception err)
        {
            Console.WriteLine("Error in BuildWriteCommand: " + err.Message);
        }
    }
    static async Task onDesiredPropertiesUpdate(TwinCollection desiredProperties, object userContext)
    {
        if (desiredProperties.Count == 0)
        {
            return;
        }
        try
        {
            Console.WriteLine("Desired property change:");
            Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));
            var moduleClient = userContext as ModuleClient;
            if (moduleClient == null)
            {
                throw new InvalidOperationException("UserContext doesn't contain expected values");
            }
            var reportedProperties = new TwinCollection();
            if (desiredProperties["hwId"] != null)
            {
                HwId = desiredProperties["hwId"];
                reportedProperties["hwId"] = HwId;
            }
            if (desiredProperties["uId"] != null)
            {
                UId = desiredProperties["uId"];
                reportedProperties["uId"] = UId;
            }
            if (desiredProperties["address"] != null)
            {
                Address = desiredProperties["address"];
                reportedProperties["address"] = Address;
            }
            if (desiredProperties["value"] != null)
            {
                Value = desiredProperties["value"];
                reportedProperties["value"] = Value;
            }
            var modbusMessageBody = new ModbusMessage
            {
                HwId = HwId,
                UId = UId,
                Address = Address,
                Value = Value,
            };
            var jsonMessage = JsonConvert.SerializeObject(modbusMessageBody);
            BuildWriteCommand(jsonMessage, moduleClient);
            if (reportedProperties.Count > 0)
            {
                await moduleClient.UpdateReportedPropertiesAsync(reportedProperties).ConfigureAwait(false);
            }
        }
        catch (AggregateException ex)
        {
            foreach (Exception exception in ex.InnerExceptions)
            {
                Console.WriteLine();
                Console.WriteLine("Error when receiving desired property: {0}", exception);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine();
            Console.WriteLine("Error when receiving desired property: {0}", ex.Message);
        }
    }
    public class ModbusMessage
    {
        public string HwId { get; set; }
        public string UId { get; set; }
        public string Address { get; set; }
        public string Value { get; set; }
    }
}

Добавим в Init() регистрацию CallBack функции для перехвата события о изменении properties.desired:

// Attach callback for Twin desired properties updates
await ioTHubModuleClient.SetDesiredPropertyUpdateCallbackAsync(onDesiredPropertiesUpdate, ioTHubModuleClient);
// Execute callback method for Twin desired properties updates
var twin = await ioTHubModuleClient.GetTwinAsync();
await onDesiredPropertiesUpdate(twin.Properties.Desired, ioTHubModuleClient);
// Start the client
await ioTHubModuleClient.OpenAsync();

Важный момент — функция deployment.template.json:

{
  "modulesContent": {
    "$edgeAgent": {
      "properties.desired": {
        "schemaVersion": "1.0",
        "runtime": {
          "type": "docker",
          "settings": {
            "minDockerVersion": "v1.25",
            "loggingOptions": "",
            "registryCredentials": {
              "docker": {
                "username": "$CONTAINER_REGISTRY_USERNAME_docker",
                "password": "$CONTAINER_REGISTRY_PASSWORD_docker",
                "address": "azureiotlabcontainerregistry683.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": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}], \"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
            }
          }
        },
        "modules": {
          "modbus": {
                "version": "1.0",
              "type": "docker",
              "status": "running",
              "restartPolicy": "always",
              "settings": {
                "image": "mcr.microsoft.com/azureiotedge/modbus:1.0",
                "createOptions": ""
              }
          },
          "modbuswriter": {
            "version": "1.0",
            "type": "docker",
            "status": "running",
            "restartPolicy": "always",
            "settings": {
              "image": "${MODULES.ModbusWriter.amd64}",
              "createOptions": "{}"
            }
          }
        }
      }
    },
    "$edgeHub": {
      "properties.desired": {
        "schemaVersion": "1.0",
        "routes": {
          "fromModbusWriterToModbus": "FROM /messages/modules/modbuswriter/outputs/* INTO BrokeredEndpoint(\"/modules/modbus/inputs/input1\")",
          "fromModbusToIotHub": "FROM /messages/modules/modbus/outputs/* INTO $upstream"
        },
        "storeAndForwardConfiguration": {
          "timeToLiveSecs": 7200
        }
      }
    }
  }
}

Нужно обратить пристальное внимание на маршрутизацию:

"routes": {
  "fromModbusWriterToModbus": "FROM /messages/modules/modbuswriter/outputs/* INTO BrokeredEndpoint(\"/modules/modbus/inputs/input1\")",
  "fromModbusToIotHub": "FROM /messages/modules/modbus/outputs/* INTO $upstream"
},

С выхода разработанного модуля modbuswriter данные передаются на вход «input1» модуля modbus. С выхода modbus они отправляются в облако IoT Hub. Вводить маршрут нужно аккуратно, не исключено, что названия модулей чувствительны к регистру.

Как было описано в предыдущих уроках создаем IoT Edge device и добавляем два модуля: разработанный modbuswriter и стандартный Microsoft Modbus модуль. При создании модуля Modbus я использую properties.desired для датчика температуры и влажности из предыдущего урока.  Датчик температуры и влажности удобно использовать, поскольку можно проверить как отправку (запись) данных из IoT Hub на устройство так и отправку данных с датчика температуры в IoT Hub.

"properties.desired": {
    "PublishInterval": "20000",
      "SlaveConfigs": {
        "TermoSensor": {
          "SlaveConnection": "192.168.26.110",
          "HwId": "TermoSensor-0a:01:01:01:01:01",
          "TcpPort": "8899",        
          "Operations": {
            "Op01": {
              "PollingInterval": "20000",
              "UnitId": "1",
              "StartAddress": "30002",
              "Count": "1",
              "DisplayName": "Temp"
            },
            "Op02": {
              "PollingInterval": "20000",
              "UnitId": "1",
              "StartAddress": "30003",
              "Count": "1",
              "DisplayName": "Humidity"
           }
        }
     }
   }
}

Для модуля modbuswriter в properties.desired прописываем тестовый JSON:

"properties": {
  "desired": {
    "hwId": "TermoSensor-0a:01:01:01:01:01",
    "uId": "1",
    "address": "00017",
    "value": "1"
  }
}

Маршрут вводим как указано выше. Не ошибаемся с названиями и заглавными/строчными буквами модулей, чтобы они точно соответствовали названиям в разделе IoT Edge device для выбранного устройства.

После развертывания модулей на IoT Edge устройстве запускаем команду «iotedge logs modbuswriter -f» и меняем параметры в properties.desired.Модуль modbuswriter принимает сообщения. Запускаем команду «iotedge logs modbus -f» и видим, что модуль modbus также получает на вход сообщения.

На скриншоте видно, что если ошибочно ввести имя устройства, то получим сообщение «target [DeviceName] not found!» Стоит исправить имя устройства и сообщение меняется на успешное: «Write device [DeviceName] address: xxxxx, value: x».

В следующем уроке будет рассмотрен вариант управления устройствами через «Direct Method».

Продолжение следует…

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *