Skip to content
Ellis Percival edited this page Jan 21, 2021 · 8 revisions

MQTT IO is a server daemon which exposes general purpose IO (GPIO), hardware sensors and serial devices to an MQTT server, enabling remote monitoring and control. Home automation is a particular focus of the project, and is where it's been implemented and tested the most.

Originally built for the Raspberry Pi and its built in GPIO, the project quickly moved on to support other hardware platforms and many other GPIO modules, such as Banana Pi and PCF8574 GPIO chip.

Page Contents

Usage Scenarios

Since this is a piece of software that ties one thing (MQTT) to another (GPIO, sensors, streams), there are endless scenarios in which it can be used. Home Automation is where the software was born, but anywhere you require programmatic control of hardware devices, this software may be of use.

The following are a few simple examples which attempt to show most of the basic configuration options in place, and how they might be used.

Remote controlled mains sockets

A Raspberry Pi with an 8 channel PCF8574 IO chip, connected to relays which connect and disconnect the live wire to 4 mains sockets.

Example configuration

mqtt:
  host: test.mosquitto.org
  port: 1883
  user: ""
  password: ""
  topic_prefix: home/livingroom/sockets

gpio_modules:
  - name: sockets_gpio
    module: pcf8574
    i2c_bus_num: 1
    chip_addr: 0x20

digital_outputs:
  - name: socket1
    module: sockets_gpio
    pin: 1
    on_payload: "ON"
    off_payload: "OFF"
    pullup: yes

  - name: socket2
    module: sockets_gpio
    pin: 2
    on_payload: "ON"
    off_payload: "OFF"
    pullup: yes

  - name: socket3
    module: sockets_gpio
    pin: 3
    on_payload: "ON"
    off_payload: "OFF"
    pullup: yes

  - name: socket4
    module: sockets_gpio
    pin: 4
    on_payload: "ON"
    off_payload: "OFF"
    pullup: yes

This configuration uses the PCF8574 GPIO module to set 4 GPIO pins of the PCF8574 as outputs, then subscribes to messages on an MQTT topic for each output. Sending the configured on/off payload to these topics will cause the software to turn the outputs on and off, therefore supplying power to, or removing power from the individual sockets. These sockets may then be used for any general purpose, such as powering lights, heaters or fans.

In order to turn each individual socket on and off, you'd send the following MQTT messages:

home/livingroom/sockets/output/socket1/set: ON
home/livingroom/sockets/output/socket1/set: OFF

By varying the socket1 part of the topic, you're able to choose which socket you'd like to control.

Temperature and humidity sensor

A Raspberry Pi with a DHT22 temperature and humidity sensor connected to pin 4 of its built in GPIO pins.

Example configuration

mqtt:
  host: test.mosquitto.org
  port: 1883
  user: ""
  password: ""
  topic_prefix: home/livingroom/climate

sensor_modules:
  - name: dht22_sensor
    module: dht22
    type: AM2302
    pin: 4

sensor_inputs:
  - name: temperature
    module: dht22_sensor
    interval: 10
    digits: 4
    type: temperature

  - name: humidity
    module: dht22_sensor
    interval: 10
    digits: 4
    type: humidity

This configuration will poll the DHT22 sensor every 10 seconds and publish MQTT messages such as the following:

home/livingroom/climate/sensor/temperature: 23
home/livingroom/climate/sensor/humidity: 45

Float switch for water tank

A Beaglebone Black with a float switch connected to one of its built in GPIO pins which is pulled to ground when the water tank is full and the float switch engages.

Example configuration

mqtt:
  host: test.mosquitto.org
  port: 1883
  user: ""
  password: ""
  topic_prefix: home/rainwater

gpio_modules:
  - name: beaglebone_gpio
    module: beaglebone

digital_inputs:
  - name: tank
    module: beaglebone_gpio
    pin: GPIO0_26
    on_payload: full
    off_payload: ok
    inverted: yes
    pullup: yes

This configuration will poll the GPIO0_26 pin of the Beaglebone's built in GPIO and publish an MQTT message when it changes from low to high and vice versa:

home/rainwater/input/tank: ok
home/rainwater/input/tank: full

TODO: Add example for serial stream

Design Overview

The software reads the configuration file on startup, then initialises any configured IO, sensor and stream modules, connects to MQTT and subscribes to the relevant topics. GPIO and sensor inputs will either be polled as part of the main loop, or have their own polling co-routines on the asyncio version.

GPIO, sensor and stream modules are written specifically for each individual type of hardware, typically utilising a third party Python library to handle the actual communication with it. Each module implements a specific interface (GPIO, sensor or stream) so that there's a common layer of abstraction available to control them.

If a module requires any third party Python dependencies, they will be listed in the module code and installed as part of the module initialisation at runtime. This is so that the requirements for this project as a whole do not have to list and install every possible piece of hardware that's supported.

Configuration Overview

