From f94b94296943881267802a95edd6a3b178ad3a00 Mon Sep 17 00:00:00 2001 From: ChenCheng368 Date: Wed, 27 Jul 2022 23:28:16 +0800 Subject: [PATCH 1/2] add sample of module direct method in python sdk --- .../.gitignore | 3 + .../README.md | 9 ++ .../deploymentManual.json | 85 +++++++++++++ .../modules/ConfigModule/.gitignore | 104 +++++++++++++++ .../modules/ConfigModule/.pep8 | 2 + .../modules/ConfigModule/Dockerfile | 21 ++++ .../modules/ConfigModule/Dockerfile.amd64 | 21 ++++ .../modules/ConfigModule/main.py | 118 ++++++++++++++++++ .../modules/ConfigModule/module.json | 17 +++ .../modules/ConfigModule/requirements.txt | 4 + 10 files changed, 384 insertions(+) create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/.gitignore create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/README.md create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/deploymentManual.json create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.gitignore create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.pep8 create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile.amd64 create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/main.py create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/module.json create mode 100644 samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/requirements.txt diff --git a/samples/azure-iot-direct-method-sample-python-sdk/.gitignore b/samples/azure-iot-direct-method-sample-python-sdk/.gitignore new file mode 100644 index 00000000000..b7baf43b323 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/.gitignore @@ -0,0 +1,3 @@ +config/ +.env +/tests/__pycache__/*.pyc diff --git a/samples/azure-iot-direct-method-sample-python-sdk/README.md b/samples/azure-iot-direct-method-sample-python-sdk/README.md new file mode 100644 index 00000000000..55223a3f954 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/README.md @@ -0,0 +1,9 @@ +# Azure IoT Edge sample for configuration + +This is a sample of using Azure IoT Direct Method to update configuration parameter in the IoT Edge module. + +[Azure IoT Python SDK v2](https://github.com/Azure/azure-iot-sdk-python) is in use. + + + + diff --git a/samples/azure-iot-direct-method-sample-python-sdk/deploymentManual.json b/samples/azure-iot-direct-method-sample-python-sdk/deploymentManual.json new file mode 100644 index 00000000000..9c082339118 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/deploymentManual.json @@ -0,0 +1,85 @@ +{ + "modulesContent": { + "$edgeAgent": { + "properties.desired": { + "modules": { + "tempSensor": { + "settings": { + "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", + "createOptions": "{}" + }, + "type": "docker", + "version": "1.0", + "status": "running", + "restartPolicy": "always" + }, + + "ConfigModule": { + "settings": { + "image": "", + "createOptions": "{}" + }, + "type": "docker", + "version": "1.0", + "status": "running", + "restartPolicy": "always" + } + }, + "runtime": { + "settings": { + "minDockerVersion": "v1.25", + "registryCredentials": { + "my_acr": { + "address": "", + "password": "", + "username": "" + } + } + }, + "type": "docker" + }, + "schemaVersion": "1.1", + "systemModules": { + "edgeAgent": { + "settings": { + "image": "mcr.microsoft.com/azureiotedge-agent:1.2", + "createOptions": "{}" + }, + "type": "docker" + }, + "edgeHub": { + "settings": { + "image": "mcr.microsoft.com/azureiotedge-hub:1.2", + "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}" + }, + "type": "docker", + "status": "running", + "restartPolicy": "always" + } + } + } + }, + "$edgeHub": { + "properties.desired": { + "routes": { + "ConfigModuleToIoTHub": "FROM /messages/modules/ConfigModule/outputs/* INTO $upstream", + "sensorToConfigModule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/ConfigModule/inputs/input1\")" + }, + "schemaVersion": "1.1", + "storeAndForwardConfiguration": { + "timeToLiveSecs": 7200 + } + } + }, + "tempSensor": { + "properties.desired": { + "SendData": true, + "SendInterval": 1, + "StopAfterCount": 2000 + } + }, + "ConfigModule": { + "properties.desired": {} + } + } +} diff --git a/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.gitignore b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.gitignore new file mode 100644 index 00000000000..894a44cc066 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.gitignore @@ -0,0 +1,104 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.pep8 b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.pep8 new file mode 100644 index 00000000000..8d72125019a --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/.pep8 @@ -0,0 +1,2 @@ +[pycodestyle] +max_line_length = 150 \ No newline at end of file diff --git a/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile new file mode 100644 index 00000000000..18890e6ea40 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 +FROM python:3.7-slim-buster + +WORKDIR /app + +RUN apt-get update && \ + apt-get install -y --no-install-recommends libcurl4-openssl-dev libboost-python-dev libpython3-dev && \ + rm -rf /var/lib/apt/lists/* +RUN pip3 install --upgrade pip +RUN pip3 install setuptools +RUN pip3 install ptvsd==4.1.3 +COPY requirements.txt ./ +RUN pip3 install -r requirements.txt + +COPY . . + +RUN useradd -ms /bin/bash moduleuser +USER moduleuser + +CMD [ "python3", "-u", "./main.py" ] + diff --git a/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile.amd64 b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile.amd64 new file mode 100644 index 00000000000..18890e6ea40 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/Dockerfile.amd64 @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 +FROM python:3.7-slim-buster + +WORKDIR /app + +RUN apt-get update && \ + apt-get install -y --no-install-recommends libcurl4-openssl-dev libboost-python-dev libpython3-dev && \ + rm -rf /var/lib/apt/lists/* +RUN pip3 install --upgrade pip +RUN pip3 install setuptools +RUN pip3 install ptvsd==4.1.3 +COPY requirements.txt ./ +RUN pip3 install -r requirements.txt + +COPY . . + +RUN useradd -ms /bin/bash moduleuser +USER moduleuser + +CMD [ "python3", "-u", "./main.py" ] + diff --git a/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/main.py b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/main.py new file mode 100644 index 00000000000..a3c5561f088 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/main.py @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for +# full license information. +# Migrated with IoTHub Python SDK v2 + +import asyncio +import time +import sys +import json +from threading import Lock +import logging +# from azure.core.credentials import AzureKeyCredential +from azure.iot.device.aio import IoTHubModuleClient +from azure.iot.device import Message, MethodResponse +from datetime import datetime + +logging.basicConfig(level=logging.DEBUG) + +mutex = Lock() +# global counters +TIME_INTERVAL = 10 # in sec +CONFIG_TELE = "configmsg" + + +class HubManager(object): + def __init__(self): + self.module_client = IoTHubModuleClient.create_from_edge_environment() + + async def start(self): + await self.module_client.connect() + # set the received data handlers on the client + self.module_client.on_message_received = self.message_handler + self.module_client.on_method_request_received = self.method_handler + + async def preprocess(self, message): + # print("in preprocess function!") + message_str = message.data + if not message_str: + return None + message_obj = json.loads(message_str) + print("module receives a msg with temp: {}".format(message_obj["machine"]["temperature"])) + input_data = message_obj["machine"]["temperature"] + time_stamp = message_obj["timeCreated"] # UTC iso format. "1972-01-01T00:00:00Z" is UTC ISO 8601 + + return [time_stamp, input_data] + + async def config_telemetry(self, message, **kwargs): + # print("in config_telemetry") + + for key, value in kwargs.items(): + if key == "time_interval":# default time_interval value = 10 + print("config_telemetry time_interval ", value) + print("message ts ", message[0], type(message[0])) + + for iter_sec in range(0, 60, value): + print("iter_sec", iter_sec) + print('message[0][17:19].strip()', message[0][17:19].strip(), type(message[0][17:19].strip())) + if int(message[0][17:19].strip()) == iter_sec: + message_str = json.dumps(message) + result = Message(message_str) + print("telemetry to be send to iothub: ", message_str) + await self.forward_event_to_output(result, "output2") + + + async def message_handler(self, message): + print("in message_handler") + print ("message_handler TIME_INTERVAL", TIME_INTERVAL) + if message.input_name == "input1": + mutex.acquire() + try: + sensor_input = await self.preprocess(message) + if sensor_input!= None: + print("config freq and sending...") + await self.config_telemetry(sensor_input, time_interval= TIME_INTERVAL) + except Exception as e: + print("Error when config telemetry: %s" % e) + finally: + mutex.release() + + else: + print("message received on unknown input") + + + # Direct Method receiver + async def method_handler(self, method_request): + print("Received method [%s]" % (method_request.name)) + print("config_tele_messsage: ",method_request.payload) + global TIME_INTERVAL + TIME_INTERVAL = method_request.payload + print ("method_handler TIME_INTERVAL", TIME_INTERVAL) + print("Sent method response to module output via event [%s]" % CONFIG_TELE) + method_response = MethodResponse.create_from_method_request( + method_request, 200, "{ \"Response\": \"This is the response from the device. \" }" + ) + await self.module_client.send_method_response(method_response) + + async def forward_event_to_output(self, event, moduleOutputName): + await self.module_client.send_message_to_output(event, moduleOutputName) + +async def main(): + try: + print("\nPython %s\n" % sys.version) + print("Prototype for config IoT Edge module") + + hub_manager = HubManager() + await hub_manager.start() + print("The sample is now waiting for messages and will indefinitely. Press Ctrl-C to exit. ") + + while True: + time.sleep(1) + + except KeyboardInterrupt: + await hub_manager.module_client.shutdown() + print("Configuration sample stopped") + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/module.json b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/module.json new file mode 100644 index 00000000000..06d121da403 --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/module.json @@ -0,0 +1,17 @@ +{ + "$schema-version": "0.0.1", + "description": "", + "image": { + "repository": "${ACR_ADDRESS}/configmodule", + "tag": { + "version": "0.0.${BUILD_BUILDID}", + "platforms": { + "amd64": "./Dockerfile.amd64", + "amd64.debug": "./Dockerfile.amd64.debug", + "arm32v7": "./Dockerfile.arm32v7" + } + }, + "buildOptions": [] + }, + "language": "python" +} \ No newline at end of file diff --git a/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/requirements.txt b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/requirements.txt new file mode 100644 index 00000000000..9fdbf99d28e --- /dev/null +++ b/samples/azure-iot-direct-method-sample-python-sdk/modules/ConfigModule/requirements.txt @@ -0,0 +1,4 @@ +pathlib==1.0.1 +ruamel-yaml==0.17.16 +azure-iot-device==2.7.1 + From e876fd75219a9a26a1a9320b40a2195dabb358dd Mon Sep 17 00:00:00 2001 From: Chen Cheng <45490176+ChenCheng368@users.noreply.github.com> Date: Wed, 27 Jul 2022 23:30:51 +0800 Subject: [PATCH 2/2] update readme --- samples/azure-iot-direct-method-sample-python-sdk/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/samples/azure-iot-direct-method-sample-python-sdk/README.md b/samples/azure-iot-direct-method-sample-python-sdk/README.md index 55223a3f954..92b10e9b8ee 100644 --- a/samples/azure-iot-direct-method-sample-python-sdk/README.md +++ b/samples/azure-iot-direct-method-sample-python-sdk/README.md @@ -2,6 +2,10 @@ This is a sample of using Azure IoT Direct Method to update configuration parameter in the IoT Edge module. +Temperature Simulator IoT Edge module sends simulated telemetry to the ConfigModule IoT Edge module at default frequency. + +ConfigModule Module Direct Method can be invoked from IoT Hub. Once a new frequecy value is passed to edge via Module Direct Method, ConfigModule Direct Method handler will receive the new frequency value and update Temperature Simulator's telemetry sending frequency accordingly. + [Azure IoT Python SDK v2](https://github.com/Azure/azure-iot-sdk-python) is in use.