Raspberry Pi Zero humidity sensor using MQTT: part 2

Using a Python script to get the temperature and humidity data from the DHT22 sensors.

Raspberry Pi Zero humidity sensor using MQTT: part 2

In part one we looked at setting up the Raspberry Pi Zero W and building the hardware.  In this post I'll discuss making the DHT22 sensors report to a message queue running on the the Pi Zero.

You can obtain a copy of the scripts from my GitHub repository here.

Adding the software

We're going to use the Adafruit DHT Python libraries in order to query the sensors and obtain temperature and humidity data.  Once we've got the values from each sensor the script will publish the data to a message queue to which Home Assistant will subscribe.  First let's install Python3 and pip, it's package manager after updating our list of available packages:

sudo apt update
sudo apt install python3-dev python3-pip

Next we'll use pip to install some additional dependencies (wheel here isn't anything to do with the Linux group that grants administrator rights).

sudo python3 -m pip install --upgrade pip setuptools wheel

Finally we'll install the Adafruit DHT library and the Paho MQTT library:

sudo pip3 install Adafruit_DHT paho-mqtt

We also need to install the MQTT broker that we'll be using and make it start automatically on boot using systemctl:

sudo apt install mosquitto mosquitto-clients
sudo systemctl enable mosquitto

Now the software is installed it's time to write our script.

Beginning the script

If you're looking to replicate my set up I recommend getting the latest version from my GitHub repo, as I won't be updating this blogpost per se.  The version below is a very quick and dirty script that could use some refinement (I have plans, so check the repo!).

This script performs a number of functions:

  1. Get the data from the sensors
  2. Publish the values to a message queue
  3. Write the data to a CSV found at LOGFILE

First there's some comments - text after a # which is just there to aid us humans!  The opening comments are followed by import statements that pull in the extra libraries we'll need for the script.  For example, the os and time libraries are used for writing to the CSV log file.

# humidityTemperatureMQTT.py (c) Jonathan Haddock, @joncojonathan, 2020

# Adapted from https://pimylifeup.com/raspberry-pi-humidity-sensor-dht22/ 
# also using guidance from https://appcodelabs.com/introduction-to-iot-build-an-mqtt-server-using-raspberry-pi
# and http://www.steves-internet-guide.com/into-mqtt-python-client/
# Many thanks!

import Adafruit_DHT
import paho.mqtt.client as paho
import os
import time

Once the imports are done we set our constants.  These can be changed by the user (you!) to adjust the script for your environment without having to edit the bulk of the script.  The Adafruit library needs to know if the sensor is a DHT11 or DHT22 so we define that first:

# Define constants
# Sensor type (Adafruit_DHT.DHT11 or Adafruit_DHT.DHT22)
DHT_SENSOR = Adafruit_DHT.DHT22

Next come some constants relating to the Raspberry Pi hardware, and where the sensors are connected.  As covered before, these numbers reference GPIO pin numbers that do not equal the physical pin number on the board:

# Configure GPIO pins
CUPBOARD_PIN = 17
FLOOR_PIN = 4
WASHINGMACHINE_PIN = 27

To finish off our constants we specify where the MQTT message queue can be found and the location of the CSV log file:

# MQTT details
MQTT_BROKER="127.0.0.1"
MQTT_PORT=1883

# Output file name
LOGFILE = "/home/pi/sensors.csv"

The rest of the script handles getting the data from the sensors, publishing it to the message queue and writing it to the CSV file.

What's a message queue anyway?

MQTT is an open protocol that allows data to be published to a topic that's managed by a broker.  Clients can then subscribe to the topic to receive a copy of the data when it's published.  In this case the Raspberry Pi Zero will be running the MQTT broker and will also be acting as one of the MQTT clients - clients both publish data and receive copies of topics to which their subscribed.

Diagram showing two clients connecting to a broker.
Diagram showing how clients connect to the message broker [1].

The rest of the script

Using the Paho MQTT library we create an object called client1 to which we can pass the data to publish, along with the topic name.  More details on how this works can be found in this post.

##########################################################
# Only edit below if you know what you're doing!
##########################################################

def on_publish(client,userdata,result):             #create function for callback
    print("data published \n")
    pass
client1= paho.Client("control1")                    #create client object
client1.on_publish = on_publish                     #assign function to callback
client1.connect(MQTT_BROKER,MQTT_PORT)              #establish connection

try:
    f = open(LOGFILE, 'a+')
    if os.stat(LOGFILE).st_size == 0:
            f.write('Date,Time,Sensor,Temperature,Humidity\r\n')