For more detailed information about configuration visit the Configuration page.

The software is configured using a single YAML file specified as the first argument upon execution.

In order to help avoid any misconfigurations, the provided configuration file is tested against a Cerberus schema and the program will display errors and exit during its initialisation phase if errors are found. Failing fast is preferable to only failing when, for example, an MQTT is received and the software attempts to control a module accordingly. This enables the user to fix the config while it's still fresh in mind, instead of some arbitrary amount of time down the line when the software may no longer be being supervised.

The main configuration schema is laid out in config.schema.yml and further schema may be optionally set as part of each module in the CONFIG_SCHEMA constant. This behaviour allows the modules to optionally require extra configuration specific to them.

Sensor and stream modules may also specify a config schema to be applied to each of the configured sensors and streams within the sensor_inputs, stream_reads and stream_writes sections.

There are various sections within the config file:

mqtt

Contains everything to do with the software's connection to the MQTT server.

gpio_modules, sensor_modules, stream_modules

These are lists of modules in use by the software. Modules may be used multiple times, for example, there are multiple PCF8574 IO chips connected with different chip addresses. Each module instance is given a name, which is how it is referred to when setting up the individual inputs and outputs.

This list of modules is used as part of the software initialisation when calculating which Python packages are required in order to communicate with the configured modules.

digital_inputs, digital_outputs

Lists of each individual digital input or output to be used. Inputs will be polled and MQTT messages will be sent upon changes. MQTT topics will be subscribed to in order to change outputs when messages are received to them.

Entries in these lists will specify a name, which GPIO pin it relates to, which module to use and optionally, what the MQTT payloads should be to relate to the 'on' and 'off' values, among other configuration values.

sensor_inputs

A list of sensors that will be polled and their values published to MQTT at the given intervals.

These entries also specify a name, which module they use, the interval at which to poll them and perhaps which kind of value to pull from the sensor.

stream_reads, stream_writes

A list of streams to read from or write to.

These entries specify a name and a module. Some optional configuration such as encoding and interval is accepted for stream_reads, and further configuration may be specified by the module itself.

MQTT Usage

During initialisation of the software, the config file will be parsed and a list of inputs and outputs will be defined. For each of the outputs, a set of MQTT topics will be subscribed to, in order to give external applications access to control the outputs in various ways.

The structure of topics follow a set of rules. Each topic will begin with the string set for topic_prefix in the mqtt section of the config file. The name set within the config file for each of the inputs and outputs will be used to identify it within the topic.

Status

<topic_prefix>/<status_topic> - messages will be published to this topic containing information about the state of the software. Unless changed in the mqtt section of the config file, the payloads will either be running, stopped or dead.

Digital Inputs

<topic_prefix>/input/<input_name> - each change to the logic level of an input GPIO pin will be published here with the payload specified in on_payload and off_payload for this input.

Digital Outputs

<topic_prefix>/output/<output_name> - any time an output is set by this software, a message will be published on this topic to confirm that the change has been carried out.

<topic_prefix>/output/<output_name>/set - most of the time, this will be the topic that you will publish to in order to immediately set the output to a specific value, using the payloads set in on_payload and off_payload for this output.

<topic_prefix>/output/<output_name>/set_on_ms - publish a message to this topic using an integer payload in order to set this output 'on' for the given number of milliseconds. After this time, the output will be set to 'off'.

<topic_prefix>/output/<output_name>/set_off_ms - as above, but set the output to 'off', and then 'on' after the specified number of milliseconds.

Sensors

<topic_prefix>/sensor/<sensor_name> - each time a sensor is read, its value will be published in the payload of a message to this topic.

Stream Reads

<topic_prefix>/stream/<stream_read_name> - each time new data are received on this stream, they will be published in the payload of a message to this topic.

Stream Writes

<topic_prefix>/stream/<stream_write_name> - publish a message to this topic and the data in its payload will be sent to this stream.

Client ID

The MQTT client ID identifies an instance of the software with the MQTT broker. It allows the broker to keep track of the state of the instance so that it can resume when it reconnects. This means that the ID must be unique for each instance that connects to the MQTT broker.

