RoadTest: IDT ZMOD4410 Indoor Air Quality Raspberry Pi HAT
Author: vlasov01
Creation date:
Evaluation Type: Development Boards & Tools
Did you receive all parts the manufacturer stated would be included in the package?: True
What other parts do you consider comparable to this product?:
What were the biggest problems encountered?: HAL for Raspberry Pi was not implemented.
Detailed Review:
I'm living in a suburb of Montreal, Canada, where we can have quite cold winters and hot summers. So it requires to spend a lot of money on heating and cooling to fill comfortable. One way to reduce costs and energy consumption is to exchange air with outside environment only when it is required. Otherwise concentration of CO2 and other VOC can have a negative consequences on health and productivity. So to achieve this equilibrium between energy cost and health/productivity we need to automate control of air exchange. And it requires accessible, reliable and affordable data on air quality.
A high level of CO2 and lover levels of oxygen can impair reaction, can cause sleepiness and other negative impacts. So my second use case is to add air quality sensor to alert driver in my project Driver State Monitor with OpenCV and BeagleBone AI . Porting my code from BB AI to RPi should be relatively straightforward.
And the last use case that comes to my mind is related to the main theme of 2020. It is very clear that a good ventilation is one of requirements to reduce spread of COVID-19 (and other airborne diseases). A high level of CO2 can be easily corelated with poor ventilation and high concentration of people. So to have a precise, simple and affordable way to generate an alert on poor ventilation in public places is a necessity, that I think will be in a high demand moving forward.
After I've read specs of Avnet's Renesas ZMOD4410 Indoor Air Quality HAT for the Raspberry Pi I've decided participate in the roadtest as it seems supported all my requirements. Specifically I was looking to control my air exchanger based on air quality. My plan was to integrate ZMOD4410 Air Quality Pi Hat with Home Assistant (HA), which is a very popular open home automation platform with thousands integrations, so my air exchanger can be controlled by HA automation based on ZMOD4410 sensor data.
I've started my research by reading a nice blog Monitor Indoor Air Quality with the IDT ZMOD4410 Pi HAT by zebular13 . Monica used a demo application to work with the device and provided a setup procedure for RPi.
I was able to reproduce her steps. But I've run it in a Docker container to eliminate impact on other RPi applications.
FROM balenalib/raspberrypi3-debian-python:latest WORKDIR /home/pi/zmod ENV GS_VERSION=7.0.1 ENV PYTHONPATH="${PYTHONPATH}:/usr/lib/python3/dist-packages" ENV DEBIAN_FRONTEND=noninteractive ENV DEBCONF_NOWARNINGS yes COPY requirements.txt ./ RUN apt-get update -yqq \ && apt-get upgrade -yqq \ && apt-get install -yqq --no-install-recommends \ apt-utils \ build-essential \ libffi-dev \ libpq-dev \ libssl-dev \ python-pandas \ python-pyqtgraph \ curl \ ffmpeg \ libsm6 \ libxext6 RUN curl -SL http://downloads.element14.com/downloads/zedboard/community/GasSensorEvaluation-$GS_VERSION.tgz \ | tar xz RUN apt-get install -y python-pyside RUN python3 -m pip install --no-cache-dir pigpio RUN apt-get install -y pigpio WORKDIR /home/pi/zmod/GasSensorEvaluation/bin ENV LOCALAPPDATA=../log SKLEARN_SITE_JOBLIB=1 DISPLAY=192.168.1.45:0.0 CMD ["sudo pigpiod"]
You need to remove DISPLAY variable if you are attaching your screen directly to RPi or change IP address of DISPLAY value if you are using X-Server.
I've used the following command to build and tag the image
docker build -t arm32v7/pigpio .
Once image was built and I've lunched it in a privileged mode, connected to the container shell and run the following commands:
docker run -it --privileged -u root -v /sys:/sys -v /dev/mem:/dev/mem -v logs:/home/pi/zmod/GasSensorEvaluation/log arm32v7/pigpio /bin/sh sudo pigpiod ./GasSensorEvaluation -v warn -s
Here is one of my observations over 24+ hours period.
Integrating Avnet's Renesas ZMOD4410 Indoor Air Quality HAT for the Raspberry Pi with Home Assistant using Mosquitto MQTT Broker and Docker Compose
My next step was integration with HA.There was several decision to make.
1. I've decided to use Mosquitto MQTT broker to pass data from PI HAT to HA even HA can support I2C directly due proprietary Renesas SDK and libraries. It was simpler for me to use Mosquitto than wrapping Renesas C SDK into Python library as required by HA. On other side HA supports MQTT natively. Another advantages of MQTT for me it is quite easy to debug it, and can be extended to publish zmod4410 sensor data from other devices, like Arduino.
2. I decided to use docker-compose to run HA, Mosquitto and my custom code, which sends sensor data to MQTT broker. docker-compose has a small overhead, but provides isolation of workloads, automatic failover, simpler maintenance of different components. So I can update my code and rebuild its container without stopping or impacting anything else.
As the base of my zmod bridge I've used a project shared by saicheong . I've did some modification in the code. I've asked Renesas support if I can share my code, which is using Renesas API. I think I can share it, but I want to get a confirmation that I'm not violating any terms defined in the license agreements https://www.renesas.com/us/en/document/msc/idt-software-license-terms-gas-sensor-software .
I've got permission to publish my code, which is using Renesas functions.
The code for the project is now located on Github
But the code to interact with MQTT broker is not using Renesas API. so I'm sharing it below.
zmod2mqtt.cpp
#include "zmodhat.h" #include <mosquitto.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> struct mosquitto *mosq = NULL; char *topic = NULL; void mqtt_setup() { char *host = (char *)"mqtt"; // char *host = (char*)"localhost"; // char *host = "broker.hivemq.com"; int port = 1883; int keepalive = 60; bool clean_session = true; topic = (char *)"office/sensor1"; mosquitto_lib_init(); mosq = mosquitto_new(NULL, clean_session, NULL); if (!mosq) { fprintf(stderr, "Error: Out of memory.\n"); exit(1); } if (mosquitto_connect(mosq, host, port, keepalive)) { fprintf(stderr, "Unable to connect.\n"); exit(1); } int loop = mosquitto_loop_start(mosq); if (loop != MOSQ_ERR_SUCCESS) { fprintf(stderr, "Unable to start loop: %i\n", loop); exit(1); } } int mqtt_send(char *msg) { return mosquitto_publish(mosq, NULL, topic, strlen(msg), msg, 0, 0); } int main(int argc, char *argv[]) { mqtt_setup(); i2c_openport(); if (zmodhat_init()) { fprintf(stderr, "ZMOD: INIT ERR"); } i2c_closeport(); char *buf = (char *)malloc(96); while (1) { read_zmod_hat(buf); //fprintf(stdout, "%s\n", buf); int snd = mqtt_send(buf); if (snd != 0) fprintf(stderr, "mqtt_send error=%i for payload %s\n", snd, buf); usleep(10000000); // 10 seconds } }
The build of the code requires Mosquitto development and Renesas library. The first line installs Mosquitto library.
sudo apt install libmosquitto-dev g++ -I/home/pi/zmod/dev/ -o zmod2mqtt zmod2mqtt.cpp zmodhat.cpp zmod4xxx.c lib_iaq_2nd_gen.a -lmosquitto
I've used balenalib/raspberrypi3-debian image as the base image. The image is optimized for use IoT devices including RPi.
Dockerfile
FROM balenalib/raspberrypi3-debian:latest-run WORKDIR /home/pi/zmod/zmod2mqtt RUN install_packages libmosquitto-dev COPY zmod2mqtt ./ CMD ["./zmod2mqtt"]
It takes a few minutes to build the image:
pi@raspberrypi:~/zmod/zmod2mqtt $ docker build -t zmod2mqtt . Sending build context to Docker daemon 2.707MB Step 1/5 : FROM balenalib/raspberrypi3-debian:latest-run ---> 1eb1be4b4572 Step 2/5 : WORKDIR /home/pi/zmod/zmod2mqtt ---> Using cache ---> 1e59abd9fa3f Step 3/5 : RUN install_packages libmosquitto-dev ---> Running in 58087d7d2ed7 Here are a few details about this Docker image (For more information please visit https://www.balena.io/docs/reference/base-images/base-images/): Architecture: ARM v7 OS: Debian Buster Variant: run variant Default variable(s): UDEV=off Extra features: - Easy way to install packages with `install_packages <package-name>` command - Run anywhere with cross-build feature (for ARM only) - Keep the container idling with `balena-idle` command - Show base image details with `balena-info` command Reading package lists... Building dependency tree... Reading state information... The following package was automatically installed and is no longer required: libidn11 Use 'apt autoremove' to remove it. The following additional packages will be installed: libmosquitto1 The following NEW packages will be installed: libmosquitto-dev libmosquitto1 0 upgraded, 2 newly installed, 0 to remove and 33 not upgraded. Need to get 111 kB of archives. After this operation, 237 kB of additional disk space will be used. Get:1 http://deb.debian.org/debian buster/main armhf libmosquitto1 armhf 1.5.7-1+deb10u1 [57.8 kB] Get:2 http://deb.debian.org/debian buster/main armhf libmosquitto-dev armhf 1.5.7-1+deb10u1 [52.7 kB] debconf: delaying package configuration, since apt-utils is not installed Fetched 111 kB in 0s (343 kB/s) Selecting previously unselected package libmosquitto1:armhf. (Reading database ... 10103 files and directories currently installed.) Preparing to unpack .../libmosquitto1_1.5.7-1+deb10u1_armhf.deb ... Unpacking libmosquitto1:armhf (1.5.7-1+deb10u1) ... Selecting previously unselected package libmosquitto-dev:armhf. Preparing to unpack .../libmosquitto-dev_1.5.7-1+deb10u1_armhf.deb ... Unpacking libmosquitto-dev:armhf (1.5.7-1+deb10u1) ... Setting up libmosquitto1:armhf (1.5.7-1+deb10u1) ... Setting up libmosquitto-dev:armhf (1.5.7-1+deb10u1) ... Processing triggers for libc-bin (2.28-10) ... Removing intermediate container 58087d7d2ed7 ---> bf6f3242f21d Step 4/5 : COPY zmod2mqtt ./ ---> 95ea3f3e973b Step 5/5 : CMD ["./zmod2mqtt"] ---> Running in b5aa0f5ad33f Removing intermediate container b5aa0f5ad33f ---> fea6fb459c86 Successfully built fea6fb459c86 Successfully tagged zmod2mqtt:latest
And to test it:
docker run -it --rm --name my-zmod2mqtt zmod2mqtt:latest
The docker-compose.yml file defines three services - zmod, mqtt and homeassistant. Each service requires some parameters, which defines its configuration, ports, networking, volumes dependencies.
docker-compose.yml
version: '3' services: homeassistant: container_name: home-assistant image: homeassistant/home-assistant:stable volumes: - /home/pi/homeassistant:/config environment: - TZ=America/New_York restart: on-failure network_mode: host mqtt: container_name: mqtt hostname: mqtt image: eclipse-mosquitto:latest ports: - "1883:1883" volumes: - /home/pi/mosquitto/config:/mqtt/config:ro - /home/pi/mosquitto/log:/mqtt/log - /home/pi/mosquitto/data/:/mqtt/data - /etc/localtime:/etc/localtime:ro environment: - TZ=America/New_York restart: on-failure zmod: container_name: zmod image: zmod2mqtt:latest devices: - "/dev/i2c-1:/dev/i2c-1" depends_on: - mqtt environment: - TZ=America/New_York restart: on-failure
I've used basic Mosquitto settings. You may need to extend it with extra settings, for example related to security.
/home/pi/mosquitto/config/mosquitto.conf
# Place your local configuration in /mqtt/config/conf.d/ pid_file /var/run/mosquitto.pid persistence true persistence_location /mqtt/data/ user mosquitto # Port to use for the default listener. port 1883 log_dest file /mqtt/log/mosquitto.log log_dest stdout include_dir /mqtt/config
HA configuration file defines sensors. ZMOD sensors are defined starting line 13.
/home/pi/homeassistant/configuration.yaml
homeassistant: # Name of the location where Home Assistant is running name: Home # metric for Metric, imperial for Imperial unit_system: metric # Pick yours from here: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones time_zone: America/Montreal # Customization file customize: !include customize.yaml # Sensors sensor: - platform: mqtt name: "Temperature" state_topic: "office/sensor1" unit_of_measurement: '°C' value_template: "{{ value_json.temperature }}" - platform: mqtt name: "Humidity" state_topic: "office/sensor1" unit_of_measurement: '%' value_template: "{{ value_json.humidity }}" - platform: mqtt name: "IAQ" state_topic: "office/sensor1" unit_of_measurement: 'UBA level' value_template: "{{ value_json.iaq }}" - platform: mqtt name: "eCO2" state_topic: "office/sensor1" unit_of_measurement: 'ppb' value_template: "{{ value_json.eco2 }}" - platform: mqtt name: "TVOC" state_topic: "office/sensor1" unit_of_measurement: 'ppb' value_template: "{{ value_json.tvoc }}" - platform: mqtt name: "Stabilization" state_topic: "office/sensor1" unit_of_measurement: '%' value_template: "{{ value_json.stabilization }}"
Starting docker-compose is very simple
docker-compose up -d
You may want to check state of containers
pi@raspberrypi:~ $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5b576c548fb9 07bb7ef43299 "/usr/bin/entry.sh .…" 52 minutes ago Up 51 minutes zmod 4aad875d6134 eclipse-mosquitto:latest "/docker-entrypoint.…" 42 hours ago Up 52 minutes 0.0.0.0:1883->1883/tcp mqtt 46bd48ece05a homeassistant/home-assistant:stable "/init" 43 hours ago Up 52 minutes home-assistant
And look into logs of a specific container like zmod :
docker logs zmod
To stop all three containers :
docker-compose stop
HA runs WEB UI by default on HTTP port 8123. Out of the box it provides overview of current values of all sensors.
And HA provides history of all sensors. This history is stored in SQLite database and can be extracted.
I've noticed that output of TVOC,eCO2 and IAQ sensors are quite different before and after restart of the HAT and a period of 11 minutes of stabilization.
It is not clear for me which readings represent actuals - before or after reset.
It may be the result of IAQ 2nd generation firmware (package from October 19, 2020), I hope to clarify it with Renesas support.
As it was suggested by support I've read the datasheet for ZMOD4410 section 8.4 Conditioning and Stability and found the complete explanation. "The ZMOD4410 will respond to TVOC immediately upon start-up; however, a conditioning period of 48 hours followed by a sensor module restart in an ambient environment is recommended to improve stability and obtain maximum performance. Best results are achieved with continuous operation because the module algorithm can learn about the environment over time." So I shouldn't look at sensor values after restart. I think the "stabilization" value mislead me as I've assumed that after "stabilization" reached 100% its sensor data is ready for consumption. As well as per section 8.6 Accuracy and Consistency I should expect ±25% accuracy for TVOC and ±10% for IAQ without additional calibration.
Here is another example of such behavior. So it is a consistent behavior of the sensors with .the recommended IAQ 2nd generation firmware.
But temperature and humidity readings where very stable.
Stabilization of sensors after reset takes ~11 minutes .It needs to be taken in consideration.
eCO2 level jumps from 0 to 400 after stabilization process completed.
ZMOD4410 is very sensitive. here its reaction on a cup of chamomile tea.
And IAQ sensor definitely can recognize presence even of a single human.
I very thankful to Element14 and Avnet for opportunity to participate in this roadtest. I'm thankful to Matthias from Renesas for proving assistance with C SDK.
I'm very excited about possibilities which can be opened by this new generation of sensors, which can be updated and tuned to different applications. As I start collection of air quality data in my office I'm planning to add additional content about exploring correlation between air quality and health metrics like sleep and physical performance using ML time-series data classification.
Top Comments
ah, great to see the Docker Compose in action.
A great way to keep running several services manageable.
Yes, docker-compose helps prevent a lot of issues (especially with Python and node.js) and simplify operations.
TKS SV I did a Hat RoadTest a few months ago and didn't experience any power issue. I was curious about what your environment that would result in an issue.