#AzureIoT – Using a Raspberry Pi Grove Sensor in an ☁ Azure IoT Edge Module (3/N)

Buy Me A Coffee

Hi !

In my previous post I shared a Python script that reads value from a Grove sensor and send the values as device telemetry to Azure IoT Hub. In today’s post I’ll share some insights and lessons learned on how to pack this as an Azure IoT Module.

In the references section I shared a Microsoft Docs tutorial on how to create a Azure IoT Module. That article cover the core steps to create the module using Visual Studio Code. I’ll focus on the main changes that we need to perform to make the module work.

Module Code

The module code is similar to the previous posts. The main difference will be around how the code and threads are managed. Based on the Azure IoT Python template, I’m using asyncio to work with threads. Another change is that, in this scenario, the interval timer will be defined as an environmental variable in the module definition.

# Copyright (c) Bruno Capuano. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
# Using the Python Device SDK for IoT Hub:
# https://github.com/Azure/azure-iot-sdk-python
# Azure IoT dependencies installed with >> pip install azure-iot-device
# read temp and humi from DHT11 Sensor
# read values are triggered device message with the information.
import time
import os
import sys
import asyncio
import seeed_dht
from six.moves import input
import threading
from azure.iot.device.aio import IoTHubModuleClient
from azure.iot.device import IoTHubDeviceClient, Message
from AzureIoTLogger import AzureIoTLogger
from AzureIoTEnvironment import AzureIoTEnvironment
trigger_enabled = False
refresh_interval = 5
MSG_TXT = '{{"temperature": {temperature},"humidity": {humidity}}}'
sensor = None
log = None
def twin_patch_handler(patch):
AzureIoTLogger.Log("the data in the desired properties patch was: {}".format(patch))
async def main():
global refresh_interval, sensor, module_client, log
if not sys.version >= "3.5.3":
raise Exception( "The sample requires python 3.5.3+. Current version of Python: %s" % sys.version )
# The client object is used to interact with your Azure IoT hub.
module_client = IoTHubModuleClient.create_from_edge_environment()
AzureIoTLogger.Log("IoT Hub Client for Python OK" )
# read environmental vars
trigger_enabled = AzureIoTEnvironment.GetEnvVarBool('ReportValues')
refresh_interval = AzureIoTEnvironment.GetEnvVarInt('Interval')
# connect the client.
await module_client.connect()
AzureIoTLogger.Log("Azure IoT client connected OK" )
# set the twin patch handler on the client
module_client.on_twin_desired_properties_patch_received = twin_patch_handler
# init sensor
sensor = seeed_dht.DHT("11", 12)
AzureIoTLogger.Log("Temp/Humi sensor OK" )
while True:
humi, temp = sensor.read()
if not humi is None:
AzureIoTLogger.Log('DHT{0}, humidity {1:.1f}%, temperature {2:.1f}*'.format(sensor.dht_type, humi, temp))
AzureIoTLogger.Log('DHT{0}, humidity & temperature: {1}'.format(sensor.dht_type, temp))
if (trigger_enabled == True):
msg_txt_formatted = MSG_TXT.format(temperature=temp, humidity=humi)
message = Message(msg_txt_formatted)
message.custom_properties["temperature"] = temp
message.custom_properties["humidity"] = humi
await module_client.send_message(message)
except Exception as e:
AzureIoTLogger.Log("stdin_listener exception.", e)
# Cancel listening
# Finally, disconnect
await module_client.disconnect()
except Exception as e:
AzureIoTLogger.Log ( "Unexpected error %s " % e )
if __name__ == "__main__":

Docker Permission to grant low level hardware access

Our module will interact with GPIO, so when we define and create the module for our device, in the Container Create Options, we need to add the following lines:

  "HostConfig": {
    "Privileged": true

Enabling access to the i2c and also running the module with elevated privileges:

Install Grove dependencies on the Docker Image

This one took me some try & test & fail & learn until I figure it out. So, we need to add this dependencies in the requirements.txt file:


However, when I build the image remotely in my device I got the following error

    Running setup.py install for RPi.GPIO: started
    Running setup.py install for RPi.GPIO: finished with status 'error'
    ERROR: Command errored out with exit status 1:
     command: /usr/local/bin/python -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-zxn0f5l4/rpi-gpio_20705074d6cc4e37b7794843814bcb85/setup.py'"'"'; __file__='"'"'/tmp/pip-install-zxn0f5l4/rpi-gpio_20705074d6cc4e37b7794843814bcb85/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-4mka4lwh/install-record.txt --single-version-externally-managed --compile --install-headers /usr/local/include/python3.7m/RPi.GPIO
         cwd: /tmp/pip-install-zxn0f5l4/rpi-gpio_20705074d6cc4e37b7794843814bcb85/
    Complete output (16 lines):
    running install
    running build
    running build_py
    creating build
    creating build/lib.linux-armv7l-3.7
    creating build/lib.linux-armv7l-3.7/RPi
    copying RPi/__init__.py -> build/lib.linux-armv7l-3.7/RPi
    creating build/lib.linux-armv7l-3.7/RPi/GPIO
    copying RPi/GPIO/__init__.py -> build/lib.linux-armv7l-3.7/RPi/GPIO
    running build_ext
    building 'RPi._GPIO' extension
    creating build/temp.linux-armv7l-3.7
    creating build/temp.linux-armv7l-3.7/source
    gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/usr/local/include/python3.7m -c source/py_gpio.c -o build/temp.linux-armv7l-3.7/source/py_gpio.o
    unable to execute 'gcc': No such file or directory
    error: command 'gcc' failed with exit status 1
ERROR: Command errored out with exit status 1: /usr/local/bin/python -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-zxn0f5l4/rpi-gpio_20705074d6cc4e37b7794843814bcb85/setup.py'"'"'; __file__='"'"'/tmp/pip-install-zxn0f5l4/rpi-gpio_20705074d6cc4e37b7794843814bcb85/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-4mka4lwh/install-record.txt --single-version-externally-managed --compile --install-headers /usr/local/include/python3.7m/RPi.GPIO Check the logs for full command output.
The command '/bin/sh -c pip install -r requirements.txt' returned a non-zero code: 1

As I mentioned, this took me some time until I figure out the right dependencies, and at the end I added 2 changes to my docker to make it work. I updated the [Dockerfile.arm32v7] and end it with these content

FROM arm32v7/python:3.7-slim-buster


# custom installation for RPI Grove dependencies defined in requirements.txt
RUN apt-get update 
RUN apt-get install -y gcc

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY . .

CMD [ "python3", "-u", "./main.py" ]

Full module source is available here https://github.com/elbruno/Blog/tree/main/20210308%20AzureIoT%20Module%20Rpi%20Grove%20Temp/ebGroveTemperatureDht11

In tomorrow post, I’ll share the final steps to have the module up and running with Azure IoT.


Happy coding!


El Bruno

¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.