Since the MQTT client ID for each instance of the software is based on the topic_prefix supplied in config (#24), having multiple instances share the same topic_prefix will require you to set a different client_id for each:

Device 1
mqtt:
  host: test.mosquitto.org
  client_id: mqtt-io-device1
  topic_prefix: home/office
Device 2
mqtt:
  host: test.mosquitto.org
  client_id: mqtt-io-device2
  topic_prefix: home/office

This configuration isn't explicitly supported by the software, so it's important to understand that you'll get multiple responses when setting outputs and won't be able to identify their confirmations or and changed input messages.

Hardware Modules

One of the main goals for the project is to make it easy to implement support for different hardware. A GPIO abstraction layer containing all of the functionality that GPIO commonly exposes makes it simple to map specific GPIO library functions to the API of MQTT IO.

GPIO

General Purpose Input and Output (GPIO) allows you to set individual device pins "high" or "low", or to detect whether they are being pulled high or low by an external device such as a switch.

When used as an output, one can send MQTT messages to a specific topic and this software will set the pin to the desired state. When set up as an input, an MQTT message will be published whenever the state of the pin changes.

Sensors

Sensors take measurements at regular intervals and the values are published to specific MQTT topics.

Streams

Stream modules allow the sending and receiving of data from streams, such as a serial port. When data is received on the serial port, it is published to MQTT. When data is received on a specific MQTT topic, it's sent to the relevant serial port.

Module Requirements

Most of the modules use an external Python library to control the hardware for which the module is written. This means that we don't know until runtime which Python packages should be installed. Just after the configuration file is parsed, the software works out which packages are required and installs any that are missing. Each of the modules specifies a REQUIREMENTS constant which lists the name(s) of any required Python packages installable with pip. For example, in the dht22 sensor module:

REQUIREMENTS = ("Adafruit_DHT",)

Runtime Startup Sequence

  1. Loads and parses the configuration file
  2. Configures Python logging module
  3. Sets MQTT last will and testament (LWT) on MQTT client
  4. Validates config schema for GPIO modules
  5. Installs missing requirements for GPIO modules
  6. Validates config schema for sensor modules
  7. Installs missing requirements for sensor modules
  8. Validates config schema for stream modules
  9. Installs missing requirements for stream modules
  10. TODO: Validate GPIO input and output configurations
  11. Sets GPIO input pins to be inputs and configures their pullup/down values
  12. Configures interrupts for GPIO input pins if required/available
  13. Sets GPIO output pins to be outputs and sets their initial state if configured to do so
  14. Validates sensor input configs and performs any setup required by the sensor module for individual sensors
  15. Validates stream read configs and performs any setup required by the stream module for individual streams
  16. Validates stream write configs and performs any setup required by the stream module for individual streams
  17. Connects to MQTT
  18. Subscribes to MQTT topics for digital outputs and stream writes
  19. Publishes Home Assistant MQTT announcement messages for digital inputs, digital output and sensors
  20. Publishes initial states for digital outputs if configured to do so
  21. Starts sensor reading loop in a separate thread
  22. Starts stream reading loop in a separate thread
  23. Starts GPIO input reading loop in the main thread

Interrupts

Interrupts are an experimental feature that uses a GPIO module's underlying Python library to configure interrupts on digital inputs. The software provides a callback function to handle publishing to MQTT that an interrupt occurred. TODO: Include example of published message. This has mostly been tested on the Raspberry Pi's own GPIO.

Unfortunately there are some difficult issues to solve with interrupts. These include currently relying on the underlying library's own debouncing logic, which isn't perfect on Raspberry Pi, and the sub-optimal necessity to poll the inputs to receive their values after receiving an interrupt that says something changed.

Home Assistant Discovery

This software was designed in a way that intends to match some of the design principles of Home Assistant. It also contains a feature that specifically enables Home Assistant auto-discovery of the digital inputs, digital outputs and sensors configured for use by this software.

After connecting to the MQTT server, the software will announce digital inputs, digital outputs and sensors to Home Assistant by publishing a JSON payload containing details of the input/output/sensor to the Home Assistant discovery topics. For example, the following JSON might be sent to the homeassistant/binary_sensor/pi-mqtt-gpio-429373a4/button/config topic for a digital input:

{
  "name": "button",
  "unique_id": "pi-mqtt-gpio-429373a4_stdio_input_button",
  "state_topic": "pimqttgpio/mydevice/input/button",
  "availability_topic": "pimqttgpio/mydevice/status",
  "payload_available": "running",
  "payload_not_available": "dead",
  "payload_on": "ON",
  "payload_off": "OFF",
  "device": {
    "manufacturer": "MQTT GPIO",
    "identifiers": [
      "mqtt-gpio",
      "pi-mqtt-gpio-429373a4"
    ],
    "name": "MQTT GPIO"
  }
}

Installation, Execution and Deployment

The software is packaged like any other Python package, and uploaded to PyPI. This means that as long as you have pip installed, you can install this software with:

pip install pi-mqtt-gpio

To run the software, you must create a config file such as in the examples in this document, then use the following command (where config.yml is your configuration file):

python -m pi_mqtt_gpio.server config.yml

The software isn't tied to any specific deployment method, so it's left up to the user to decide how to deploy it. There is a short tutorial on how to configure supervisor in the project's README.md file.

An experimental Docker image is provided at https://hub.docker.com/r/flyte/mqtt-gpio but it's currently unmaintained, with the intention of setting up an automated build at some point soon.

TODO

  • Scheduler? (for set_on_ms etc.)
  • GPIO abstraction API
  • Sensor abstraction API
  • Streams abstraction API