IDT ZMOD4410 Indoor Air Quality Raspberry Pi HAT - Review

Table of contents

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:

My Use Cases

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.


Demo Application

I've started my research by reading a nice blog Monitor Indoor Air Quality with the IDT ZMOD4410 Pi HAT by . 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 PYTHONPATH="${PYTHONPATH}:/usr/lib/python3/dist-packages"
ENV DEBIAN_FRONTEND=noninteractive

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 \
RUN curl -SL$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
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.




Home Assistant I2C vs MQTT


As the base of my zmod bridge I've used a project shared by . 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  .


UPDATE January 10th, 2021

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.



#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 = "";
  int port = 1883;
  int keepalive = 60;
  bool clean_session = true;
  topic = (char *)"office/sensor1";

  mosq = mosquitto_new(NULL, clean_session, NULL);
  if (!mosq) {
    fprintf(stderr, "Error: Out of memory.\n");

  if (mosquitto_connect(mosq, host, port, keepalive)) {
    fprintf(stderr, "Unable to connect.\n");
  int loop = mosquitto_loop_start(mosq);
  if (loop != MOSQ_ERR_SUCCESS) {
    fprintf(stderr, "Unable to start loop: %i\n", loop);

int mqtt_send(char *msg) {
  return mosquitto_publish(mosq, NULL, topic, strlen(msg), msg, 0, 0);

int main(int argc, char *argv[]) {


  if (zmodhat_init()) {
    fprintf(stderr, "ZMOD: INIT ERR");

  char *buf = (char *)malloc(96);
  while (1) {
    //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



Building ZMOD to MQTT bridge

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


Building Docker Image for ZMOD to MQTT bridge

I've used balenalib/raspberrypi3-debian image as the base image. The image is optimized for use IoT devices including RPi.


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
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:
Use 'apt autoremove' to remove it.
The following additional packages will be installed:
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 buster/main armhf libmosquitto1 armhf 1.5.7-1+deb10u1 [57.8 kB]
Get:2 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


Configuration for docker-compose

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.


version: '3'
    container_name: home-assistant
    image: homeassistant/home-assistant:stable
      - /home/pi/homeassistant:/config
      - TZ=America/New_York
    restart: on-failure
    network_mode: host
    container_name: mqtt
    hostname: mqtt
    image: eclipse-mosquitto:latest
      - "1883:1883"
      - /home/pi/mosquitto/config:/mqtt/config:ro
      - /home/pi/mosquitto/log:/mqtt/log
      - /home/pi/mosquitto/data/:/mqtt/data
      - /etc/localtime:/etc/localtime:ro
      - TZ=America/New_York
    restart: on-failure
    container_name: zmod
    image: zmod2mqtt:latest
      - "/dev/i2c-1:/dev/i2c-1"
      - mqtt
      - TZ=America/New_York
    restart: on-failure


Mosquitto MQTT Broker Configuration

I've used basic Mosquitto settings. You may need to extend it with extra settings, for example related to security.


# Place your local configuration in /mqtt/config/conf.d/
pid_file /var/run/

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


Home Assistant Configuration

HA configuration file defines sensors. ZMOD sensors are defined starting line 13.


  # Name of the location where Home Assistant is running
  name: Home
  # metric for Metric, imperial for Imperial
  unit_system: metric
  # Pick yours from here:
  time_zone: America/Montreal
  # Customization file
  customize: !include customize.yaml

# Sensors
  - 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 }}"


Running HA, ZMOD and MQTT in  docker-compose

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/ .…"   52 minutes ago      Up 51 minutes                                       zmod
4aad875d6134        eclipse-mosquitto:latest                 "/docker-entrypoint.…"   42 hours ago        Up 52 minutes    >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

Home Assistant

Dashboard with IAQ, TVOC, eCO2, Temperature, Humidity and other sensors

HA runs WEB UI by default on HTTP port 8123. Out of the box it provides overview of current values of all sensors.



Home Assistant eCO2 and TVOC History

And HA provides history of all sensors. This history is stored in SQLite database and can be extracted.



Sensors' readings before and after restart of zmod container

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.


UPDATE January 10th, 2021

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.


Electronic Nose

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.





  • Easy to install the HAT on RPi using recommended for RPi power supply.
  • Pre-calibrated sensors
  • Very sensitive
  • A lot of potentials to extend capabilities of "electronic nose"
  • Firmware and algorithm can be changed
  • Several firmware options provided for different use cases
  • C SDK is available for different platforms
  • A lot of documentation on C SDK
  • It is using a limited amount of compute and memory resources



  • No HAL provided for RPi
  • Policies on propriate software limit community investments
  • Time difference between Germany (IDT Renesas location) and Canada (my home) may impact time required to resolve challenges with support
  • IAQ (v2) value after reset can be very different compare to a calculated value before reset. May be it is result of a neural network used to calculate IAQ.
  • The HAT reduces air circulation around RPi CPU and may lead to overheating issues. But I never experienced it.
  • I'll need to add another component to get data on other important air quality indicator such as PM2.5 dust concentration.


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.