except:
    pass

We call the Adafruit library to get the humidity and temperature for each sensor.  I'd not seen the ability to assign values to multiple variables at the same time before (cupboardHumidity, cupboardTemperature =)  so that seems to be a nice feature of Python (possibly some other languages do this too).  The library is called via Adafruit_DHT.read_retry and we pass two arguments, DHT_SENSOR which is the type of sensor (DHT22 in my case) and the GPIO pin to query (CUPBOARD_PIN for my first sensor).  This is repeated for each sensor.

cupboardHumidity, cupboardTemperature = Adafruit_DHT.read_retry(DHT_SENSOR, CUPBOARD_PIN)
floorHumidity, floorTemperature = Adafruit_DHT.read_retry(DHT_SENSOR, FLOOR_PIN)
washingMachineHumidity, washingMachineTemperature = Adafruit_DHT.read_retry(DHT_SENSOR, WASHINGMACHINE_PIN)

So long as the sensor gives us some data we then publish that to the queue and record it in the CSV file.  At the time of writing, my script handles the response from each sensor in its own if else block.  This is not efficient and there's a better way to do this - something for me to improve in a later version.  Nonetheless, this approach does work.

if cupboardHumidity is not None and cupboardTemperature is not None:
    ret= client1.publish("kitchen/cupboard/temperature","{0:0.1f}".format(cupboardTemperature))
    ret= client1.publish("kitchen/cupboard/humidity","{0:0.1f}".format(cupboardHumidity))
    f.write('{0},{1},Cupboard,{2:0.1f}*C,{3:0.1f}%\r\n'.format(time.strftime('%y-%m-%d'), time.strftime('%H:%M'), cupboardTemperature, cupboardHumidity))
else:
    ret= client1.publish("kitchen/cupboard/temperature","FAILED")
    print("Failed to retrieve data from cupboard sensor")

if floorHumidity is not None and floorTemperature is not None:
    ret= client1.publish("kitchen/floor/temperature","{0:0.1f}".format(floorTemperature))
    ret= client1.publish("kitchen/floor/humidity","{0:0.1f}".format(floorHumidity))
    f.write('{0},{1},Floor,{2:0.1f}*C,{3:0.1f}%\r\n'.format(time.strftime('%y-%m-%d'), time.strftime('%H:%M'), floorTemperature, floorHumidity))
else:
    ret= client1.publish("kitchen/floor/temperature","FAILED")
    print("Failed to retrieve data from floor sensor")

if washingMachineHumidity is not None and washingMachineTemperature is not None:
    ret= client1.publish("kitchen/washing-machine/temperature","{0:0.1f}".format(washingMachineTemperature))
    ret= client1.publish("kitchen/washing-machine/humidity","{0:0.1f}".format(washingMachineHumidity))
    f.write('{0},{1},Washing-Machine,{2:0.1f}*C,{3:0.1f}%\r\n'.format(time.strftime('%y-%m-%d'), time.strftime('%H:%M'), washingMachineTemperature, washignMachineHumidity))
else:
    ret= client1.publish("kitchen/washing-machine/temperature","FAILED")
    print("Failed to retrieve data from washing-machine sensor")

We'll close the connection to the MQTT broker, for neatness and to preserve resources.

def on_disconnect(client, userdata, rc):
   print("client disconnected ok")
client1.on_disconnect = on_disconnect
client1.disconnect()

Troubleshooting missing modules

If you run the script and get an ImportError it's likely because python is linked to Python2, not Python3 - we installed the module via pip3.  The error will look something like this:

Traceback (most recent call last):
  File "humidityTemperature.py", line 2, in <module>
    import Adafruit_DHT
ImportError: No module named Adafruit_DHT

You can prove the theory by checking the versions ($ is the command prompt):

$ pip --version
pip 20.2.1 from /usr/local/lib/python3.7/dist-packages/pip (python 3.7)

$ python --version
Python 2.7.16

In order to run your script, run it via python3, e.g. python3 ./humidityTemperatureMQTT.py.

Conclusion

There's plenty of scope for improvements in this script - for a start it could be code golfed to reduce its size and remove duplicate code.  I'll take a look at that in due course.

You can get the code from my repo, here.

Next up I'll show you how to get the data into Home Assistant in part three.

Acknowledgements

Thanks to the following sites / blog posts that guided me through my set up:


Banner image: Screenshot of the beginning of the script.

[1] Image from https://en.wikipedia.org/wiki/MQTT#/media/File:MQTT_protocol_example_without_QoS.svg