diff --git a/docs/release_2.6.0/CHANGELOG.md b/docs/release_2.6.0/CHANGELOG.md
new file mode 100644
index 00000000..52b65484
--- /dev/null
+++ b/docs/release_2.6.0/CHANGELOG.md
@@ -0,0 +1,201 @@
+Unreleased
+==========
+- Nothing!
+
+.v2.4.0 - 2024-07-20
+====================
+- Bump tj-actions/branch-names from 2.2 to 7.0.7 in /.github/workflows by @dependabot in https://github.com/flyte/mqtt-io/pull/339
+- # Fix for poetry/docutils related bug by @BenjiU in https://github.com/flyte/mqtt-io/pull/367
+- upgrade DHT11/DHT22 backing library by @pansila in https://github.com/flyte/mqtt-io/pull/297
+- Install gcc for slim docker to build rpi.gpio on demand by @BenjiU in https://github.com/flyte/mqtt-io/pull/368
+- Remove lint warnings from bmp085.py by @BenjiU in https://github.com/flyte/mqtt-io/pull/375
+- Add support for YF-S201 flow rate sensor by @linucks in https://github.com/flyte/mqtt-io/pull/370
+- Support for ENS160 digital multi-gas sensor with multiple IAQ data (TVOC, eCO2, AQI) by @linucks in https://github.com/flyte/mqtt-io/pull/371
+- feat: add MH-Z19 sensor module by @kleest in https://github.com/flyte/mqtt-io/pull/365
+- Add Support for Sunxi Linux Boards by @fabys77 in https://github.com/flyte/mqtt-io/pull/100
+
+.v2.3.0 - 2024-03-01
+====================
+- 324 pinned pyyaml version incompatible with latest cython 300 by @BenjiU in #325
+- fix pipeline for tagging by @BenjiU in #323
+- pin pyyaml to v6.0.1 by @BenjiU in #326
+- Add new module for sensor adxl345 by @birdie1 in #223
+- Sensor INA219: Use optional i2c_bus_num by @mschlenstedt in #328
+- Update ads1x15.py by @maxthebuch in #329
+- repeat subscribe when reconnected to MQTT broker by @JohannesHennecke in #337
+- Fix non-unique identifiers reporting to HA by @dolai1 in #345
+- docker: use a "slim" base image by @chatziko in #342
+- Fix applying mqtt.reconnect_count by reordering except clauses by @zzeekk in #331
+- Add PMS5003 Particulate Sensor by @johnwang16 in #346
+- gpiod: enable pullup/pulldown by @chatziko in #341
+- docker: slim image, use rustup, build deps only on armv7 by @chatziko in #352
+
+.v2.2.9d - 2023-07-18
+====================
+- new sensors
+- fix for reconnection problem
+
+.v2.2.8 - 2023-01-19
+====================
+- Fix for #280 by @rlehfeld in #281
+- Fix reconnects_remaining referenced before assignment by @SamLeatherdale in #274
+- Only create one instance of sensor_module for ADS1x15 by @shbatm in #286
+- PN532 NFC/RFID reader implementation by @vytautassurvila in #269
+- Update README.md by @OzGav in #264
+- FIX OrangePi module by @neatherweb in #285
+- New DockerPi 4 Channel Relay GPIO module by @claudegel in #246
+- Digital Output: fix initial state inconsistency by @hacker-cb in #238
+- Add module mcp3xxx by @koleo9am in #227
+- Always remove finished transient_tasks. by @gmsoft-tuxicoman in #301
+
+.v2.2.7 - 2022-07-07
+====================
+- Fix some minor pylint issues and silence some others.
+- Fix bug with changing reference to 'edge' in raspberrypi module. #268 @vytautassurvila
+- Add INA219 sensor module. #221 @birdie1
+- Implement PinPUD.OFF for pcf8574/5. #217 @IlmLV
+- Ensure HCSR04 distance cannot be None. #215 @joseffallman
+- Add GPIOZero module. #212 @fipwmaqzufheoxq92ebc
+- Render config with confp to allow dynamic configuration based on environment/redis/etcd vars. #210 @fipwmaqzufheoxq92ebc
+- Log uncaught exceptions to configured logging handlers. #206 @fipwmaqzufheoxq92ebc
+
+v2.2.6 - 2021-04-23
+===================
+- Create docs in a tempdir to stop them from being clobbered when changing branches.
+
+v2.2.5 - 2021-04-23
+===================
+- Sort versions in docs. Use git pull properly.
+
+v2.2.4 - 2021-04-23
+===================
+- Generate docs versions and root index to strings and write them after switching branches
+
+v2.2.3 - 2021-04-23
+===================
+- Add docs root index to git separately
+
+v2.2.2 - 2021-04-23
+===================
+- Fix version regex for docs index generation
+
+v2.2.1 - 2021-04-23
+===================
+- Handle tags in generate docs script.
+
+v2.2.0 - 2021-04-23
+===================
+- Multi-versioned documentation.
+- Auto-reconnect to MQTT server on disconnection. #207 @fipwmaqzufheoxq92ebc
+
+v2.1.8 - 2021-04-21
+===================
+- Fix broken hcsr04 sensor that I (@flyte) broke when rewriting for v2.x. #211 @r00tat
+- Fix inversion not taken into account when publishing initial digital output value. #203 @r00tat
+- Fix #198 where Future wasn't created from the right thread. #205 @fipwmaqzufheoxq92ebc
+
+v2.1.7 - 2021-04-01
+===================
+- Add install_requirements config option to skip installing missing module requirements. #199
+
+v2.1.6 - 2021-04-01
+===================
+- Add ADS1x15 module. #200 @r00tat
+
+v2.1.5 - 2021-04-01
+===================
+- Update PyYAML version to 5.4 CVE-2020-14343
+
+v2.1.4 - 2021-03-26
+===================
+- Add version to 'model' field of HA Discovery config payload. #196 @pbill2003
+
+v2.1.3 - 2021-03-26
+===================
+- Add missing `spi_device` config schema entry for MCP3008 sensor module. #194
+
+v2.1.2 - 2021-03-24
+===================
+- Remove config validation that checks usage of the same numbered pin used twice. #191
+
+v2.1.1 - 2021-03-16
+===================
+- Fix bodged BH1750 sensor value reading code. #189
+
+v2.1.0 - 2021-03-11
+===================
+- Add *OPT-IN* error reporting to sentry. Bumps minor version because it adds a config entry.
+
+v2.0.1 - 2021-03-11
+===================
+- Fix bug where sensor config was retrieved from the wrong place https://github.com/flyte/mqtt-io/issues/185
+
+v2.0.0 - 2021-03-07
+===================
+- Rewrite core with asyncio
+- Change MQTT client to asyncio-mqtt
+- Add better validation for config
+- [Move some config values around](https://flyte.github.io/mqtt-io/#/config/v2-changes), but mostly stay compatible with existing configs
+- Add MCP23017 module
+- [Rework interrupts](https://flyte.github.io/mqtt-io/#/config/interrupts) to allow for pins to be interrupts for other pins on other modules
+- Enable extra values to be added to the [Home Assistant Discovery](https://flyte.github.io/mqtt-io/#/config/ha_discovery) config payloads
+- Rename package from pi-mqtt-gpio to mqtt-io since it's not just for Raspberry Pi, and not just for GPIO
+- Create generated documentation for the config file options ("Section Reference" section of [the documentation](https://flyte.github.io/mqtt-io/#/))
+- Tons more stuff, too varied to list here. It's safe to say that almost everything has been improved (hopefully) in some way
+
+v0.5.3 - 2020-10-17
+===================
+- Add PCF8575 support. #121
+- Add MCP3008 sensor support. #115
+- Add AHT20 sensor support. #122
+- Add BME280 sensor support. #132
+- Install requirements using current Python executable. #134
+- Add sensors to HASS discovery. #133
+- Add option to publish output value on startup. #125
+
+v0.5.2 - 2020-10-17
+===================
+- Update PyYAML to a version that doesn't suffer from CVE-2020-1747 vulnerability.
+- Add 'stream' IO.
+
+v0.3.1 - 2019-03-10
+===================
+- Pin safe version of PyYAML in requirements.
+
+v0.3.0 - 2019-03-10
+===================
+- Merge PR from @BenjiU which implements a new sensor interface. #52
+
+v0.0.12 - 2017-07-26
+====================
+- Add cleanup function to modules which are called before program exit. #16
+
+v0.0.11 - 2017-07-26
+====================
+- Decode received MQTT message payload as utf8 before trying to match with on/off payload values. #14
+
+v0.0.10 - 2017-07-26
+====================
+- Fix bug with selection of pullup value in raspberrypi module when none set. #15
+
+v0.0.9 - 2017-07-26
+===================
+- Successful fix for bug with loading config schema. #13
+
+v0.0.8 - 2017-07-26
+===================
+- Failed fix for bug with loading config schema. #13
+
+v0.0.7 - 2017-07-17
+===================
+
+- Implement `set_on_ms` and `set_off_ms` topic suffixes. Closes #10
+
+v0.0.6 - 2017-07-17
+===================
+
+- Large refactor and tidyup.
+- Implement config validation using cerberus.
+- Enable configuration of MQTT protocol. Closes #11.
+- Deploy Python Wheel as well as source package.
+- Add some (not exhaustive) tests.
diff --git a/docs/release_2.6.0/README.md b/docs/release_2.6.0/README.md
new file mode 100644
index 00000000..172d56b5
--- /dev/null
+++ b/docs/release_2.6.0/README.md
@@ -0,0 +1,280 @@
+
+
+# MQTT IO
+
+_Documentation version: `release_2.6.0`_
+
+[![Discord](https://img.shields.io/discord/713749043662290974.svg?label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/gWyV9W4)
+
+Exposes general purpose inputs and outputs (GPIO), hardware sensors and serial devices to an MQTT server. Ideal for single-board computers such as the Raspberry Pi.
+
+## Supported Hardware
+
+Hardware support is provided by specific GPIO, Sensor and Stream modules. It's easy to add support for new hardware and the list is growing fast.
+
+### GPIO Modules
+
+ - Beaglebone GPIO (`beaglebone`)
+ - DockerPi 4 Channel Relay GPIO (`dockerpi`)
+ - Linux Kernel 4.8+ libgpiod (`gpiod`)
+ - GPIO Zero (`gpiozero`)
+ - MCP23017 IO expander (`mcp23017`)
+ - Orange Pi GPIO (`orangepi`)
+ - PCF8574 IO expander (`pcf8574`)
+ - PCF8575 IO expander (`pcf8575`)
+ - PiFace Digital IO 2 (`piface2`)
+ - Raspberry Pi GPIO (`raspberrypi`)
+ - Sunxi Board (`sunxi`)
+ - XL9535/PCA9535/TCA9535 IO expander (`xl9535`)
+
+### Sensors
+
+ - ADS1x15 analog to digital converters (`ads1x15`)
+ - ADXL345 Digital Accelerometer Sensor
+
+Mandatory:
+- chip_addr
+
+Optional:
+- output_g (set True if output in g). default:m*s²
+
+Output:
+- x (in m*s²)
+- y (in m*s²)
+- z (in m*s²) (`adxl345`)
+ - AHT20 temperature and humidity sensor (`aht20`)
+ - AS3935 Ligntning Sensor
+
+Example configuration:
+
+sensor_modules:
+ - name: AS3935_Sensor
+ module: as3935
+ pin: 17
+ auto_filter: True
+ indoor: True
+
+sensor_inputs:
+ - name: distance
+ module: AS3935_Sensor
+ digits: 4
+ interval: 5
+ type: distance
+
+Module Options
+--------------
+See also:
+https://www.embeddedadventures.com/datasheets/AS3935_Datasheet_EN_v2.pdf
+https://github.com/fourstix/Sparkfun_CircuitPython_QwiicAS3935/blob/master/sparkfun_qwiicas3935.py
+https://github.com/fourstix/Sparkfun_CircuitPython_QwiicAS3935/blob/master/examples
+
+pin: Interrupt GPIO Pin
+auto_filter: Set noise_level, mask_disturber and watchdog_threshold automatically.
+ Default: True
+indoor: Set whether or not the sensor should use an indoor configuration.
+ Default: True
+lightning_threshold: number of lightning detections required before an
+ interrupt is raised.
+ Default: 1
+watchdog_threshold: This function returns the threshold for events that trigger the IRQ
+ Pin. Only sensitivity threshold values 1 to 10 allowed.
+ Default: 2
+spike_rejection: This setting, like the watchdog threshold, can help determine
+ between false events and actual lightning. The shape of the spike is
+ analyzed during the chip's signal validation routine. Increasing this
+ value increases robustness at the cost of sensitivity to distant
+ events.
+ Default: 2
+noise_level: The noise floor level is compared to a known reference voltage. If
+ this level is exceeded the chip will issue an interrupt to the IRQ pin,
+ broadcasting that it can not operate properly due to noise (INT_NH).
+ Check datasheet for specific noise level tolerances when setting this
+ register.
+ Default: 2
+mask_disturber Setting this True or False will change whether or not disturbers
+ trigger the IRQ pin.
+ Default: False
+division_ratio: The antenna is designed to resonate at 500kHz and so can be tuned
+ with the following setting. The accuracy of the antenna must be within
+ 3.5 percent of that value for proper signal validation and distance
+ estimation. The division ratio can only be set to 16, 32, 64 or 128.
+ Default: 16
+tune_cap: This setting will add capacitance to the series RLC antenna on the
+ product. It's possible to add 0-120pF in steps of 8pF to the antenna.
+ The Tuning Cap value will be set between 0 and 120pF, in steps of 8pF.
+ If necessary, the input value is rounded down to the nearest 8pF.
+ Default: 0
+
+Sensor Options
+--------------
+
+type: The following types are supported:
+ last: last lightning in unix timestamp format
+ distance: distance of last lightning in km
+ energy: energy of last lightning (no unit, no physical meaning)
+ number: number of lightning events since start (`as3935`)
+ - BH1750 light level sensor (`bh1750`)
+ - BME280 temperature, humidity and pressure sensor (`bme280`)
+ - BME680 temperature, humidity and pressure sensor (`bme680`)
+ - BMP085 temperature and pressure sensor (`bmp085`)
+ - DHT11/DHT22/AM2302 temperature and humidity sensors (`dht22`)
+ - DS18S20/DS1822/DS18B20/DS1825/DS28EA00/MAX31850K temperature sensors (`ds18b`)
+ - ENS160 Air Quality Sensor
+
+sensor_modules:
+ - name: ens160
+ module: ens160
+ chip_addr: 0x53
+ temperature_compensation: 25
+ humidity_compensation: 50
+
+sensor_inputs:
+ - name: air_quality
+ module: ens160
+ interval: 10
+ digits: 0
+ type: aqi
+
+ - name: volatile_organic_compounds
+ module: ens160
+ interval: 10
+ digits: 0
+ type: tvoc
+
+ - name: eco2
+ module: ens160
+ interval: 10
+ digits: 0
+ type: eco2 (`ens160`)
+ - Flowsensor: Generic Flow Rate Sensor
+
+Example configuration:
+
+sensor_modules:
+ - name: yfs201
+ module: flowsensor
+
+sensor_inputs:
+ - name: flow_rate1
+ module: yfs201
+ pin: 0
+ digits: 0
+ interval: 10
+ factor: 0.2
+
+Factor can be calculated from Pulse characteristcs (dicumentation):
+
+From YF-S201 manual:
+ Pulse Characteristic: F = 7 * Q (L/MIN).
+ Pulse frequency (Hz) / 7.0 = flow rate in L/min
+ ==> Factor = 7.0
+
+From YF-DN50 manual:
+ Pulse Characteristic: F = 0.2 * Q (L/MIN).
+ Pulse frequency (Hz) / 0.2 = flow rate in L/min
+ ==> Factor = 0.2
+
+If you use "factor = 1", the sensor module returns the frequency in Hertz (Hz). (`flowsensor`)
+ - Frequencycounter: Generic Frequency Counter
+
+Example configuration:
+
+sensor_modules:
+ - name: frequency
+ module: frequencycounter
+
+sensor_inputs:
+ - name: flow_rate1
+ module: frequency
+ pin: 0
+ digits: 0
+ interval: 10 (`frequencycounter`)
+ - HCSR04 ultrasonic range sensor (connected to the Raspberry Pi on-board GPIO) (`hcsr04`)
+ - INA219 DC current sensor (`ina219`)
+ - LM75 temperature sensor (`lm75`)
+ - MCP3008 analog to digital converter (`mcp3008`)
+ - MCP3xxx analog to digital converter via GPIOZero (`mcp3xxx`)
+ - MH-Z19 NDIR CO2 sensor (`mhz19`)
+ - PMS5003 Particulate Matter Sensor (`pms5003`)
+ - SHT4x temperature and humidity sensor (`sht4x`)
+ - TSL2561 luminosity sensor (`tsl2561`)
+ - VEML 6075 UV sensor (`veml6075`)
+ - VEML7700 luminosity sensor (`veml7700`)
+
+### Streams
+
+ - PN532 NFC/RFID reader (`pn532`)
+ - Serial port (`serial`)
+
+## Installation
+
+_Requires Python 3.6+_
+
+`pip3 install mqtt-io`
+
+## Execution
+
+`python3 -m mqtt_io config.yml`
+
+## Configuration Example
+
+Configuration is written in a YAML file which is passed as an argument to the server on startup.
+
+
+
+The following example will configure the software to do the following:
+
+- Publish MQTT messages on the `home/input/doorbell` topic when the doorbell is pushed and released.
+- Subscribe to the MQTT topic `home/output/port_light/set` and change the output when messages are received on it.
+- Periodically read the value of the LM75 sensor and publish it on the MQTT topic `home/sensor/porch_temperature`.
+- Publish any data received on the `/dev/ttyUSB0` serial port to the MQTT topic `home/serial/alarm_system`.
+- Subscribe to the MQTT topic `home/serial/alarm_system/send` and send any data received on that topic to the serial port.
+
+```yaml
+mqtt:
+ host: localhost
+ topic_prefix: home
+
+# GPIO
+gpio_modules:
+ # Use the Raspberry Pi built-in GPIO
+ - name: rpi
+ module: raspberrypi
+
+digital_inputs:
+ # Pin 0 is an input connected to a doorbell button
+ - name: doorbell
+ module: rpi
+ pin: 0
+
+digital_outputs:
+ # Pin 1 is an output connected to a light
+ - name: porch_light
+ module: rpi
+ pin: 1
+
+# Sensors
+sensor_modules:
+ # An LM75 sensor attached to the I2C bus
+ - name: lm75_sensor
+ module: lm75
+ i2c_bus_num: 1
+ chip_addr: 0x48
+
+sensor_inputs:
+ # The configuration of the specific sensor value to use (LM75 only has temperature)
+ - name: porch_temperature
+ module: lm75_sensor
+
+# Streams
+stream_modules:
+ # A serial port to communicate with the house alarm system
+ - name: alarm_system
+ module: serial
+ device: /dev/ttyUSB0
+ baud: 9600
+```
\ No newline at end of file
diff --git a/docs/release_2.6.0/_images/interrupt_callbacks.dot.svg b/docs/release_2.6.0/_images/interrupt_callbacks.dot.svg
new file mode 100644
index 00000000..f8bcc033
--- /dev/null
+++ b/docs/release_2.6.0/_images/interrupt_callbacks.dot.svg
@@ -0,0 +1,140 @@
+
+
+
+
+
diff --git a/docs/release_2.6.0/_images/interrupt_handling.dot.svg b/docs/release_2.6.0/_images/interrupt_handling.dot.svg
new file mode 100644
index 00000000..a0dc96be
--- /dev/null
+++ b/docs/release_2.6.0/_images/interrupt_handling.dot.svg
@@ -0,0 +1,471 @@
+
+
+
+
+
diff --git a/docs/release_2.6.0/_images/pimcp.png b/docs/release_2.6.0/_images/pimcp.png
new file mode 100644
index 00000000..bc7dda81
Binary files /dev/null and b/docs/release_2.6.0/_images/pimcp.png differ
diff --git a/docs/release_2.6.0/_sidebar.md b/docs/release_2.6.0/_sidebar.md
new file mode 100644
index 00000000..805fb873
--- /dev/null
+++ b/docs/release_2.6.0/_sidebar.md
@@ -0,0 +1,34 @@
+- [**MQTT IO**](/)
+
+- Documentation
+
+ - [Versions](../versions.md)
+
+- Configuration
+
+ - [Usage Scenarios](config/scenarios.md)
+ - [Interrupts](config/interrupts.md)
+ - [Home Assistant Discovery](config/ha_discovery.md)
+ - [V2 Changes](config/v2-changes.md)
+ - Section Reference
+ - [mqtt](config/reference/mqtt/)
+ - [gpio_modules](config/reference/gpio_modules/)
+ - [sensor_modules](config/reference/sensor_modules/)
+ - [stream_modules](config/reference/stream_modules/)
+ - [digital_inputs](config/reference/digital_inputs/)
+ - [digital_outputs](config/reference/digital_outputs/)
+ - [sensor_inputs](config/reference/sensor_inputs/)
+ - [logging](config/reference/logging/)
+ - [reporting](config/reference/reporting/)
+ - [options](config/reference/options/)
+
+- Deployment
+
+ - [Supervisor](deployment/supervisor.md)
+ - [Docker](deployment/docker.md)
+
+- Development
+
+ - [Config Schema](dev/config_schema.md)
+ - [Modules](dev/modules/README.md)
+ - [Changelog](CHANGELOG.md)
\ No newline at end of file
diff --git a/docs/release_2.6.0/_sidebar.md.j2 b/docs/release_2.6.0/_sidebar.md.j2
new file mode 100644
index 00000000..12cdbb88
--- /dev/null
+++ b/docs/release_2.6.0/_sidebar.md.j2
@@ -0,0 +1,27 @@
+- [**MQTT IO**](/)
+
+- Documentation
+
+ - [Versions](../versions.md)
+
+- Configuration
+
+ - [Usage Scenarios](config/scenarios.md)
+ - [Interrupts](config/interrupts.md)
+ - [Home Assistant Discovery](config/ha_discovery.md)
+ - [V2 Changes](config/v2-changes.md)
+ - Section Reference
+ {%- for ref_section in ref_sections %}
+ - [{{ ref_section["title"] }}]({{ ref_section["path"] }})
+ {%- endfor %}
+
+- Deployment
+
+ - [Supervisor](deployment/supervisor.md)
+ - [Docker](deployment/docker.md)
+
+- Development
+
+ - [Config Schema](dev/config_schema.md)
+ - [Modules](dev/modules/README.md)
+ - [Changelog](CHANGELOG.md)
diff --git a/docs/release_2.6.0/config/ha_discovery.md b/docs/release_2.6.0/config/ha_discovery.md
new file mode 100644
index 00000000..64d770d7
--- /dev/null
+++ b/docs/release_2.6.0/config/ha_discovery.md
@@ -0,0 +1,83 @@
+# Home Assistant Discovery
+
+In order to avoid having to configure your devices both on MQTT IO and on Home Assistant, MQTT IO will [send an announcement](https://www.home-assistant.io/docs/mqtt/discovery/) to Home Assistant to notify it of the devices that have been exposed.
+
+## Configuration
+
+Enable Home Assistant discovery:
+
+```yaml
+mqtt:
+ ha_discovery:
+ enabled: yes
+```
+
+To modify the announcements for individual inputs/outputs/sensors more appropriate, add the `ha_discovery` section to their config entries and add entries [as specified in the HA docs](https://www.home-assistant.io/docs/mqtt/discovery/)
+
+### Example
+
+```yaml
+mqtt:
+ host: localhost
+ ha_discovery:
+ enabled: yes
+
+gpio_modules:
+ - name: rpi
+ module: raspberrypi
+
+digital_inputs:
+ - name: door_sensor
+ module: rpi
+ pin: 1
+ ha_discovery:
+ name: Front Door
+ device_class: door
+
+digital_outputs:
+ - name: hall_fan
+ module: rpi
+ pin: 2
+ ha_discovery:
+ name: Hall Fan
+ device_class: fan
+```
+
+### Availability
+
+In order for Home Assistant to establish whether the input/output/sensor is available, it monitors the `state_topic` for `payload_available` and `payload_not_available`. By default, MQTT IO will set this to one of _three_ values depicted in the `mqtt` section as `status_payload_running`, `status_payload_stopped` and `status_payload_dead`. For Home Assistant's availability checking to work correctly, it might be worth changing your status payloads to be one of _two_ values instead:
+
+```yaml
+mqtt:
+ host: localhost
+ status_payload_running: available
+ status_payload_stopped: unavailable
+ status_payload_dead: unavailable
+```
+
+Unless set specifically in the `ha_discovery` section of the input/output/sensor configs, `payload_available` will be set to the value of `mqtt.status_payload_running` and `payload_not_available` will be set to the value of `mqtt.status_payload_dead`.
+
+## Implementation
+
+After connecting to the MQTT server, MQTT IO 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:
+
+```json
+{
+ "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 IO",
+ "identifiers": [
+ "mqtt-gpio",
+ "pi-mqtt-gpio-429373a4"
+ ],
+ "name": "MQTT IO"
+ }
+}
+```
diff --git a/docs/release_2.6.0/config/interrupts.md b/docs/release_2.6.0/config/interrupts.md
new file mode 100644
index 00000000..3dac0c84
--- /dev/null
+++ b/docs/release_2.6.0/config/interrupts.md
@@ -0,0 +1,113 @@
+# Interrupts
+
+Repeatedly checking the value of all of the GPIO inputs we're configured with is a lot of work and can miss level changes if they change back before we poll the pin again. Interrupts exist for this reason. They invert the process, which is to say that the lower level code (C library/kernel etc.) calls the higher level code (MQTT IO's Python code) when an input changes level. This has the benefit of not wasting CPU cycles on polling, as well as being much more responsive to input level changes.
+
+## Implementation
+
+The way that interrupts are exposed and supported by different hardware modules and software libraries can vary greatly.
+
+Sometimes the interrupts are available as software callbacks on the hardware's Python library. Sometimes the hardware will have registers that store which pin triggered the interrupt and what value it had. Sometimes the hardware will simply change the logic level on a dedicated pin to indicate that one of its inputs changed.
+
+MQTT IO attempts to accommodate these various configurations as best it can.
+
+Sometimes, like with the Raspberry Pi, this means that we get an interrupt on a pin, our Python callback function is called, but we still have to poll the pin for its current value (this only applies when the interrupt is configured to fire on both rising AND falling edges, as we can infer the value when it only triggers on a rising OR falling edge.)
+
+Other times, like with the MCP23017, we get no information from the software library that an interrupt has occurred, but by connecting its interrupt output pin to a Raspberry Pi input pin, we can use the software callback to trigger a read of the MCP23017's registers that specify which pin changed, and what value it got set to.
+
+## Example
+
+This is an example configuration using the Raspberry Pi and MCP23017 IO expander chip in which (as above) one of the MCP's interrupt output pins is connected to an input on the Raspberry Pi and the software is configured to listen on the Raspberry Pi pin's interrupt to then get the interrupt pin and value from the MCP.
+
+The following steps occur when the button in the diagram is pushed:
+
+1. `SW_Push` button is pushed, pulling the MCP's `GPA3` low
+2. The MCP pulls its interrupt pin `INTA` low
+3. The Raspberry Pi input pin `GPIO5` is pulled low and triggers an interrupt
+4. The Raspberry Pi's GPIO library calls MQTT IO's callback
+5. MQTT IO checks the `mcp23017` module's interrupt support and uses the `get_int_pins()` and `get_captured_int_pin_values()` methods to figure out which pin triggered the interrupt and what logic level it changed to
+6. MQTT IO publishes the value of the pin that changed (`GPA3`) to the `/input/mcp3` MQTT topic
+
+### Connection Diagram
+
+![Raspberry Pi MCP23017 circuit diagram](../_images/pimcp.png)
+
+### Configuration
+
+This is configured by adding the `interrupt_for` section to a `digital_inputs` entry in the config file:
+
+```yaml
+gpio_modules:
+ - name: rpi
+ module: raspberrypi
+
+ - name: mcp
+ module: mcp23017
+
+digital_inputs:
+ - name: pi5
+ module: rpi
+ pin: 5
+ interrupt: falling
+ interrupt_for:
+ - mcp3
+
+ - name: mcp3
+ module: mcp23017
+ pin: 3
+ interrupt: both
+```
+
+## Module Support
+
+It's up to each GPIO module to specify which interrupt features are supported by setting flags on its `INTERRUPT_SUPPORT` constant, as well as implement the methods to support them:
+
+```python
+from . import GenericGPIO, InterruptSupport
+
+class GPIO(GenericGPIO):
+ """
+ Implementation of GPIO class for the MCP23017 IO expander chip.
+ Pin numbers 0 - 15.
+ """
+
+ INTERRUPT_SUPPORT = (
+ InterruptSupport.FLAG_REGISTER
+ | InterruptSupport.CAPTURE_REGISTER
+ | InterruptSupport.INTERRUPT_PIN
+ | InterruptSupport.SET_TRIGGERS
+ )
+
+ def get_int_pins(self):
+ """
+ Read the register and return a list of pins that triggered the interrupt.
+ """
+ return self.io.int_flag
+
+ def get_captured_int_pin_values(self, pins=None):
+ """
+ Read the register that logs the values of the pins at the point of
+ the last interrupt, and return a dict.
+
+ If pins is None, then we get the whole register and return it, otherwise
+ just return the values for the pins requested.
+ """
+ values = self.io.int_cap
+ if pins is None:
+ pins = range(16)
+ pin_values = {}
+ for pin in pins:
+ pin_values[pin] = values[pin]
+ return pin_values
+
+ ...
+```
+
+This way, when an interrupt pin (for example on Raspberry Pi) is configured as an `interrupt_for` a pin on this module, the `GenericGPIO.get_interrupt_values_remote()` method will use the functions available to establish which pins were changed and what their values were.
+
+If the hardware doesn't support these features, then `GenericGPIO.get_interrupt_values_remote()` will just fall back to polling the pin(s) and returning those values instead.
+
+## Flow Chart
+
+The following is the somewhat complex flow of internal logic that describes the behaviour of the interrupt system.
+
+![Interrupt handling flow chart](../_images/interrupt_handling.dot.svg)
\ No newline at end of file
diff --git a/docs/release_2.6.0/config/reference.md.j2 b/docs/release_2.6.0/config/reference.md.j2
new file mode 100644
index 00000000..937b4c88
--- /dev/null
+++ b/docs/release_2.6.0/config/reference.md.j2
@@ -0,0 +1,59 @@
+{% for entry in ref_sections -%}
+{% if entry["toplevel_name"] == section -%}
+#{{ "#" * entry["depth"] }} {{ entry["title"] }} :id={{ entry["element_id"] }}
+
+{%- if entry["subtitle"] %}
+
+{{ entry["subtitle"] }}
+{%- endif %}
+
+{%- if "description" in entry["meta"] %}
+
+{{ entry["meta"]["description"] }}
+{%- endif %}
+
+```yaml
+Type: {{ entry["schema"]["type"] }}
+Required: {{ entry["schema"]["required"] }}
+
+{%- if "unit" in entry["meta"] %}
+Unit: {{ entry["meta"]["unit"] }}
+{%- endif %}
+
+{%- if "allowed" in entry["schema"] %}
+Allowed: {{ entry["schema"]["allowed"] }}
+{%- endif %}
+
+{%- if "min_val" in entry["schema"] %}
+Minimum value: {{ entry["schema"]["min_val"] }}
+{%- endif %}
+
+{%- if "max_val" in entry["schema"] %}
+Maximum value: {{ entry["schema"]["max_val"] }}
+{%- endif %}
+
+{%- if "allow_unknown" in entry["schema"] %}
+Unlisted entries accepted: {{ entry["schema"]["allow_unknown"] }}
+{%- endif %}
+
+{%- if "default" in entry["schema"] %}
+Default: {{ entry["schema"]["default"] }}
+{%- endif %}
+```
+
+{%- if "extra_info" in entry["meta"] %}
+
+?> {{ entry["meta"]["extra_info"] }}
+{%- endif %}
+
+{%- if "yaml_example" in entry["meta"] %}
+
+**Example**:
+
+```yaml
+{{ entry["meta"]["yaml_example"] -}}
+```
+{%- endif %}
+
+{% endif %}
+{%- endfor %}
diff --git a/docs/release_2.6.0/config/reference/digital_inputs/README.md b/docs/release_2.6.0/config/reference/digital_inputs/README.md
new file mode 100644
index 00000000..6b0907db
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/digital_inputs/README.md
@@ -0,0 +1,333 @@
+# digital_inputs :id=digital_inputs
+
+List of digital inputs to configure.
+
+```yaml
+Type: list
+Required: False
+Default: []
+```
+
+?> Some modules require extra config entries, specified by the modules themselves.
+Until the documentation is written for the individual modules, please refer to the
+`PIN_SCHEMA` and `INPUT_SCHEMA` values of the module's code in
+[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).
+TODO: Link this to the pending wiki pages on each module's requirements.
+
+
+**Example**:
+
+```yaml
+gpio_modules:
+ - name: rpi
+ module: raspberrypi
+
+digital_inputs:
+ - name: gpio0
+ module: rpi
+ pin: 0
+
+ - name: gpio1
+ module: rpi
+ pin: 1
+```
+
+## digital_inputs.* :id=digital_inputs-star
+
+*digital_inputs*.*****
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+## name :id=digital_inputs-star-name
+
+*digital_inputs.**.**name**
+
+Name of the input. Used in the MQTT topic when publishing input changes.
+
+The topic that input changes will be published to is:
+`/input/`
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## module :id=digital_inputs-star-module
+
+*digital_inputs.**.**module**
+
+Name of the module configured in `gpio_modules` that this input is attached to.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## pin :id=digital_inputs-star-pin
+
+*digital_inputs.**.**pin**
+
+Which of the GPIO module's pins this input refers to.
+
+```yaml
+Type: ['string', 'integer']
+Required: True
+```
+
+?> Depending on the GPIO module's implementation, this can be either a string
+or an integer.
+
+
+## on_payload :id=digital_inputs-star-on_payload
+
+*digital_inputs.**.**on_payload**
+
+Payload to be sent when the input changes to what is considered to be "on".
+See `inverted` below for the definition of "on" and "off".
+
+
+```yaml
+Type: string
+Required: False
+Default: ON
+```
+
+?> Make sure to avoid YAML's automatic boolean type conversion when setting this
+option by surrounding potential booleans with quotes.
+See the "Regexp" section of the
+[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that
+will be parsed as boolean.
+
+
+## off_payload :id=digital_inputs-star-off_payload
+
+*digital_inputs.**.**off_payload**
+
+Payload to be sent when the input changes to what is considered to be "off".
+See `inverted` below for the definition of "on" and "off".
+
+
+```yaml
+Type: string
+Required: False
+Default: OFF
+```
+
+?> Make sure to avoid YAML's automatic boolean type conversion when setting this
+option by surrounding potential booleans with quotes.
+See the "Regexp" section of the
+[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that
+will be parsed as boolean.
+
+
+## inverted :id=digital_inputs-star-inverted
+
+*digital_inputs.**.**inverted**
+
+Invert the logic level so that "low" levels are considered to be "on" and
+"high" levels are considered "off".
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+?> This can be useful for when an input is pulled "high" with a resistor and a
+device (like a button or another IC) connects it to ground when it's "active".
+
+
+## pullup :id=digital_inputs-star-pullup
+
+*digital_inputs.**.**pullup**
+
+Enable the pull-up resistor for this input so that the logic level is pulled
+"high" by default.
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+?> Not all GPIO modules support pull-up resistors.
+
+## pulldown :id=digital_inputs-star-pulldown
+
+*digital_inputs.**.**pulldown**
+
+Enable the pull-down resistor for this input so that the logic level is pulled
+"low" by default.
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+?> Not all GPIO modules support pull-down resistors.
+
+## interrupt :id=digital_inputs-star-interrupt
+
+*digital_inputs.**.**interrupt**
+
+Configure this pin to trigger an interrupt when the logic level is "rising",
+"falling" or "both".
+
+
+```yaml
+Type: string
+Required: False
+Allowed: ['rising', 'falling', 'both']
+```
+
+?> Not all GPIO modules support interrupts, and those that do may do so in
+various ways.
+TODO: Add link to interrupt documentation.
+
+
+## interrupt_for :id=digital_inputs-star-interrupt_for
+
+*digital_inputs.**.**interrupt_for**
+
+List of other pin names that this pin is an interrupt for.
+
+This is generally used on GPIO modules that provide software callbacks on
+interrupts, so that we can attach another "remote" module's interrupt output
+pin (one that changes logic level when one of its pins triggers an interrupt)
+to this input and use the callback to get the value of the "remote" pin and
+publish it on MQTT.
+
+TODO: Add link to interrupt documentation.
+
+
+```yaml
+Type: list
+Required: False
+```
+
+### interrupt_for.* :id=digital_inputs-star-interrupt_for-star
+
+*digital_inputs.*.interrupt_for*.*****
+
+```yaml
+Type: string
+Required: True
+```
+
+## bouncetime :id=digital_inputs-star-bouncetime
+
+*digital_inputs.**.**bouncetime**
+
+Don't trigger interrupts more frequently than once per `bouncetime`.
+
+
+```yaml
+Type: integer
+Required: False
+Unit: milliseconds
+Default: 100
+```
+
+## retain :id=digital_inputs-star-retain
+
+*digital_inputs.**.**retain**
+
+Set the retain flag on MQTT messages published on input change.
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+## poll_interval :id=digital_inputs-star-poll_interval
+
+*digital_inputs.**.**poll_interval**
+
+How long to wait between checking the value of this input.
+
+```yaml
+Type: float
+Required: False
+Unit: seconds
+Default: 0.1
+```
+
+?> When the pin is configured as an interrupt, the pin is no longer polled.
+The only exception to this is if the pin is configured as an interrupt for
+another pin. In this case, whether or not we poll is decided by the
+`poll_when_interrupt_for` setting below.
+
+
+## poll_when_interrupt_for :id=digital_inputs-star-poll_when_interrupt_for
+
+*digital_inputs.**.**poll_when_interrupt_for**
+
+Poll this pin when it's configured as an interrupt for another pin.
+
+```yaml
+Type: boolean
+Required: False
+Default: True
+```
+
+?> Polling the pin when it's configured as an interrupt for another pin is useful
+in order to make sure that if we somehow miss an interrupt on this pin (the
+remote module's interrupt output pin goes low ("triggered")), we
+don't end up stuck in that state where we don't handle the remote module's
+interrupt at all. If we poll the "triggered" value on this pin and our
+interrupt handling hasn't dealt with it, then we'll handle it here.
+
+
+## ha_discovery :id=digital_inputs-star-ha_discovery
+
+*digital_inputs.**.**ha_discovery**
+
+Configures the
+[Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
+for this pin.
+
+Any values entered into this section will be sent as part of the discovery
+config payload. See the above link for documentation.
+
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+**Example**:
+
+```yaml
+digital_inputs:
+ - name: livingroom_motion
+ module: rpi
+ ha_discovery:
+ component: binary_sensor
+ name: Living Room Motion
+ device_class: motion
+```
+
+### component :id=digital_inputs-star-ha_discovery-component
+
+*digital_inputs.*.ha_discovery*.**component**
+
+Type of component to report this input as to Home Assistant.
+
+```yaml
+Type: string
+Required: False
+Default: binary_sensor
+```
+
diff --git a/docs/release_2.6.0/config/reference/digital_outputs/README.md b/docs/release_2.6.0/config/reference/digital_outputs/README.md
new file mode 100644
index 00000000..3baae31f
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/digital_outputs/README.md
@@ -0,0 +1,254 @@
+# digital_outputs :id=digital_outputs
+
+List of digital outputs to configure.
+
+```yaml
+Type: list
+Required: False
+Default: []
+```
+
+?> Some modules require extra config entries, specified by the modules themselves.
+Until the documentation is written for the individual modules, please refer to the
+`PIN_SCHEMA` and `OUTPUT_SCHEMA` values of the module's code in
+[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).
+TODO: Link this to the pending wiki pages on each module's requirements.
+
+
+**Example**:
+
+```yaml
+gpio_modules:
+ - name: rpi
+ module: raspberrypi
+
+digital_outputs:
+ - name: gpio0
+ module: rpi
+ pin: 0
+
+ - name: gpio1
+ module: rpi
+ pin: 1
+```
+
+## digital_outputs.* :id=digital_outputs-star
+
+*digital_outputs*.*****
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+## name :id=digital_outputs-star-name
+
+*digital_outputs.**.**name**
+
+Name of the output. Used in the MQTT topics that are subscribed to in order to
+change the output value according to received MQTT messages, as well as in the
+MQTT topic for publishing output changes.
+
+The topics subscribed to for each output are:
+- `/output//set`
+- `/output//set_on_ms`
+- `/output//set_off_ms`
+
+The topic that output changes will be published to is:
+`/output/`
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## module :id=digital_outputs-star-module
+
+*digital_outputs.**.**module**
+
+Name of the module configured in `gpio_modules` that this output is attached to.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## pin :id=digital_outputs-star-pin
+
+*digital_outputs.**.**pin**
+
+Which of the GPIO module's pins this output refers to.
+
+```yaml
+Type: ['string', 'integer']
+Required: True
+```
+
+?> Depending on the GPIO module's implementation, this can be either a string
+or an integer.
+
+
+## on_payload :id=digital_outputs-star-on_payload
+
+*digital_outputs.**.**on_payload**
+
+Payload to consider as "on" when received to the `/set` topic for this output.
+See `inverted` below for the definition of "on" and "off".
+
+
+```yaml
+Type: string
+Required: False
+Default: ON
+```
+
+?> Make sure to avoid YAML's automatic boolean type conversion when setting this
+option by surrounding potential booleans with quotes.
+See the "Regexp" section of the
+[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that
+will be parsed as boolean.
+
+
+## off_payload :id=digital_outputs-star-off_payload
+
+*digital_outputs.**.**off_payload**
+
+Payload to consider as "off" when received to the `/set` topic for this output.
+See `inverted` below for the definition of "on" and "off".
+
+
+```yaml
+Type: string
+Required: False
+Default: OFF
+```
+
+?> Make sure to avoid YAML's automatic boolean type conversion when setting this
+option by surrounding potential booleans with quotes.
+See the "Regexp" section of the
+[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that
+will be parsed as boolean.
+
+
+## inverted :id=digital_outputs-star-inverted
+
+*digital_outputs.**.**inverted**
+
+Invert the logic level so that "low" levels are considered to be "on" and
+"high" levels are considered "off".
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+?> This can be useful for when an output turns something on when its output is
+"low".
+
+
+## timed_set_ms :id=digital_outputs-star-timed_set_ms
+
+*digital_outputs.**.**timed_set_ms**
+
+How long to set an output to the desired value on receipt of an MQTT message
+to the `/set` topic before then setting it back to the opposite value.
+
+
+```yaml
+Type: integer
+Required: False
+Unit: milliseconds
+```
+
+?> This may be useful if the output controls a device where leaving the ouput
+"on" for too long would be detrimental. Using this option means that you don't
+have to rely on a second "off" message getting through MQTT for the output to
+return to a safe state.
+
+
+## initial :id=digital_outputs-star-initial
+
+*digital_outputs.**.**initial**
+
+Set the output to an initial "high" or "low" state when the software starts.
+
+
+```yaml
+Type: string
+Required: False
+Allowed: ['high', 'low']
+```
+
+## publish_initial :id=digital_outputs-star-publish_initial
+
+*digital_outputs.**.**publish_initial**
+
+Whether to publish an MQTT message for the initial "high" or "low" state set
+above.
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+## retain :id=digital_outputs-star-retain
+
+*digital_outputs.**.**retain**
+
+Set the retain flag on MQTT messages published on output change.
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+## ha_discovery :id=digital_outputs-star-ha_discovery
+
+*digital_outputs.**.**ha_discovery**
+
+Configures the
+[Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
+for this pin.
+
+Any values entered into this section will be sent as part of the discovery
+config payload. See the above link for documentation.
+
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+**Example**:
+
+```yaml
+digital_outputs:
+ - name: garage_door1
+ module: rpi
+ ha_discovery:
+ component: switch
+ name: Ferrari Garage Door
+ device_class: garage_door
+```
+
+### component :id=digital_outputs-star-ha_discovery-component
+
+*digital_outputs.*.ha_discovery*.**component**
+
+Type of component to report this output as to Home Assistant.
+
+```yaml
+Type: string
+Required: False
+Default: switch
+```
+
diff --git a/docs/release_2.6.0/config/reference/gpio_modules/README.md b/docs/release_2.6.0/config/reference/gpio_modules/README.md
new file mode 100644
index 00000000..cc14636c
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/gpio_modules/README.md
@@ -0,0 +1,79 @@
+# gpio_modules :id=gpio_modules
+
+List of GPIO modules to configure for use with inputs and/or outputs.
+
+
+```yaml
+Type: list
+Required: False
+Default: []
+```
+
+?> Some modules require extra config entries, specified by the modules themselves.
+Until the documentation is written for the individual modules, please refer to the
+`CONFIG_SCHEMA` value of the module's code in
+[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).
+TODO: Link this to the pending wiki pages on each module's requirements.
+
+
+**Example**:
+
+```yaml
+gpio_modules:
+ - name: rpi_gpio
+ module: raspberrypi
+
+ - name: pcf
+ module: pcf8574
+ i2c_bus_num: 1
+ chip_addr: 0x20
+```
+
+## gpio_modules.* :id=gpio_modules-star
+
+*gpio_modules*.*****
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+## name :id=gpio_modules-star-name
+
+*gpio_modules.**.**name**
+
+Your name for this configuration of the module. Will be referred to by entries
+in the `digital_inputs` and/or `digital_outputs` sections.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## module :id=gpio_modules-star-module
+
+*gpio_modules.**.**module**
+
+Name of the module in the code. This is listed in the README's
+"Supported Hardware" section in brackets.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## cleanup :id=gpio_modules-star-cleanup
+
+*gpio_modules.**.**cleanup**
+
+Whether to run the module's `cleanup()` method on exit.
+
+```yaml
+Type: boolean
+Required: False
+Default: True
+```
+
diff --git a/docs/release_2.6.0/config/reference/logging/README.md b/docs/release_2.6.0/config/reference/logging/README.md
new file mode 100644
index 00000000..66fc0d64
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/logging/README.md
@@ -0,0 +1,14 @@
+# logging :id=logging
+
+Config to pass directly to
+[Python's logging module](https://docs.python.org/3/library/logging.config.html#logging-config-dictschema)
+to influence the logging output of the software.
+
+
+```yaml
+Type: dict
+Required: False
+Unlisted entries accepted: True
+Default: {'version': 1, 'handlers': {'console': {'class': 'logging.StreamHandler', 'formatter': 'default', 'level': 'INFO'}}, 'formatters': {'default': {'format': '%(asctime)s %(name)s [%(levelname)s] %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S'}}, 'loggers': {'mqtt_io': {'level': 'INFO', 'handlers': ['console'], 'propagate': True}}}
+```
+
diff --git a/docs/release_2.6.0/config/reference/mqtt/README.md b/docs/release_2.6.0/config/reference/mqtt/README.md
new file mode 100644
index 00000000..c7302edf
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/mqtt/README.md
@@ -0,0 +1,445 @@
+# mqtt :id=mqtt
+
+Contains the configuration data used for connecting to an MQTT server.
+
+
+```yaml
+Type: dict
+Required: True
+```
+
+**Example**:
+
+```yaml
+mqtt:
+ host: test.mosquitto.org
+ port: 8883
+ topic_prefix: mqtt_io
+ ha_discovery:
+ enabled: yes
+ tls:
+ enabled: yes
+ ca_certs: mosquitto.org.crt
+ certfile: client.crt
+ keyfile: client.key
+```
+
+## host :id=mqtt-host
+
+*mqtt*.**host**
+
+Host name or IP address of the MQTT server.
+
+```yaml
+Type: string
+Required: True
+```
+
+## port :id=mqtt-port
+
+*mqtt*.**port**
+
+Port number to connect to on the MQTT server.
+
+```yaml
+Type: integer
+Required: False
+Default: 1883
+```
+
+## user :id=mqtt-user
+
+*mqtt*.**user**
+
+Username to authenticate with on the MQTT server.
+
+```yaml
+Type: string
+Required: False
+Default:
+```
+
+## password :id=mqtt-password
+
+*mqtt*.**password**
+
+Password to authenticate with on the MQTT server.
+
+```yaml
+Type: string
+Required: False
+Default:
+```
+
+## client_id :id=mqtt-client_id
+
+*mqtt*.**client_id**
+
+[MQTT client ID](https://www.cloudmqtt.com/blog/2018-11-21-mqtt-what-is-client-id.html) to use on the MQTT server.
+
+
+```yaml
+Type: string
+Required: False
+Default:
+```
+
+## topic_prefix :id=mqtt-topic_prefix
+
+*mqtt*.**topic_prefix**
+
+Prefix to use for all topics.
+
+```yaml
+Type: string
+Required: False
+Default:
+```
+
+?> For example, a `topic_prefix` of `home/livingroom` would make a digital input
+called "doorbell" publish its changes to the `home/livingroom/input/doorbell`
+topic.
+
+
+## clean_session :id=mqtt-clean_session
+
+*mqtt*.**clean_session**
+
+Whether or not to start a
+[clean MQTT session](https://www.hivemq.com/blog/mqtt-essentials-part-7-persistent-session-queuing-messages/)
+on every MQTT connection.
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+## protocol :id=mqtt-protocol
+
+*mqtt*.**protocol**
+
+Version of the MQTT protocol to use.
+
+```yaml
+Type: string
+Required: False
+Allowed: ['3.1', '3.1.1']
+Default: 3.1.1
+```
+
+?> This renders in the documentation as a float, but should always be set within quotes.
+
+
+## keepalive :id=mqtt-keepalive
+
+*mqtt*.**keepalive**
+
+How frequently in seconds to send
+[ping packets](https://www.hivemq.com/blog/mqtt-essentials-part-10-alive-client-take-over/)
+to the MQTT server.
+
+
+```yaml
+Type: integer
+Required: False
+Unit: seconds
+Default: 10
+```
+
+## status_topic :id=mqtt-status_topic
+
+*mqtt*.**status_topic**
+
+Topic on which to send messages about the running status of this software.
+
+```yaml
+Type: string
+Required: False
+Default: status
+```
+
+?> Sends the payloads configured in `status_payload_running`,
+`status_payload_stopped` and `status_payload_dead`.
+
+
+## status_payload_running :id=mqtt-status_payload_running
+
+*mqtt*.**status_payload_running**
+
+Payload to send on the status topic when the software is running.
+
+```yaml
+Type: string
+Required: False
+Default: running
+```
+
+## status_payload_stopped :id=mqtt-status_payload_stopped
+
+*mqtt*.**status_payload_stopped**
+
+Payload to send on the status topic when the software has exited cleanly.
+
+```yaml
+Type: string
+Required: False
+Default: stopped
+```
+
+## status_payload_dead :id=mqtt-status_payload_dead
+
+*mqtt*.**status_payload_dead**
+
+Payload to send on the status topic when the software has exited unexpectedly.
+
+```yaml
+Type: string
+Required: False
+Default: dead
+```
+
+?> Uses [MQTT Last Will and Testament](https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament/)
+to make the server automatically send this payload if our connection fails.
+
+
+## client_module :id=mqtt-client_module
+
+*mqtt*.**client_module**
+
+MQTT Client implementation module path.
+
+```yaml
+Type: string
+Required: False
+Default: mqtt_io.mqtt.aiomqtt
+```
+
+?> There's currently only one implementation, which uses the
+[aiomqtt](https://github.com/sbtinstruments/aiomqtt/) client.
+
+
+## ha_discovery :id=mqtt-ha_discovery
+
+*mqtt*.**ha_discovery**
+
+```yaml
+Type: dict
+Required: False
+```
+
+### enabled :id=mqtt-ha_discovery-enabled
+
+*mqtt.ha_discovery*.**enabled**
+
+Enable [Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
+of our configured devices.
+
+
+```yaml
+Type: boolean
+Required: True
+```
+
+### prefix :id=mqtt-ha_discovery-prefix
+
+*mqtt.ha_discovery*.**prefix**
+
+Prefix for the Home Assistant MQTT discovery topic.
+
+```yaml
+Type: string
+Required: False
+Default: homeassistant
+```
+
+### name :id=mqtt-ha_discovery-name
+
+*mqtt.ha_discovery*.**name**
+
+Name to identify this "device" in Home Assistant.
+
+```yaml
+Type: string
+Required: False
+Default: MQTT IO
+```
+
+## tls :id=mqtt-tls
+
+*mqtt*.**tls**
+
+TLS/SSL settings for connecting to the MQTT server over an encrypted connection.
+
+
+```yaml
+Type: dict
+Required: False
+```
+
+**Example**:
+
+```yaml
+mqtt:
+ host: localhost
+ tls:
+ enabled: yes
+ ca_certs: mosquitto.org.crt
+ certfile: client.crt
+ keyfile: client.key
+```
+
+### enabled :id=mqtt-tls-enabled
+
+*mqtt.tls*.**enabled**
+
+Enable a secure connection to the MQTT server.
+
+```yaml
+Type: boolean
+Required: True
+```
+
+?> Most of these options map directly to the
+[`tls_set()` arguments](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)
+on the Paho MQTT client.
+
+
+### ca_certs :id=mqtt-tls-ca_certs
+
+*mqtt.tls*.**ca_certs**
+
+Path to the Certificate Authority certificate files that are to be treated
+as trusted by this client.
+[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)
+
+
+```yaml
+Type: string
+Required: False
+```
+
+### certfile :id=mqtt-tls-certfile
+
+*mqtt.tls*.**certfile**
+
+Path to the PEM encoded client certificate.
+[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)
+
+
+```yaml
+Type: string
+Required: False
+```
+
+### keyfile :id=mqtt-tls-keyfile
+
+*mqtt.tls*.**keyfile**
+
+Path to the PEM encoded client private key.
+[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)
+
+
+```yaml
+Type: string
+Required: False
+```
+
+### cert_reqs :id=mqtt-tls-cert_reqs
+
+*mqtt.tls*.**cert_reqs**
+
+Defines the certificate requirements that the client imposes on the MQTT server.
+[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)
+
+
+```yaml
+Type: string
+Required: False
+Allowed: ['CERT_NONE', 'CERT_OPTIONAL', 'CERT_REQUIRED']
+Default: CERT_REQUIRED
+```
+
+?> By default this is `CERT_REQUIRED`, which means that the broker must provide a certificate.
+
+
+### tls_version :id=mqtt-tls-tls_version
+
+*mqtt.tls*.**tls_version**
+
+Specifies the version of the SSL/TLS protocol to be used.
+[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)
+
+
+```yaml
+Type: string
+Required: False
+```
+
+?> By default the highest TLS version is detected.
+
+
+### ciphers :id=mqtt-tls-ciphers
+
+*mqtt.tls*.**ciphers**
+
+Which encryption ciphers are allowable for this connection.
+[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)
+
+
+```yaml
+Type: string
+Required: False
+```
+
+### insecure :id=mqtt-tls-insecure
+
+*mqtt.tls*.**insecure**
+
+Configure verification of the server hostname in the server certificate.
+[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-insecure-set)
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+?> If set to true, it is impossible to guarantee that the host you are
+connecting to is not impersonating your server. This can be useful in
+initial server testing, but makes it possible for a malicious third party
+to impersonate your server through DNS spoofing, for example.
+Do not use this function in a real system. Setting value to true means there
+is no point using encryption.
+
+
+## reconnect_delay :id=mqtt-reconnect_delay
+
+*mqtt*.**reconnect_delay**
+
+Time in seconds to wait between reconnect attempts.
+
+
+```yaml
+Type: integer
+Required: False
+Default: 2
+```
+
+## reconnect_count :id=mqtt-reconnect_count
+
+*mqtt*.**reconnect_count**
+
+Max number of retries of connections before giving up and exiting.
+Null value means infinite reconnects (default).
+The counter is reset when the connection is reestablished successfully.
+
+
+```yaml
+Type: integer
+Required: False
+Default: None
+```
+
diff --git a/docs/release_2.6.0/config/reference/options/README.md b/docs/release_2.6.0/config/reference/options/README.md
new file mode 100644
index 00000000..d9de834c
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/options/README.md
@@ -0,0 +1,29 @@
+# options :id=options
+
+Miscellaneous options regarding the runtime behaviour of MQTT IO.
+
+```yaml
+Type: dict
+Required: False
+Default: {}
+```
+
+**Example**:
+
+```yaml
+options:
+ install_requirements: no
+```
+
+## install_requirements :id=options-install_requirements
+
+*options*.**install_requirements**
+
+Whether to install missing module packages on startup.
+
+```yaml
+Type: boolean
+Required: False
+Default: True
+```
+
diff --git a/docs/release_2.6.0/config/reference/reporting/README.md b/docs/release_2.6.0/config/reference/reporting/README.md
new file mode 100644
index 00000000..3f874673
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/reporting/README.md
@@ -0,0 +1,54 @@
+# reporting :id=reporting
+
+Configuration for reporting back to the developers using
+[Sentry](https://sentry.io/welcome/) to help diagnose issues.
+
+*This is **not** enabled by default*
+
+
+```yaml
+Type: dict
+Required: False
+```
+
+?> Your config file is included in the report, but has the host, port and username
+hashed and the password removed. Sentry's SDK automatically attempts to remove
+password data, but the other values may still be exposed within the Python traceback
+context.
+
+
+**Example**:
+
+```yaml
+reporting:
+ enabled: yes
+ issue_id: 123
+```
+
+## enabled :id=reporting-enabled
+
+*reporting*.**enabled**
+
+Enable the sending of error reports to the developers if the software crashes.
+
+
+```yaml
+Type: boolean
+Required: True
+```
+
+## issue_id :id=reporting-issue_id
+
+*reporting*.**issue_id**
+
+The GitHub Issue ID that the specific error relates to.
+
+```yaml
+Type: integer
+Required: False
+```
+
+?> This is useful if you've reported a specific issue on the project repository and
+want to provide additional context to help the developers diagnose the issue.
+
+
diff --git a/docs/release_2.6.0/config/reference/sensor_inputs/README.md b/docs/release_2.6.0/config/reference/sensor_inputs/README.md
new file mode 100644
index 00000000..5f5fd07b
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/sensor_inputs/README.md
@@ -0,0 +1,176 @@
+# sensor_inputs :id=sensor_inputs
+
+List of sensor inputs to configure.
+
+```yaml
+Type: list
+Required: False
+Default: []
+```
+
+?> Some modules require extra config entries, specified by the modules themselves.
+Until the documentation is written for the individual modules, please refer to the
+`MODULE_SCHEMA` values of the module's code in
+[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).
+TODO: Link this to the pending wiki pages on each module's requirements.
+
+
+**Example**:
+
+```yaml
+sensor_modules:
+ - name: dht
+ module: dht22
+ type: AM2302
+ pin: 4
+
+sensor_inputs:
+ - name: workshop_temp
+ module: dht
+ type: temperature
+ interval: 30
+
+ - name: workshop_humidity
+ module: dht
+ type: humidity
+ interval: 60
+```
+
+## sensor_inputs.* :id=sensor_inputs-star
+
+*sensor_inputs*.*****
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+## name :id=sensor_inputs-star-name
+
+*sensor_inputs.**.**name**
+
+Name of the sensor. Used in the MQTT topic when publishing sensor values.
+
+The topic that sensor values will be published to is:
+`/sensor/`
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## module :id=sensor_inputs-star-module
+
+*sensor_inputs.**.**module**
+
+Name of the module configured in `sensor_modules` that this sensor reading
+comes from.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## retain :id=sensor_inputs-star-retain
+
+*sensor_inputs.**.**retain**
+
+Set the retain flag on MQTT messages published on sensor read.
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+## interval :id=sensor_inputs-star-interval
+
+*sensor_inputs.**.**interval**
+
+How long to wait between checking the value of this sensor.
+
+```yaml
+Type: integer
+Required: False
+Unit: seconds
+Default: 60
+```
+
+## digits :id=sensor_inputs-star-digits
+
+*sensor_inputs.**.**digits**
+
+How many decimal places to round the sensor reading to.
+
+```yaml
+Type: integer
+Required: False
+Default: 2
+```
+
+## ha_discovery :id=sensor_inputs-star-ha_discovery
+
+*sensor_inputs.**.**ha_discovery**
+
+Configures the
+[Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
+for this sensor.
+
+Any values entered into this section will be sent as part of the discovery
+config payload. See the above link for documentation.
+
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+**Example**:
+
+```yaml
+sensor_inputs:
+ - name: workshop_temp
+ module: dht
+ type: temperature
+ ha_discovery:
+ name: Workshop Temperature
+ device_class: temperature
+
+ - name: workshop_humidity
+ module: dht
+ type: humidity
+ ha_discovery:
+ name: Workshop Humidity
+ device_class: humidity
+```
+
+### component :id=sensor_inputs-star-ha_discovery-component
+
+*sensor_inputs.*.ha_discovery*.**component**
+
+Type of component to report this sensor as to Home Assistant.
+
+```yaml
+Type: string
+Required: False
+Default: sensor
+```
+
+### expire_after :id=sensor_inputs-star-ha_discovery-expire_after
+
+*sensor_inputs.*.ha_discovery*.**expire_after**
+
+How long after receiving a sensor update to declare it invalid.
+
+```yaml
+Type: integer
+Required: False
+```
+
+?> Defaults to `interval` * 2 + 5
+
+
diff --git a/docs/release_2.6.0/config/reference/sensor_modules/README.md b/docs/release_2.6.0/config/reference/sensor_modules/README.md
new file mode 100644
index 00000000..44609ed8
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/sensor_modules/README.md
@@ -0,0 +1,80 @@
+# sensor_modules :id=sensor_modules
+
+List of sensor modules to configure for use with sensor inputs.
+
+```yaml
+Type: list
+Required: False
+Default: []
+```
+
+?> Some modules require extra config entries, specified by the modules themselves.
+Until the documentation is written for the individual modules, please refer to the
+`CONFIG_SCHEMA` value of the module's code in
+[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).
+TODO: Link this to the pending wiki pages on each module's requirements.
+
+
+**Example**:
+
+```yaml
+sensor_modules:
+ - name: dht
+ module: dht22
+ type: AM2302
+ pin: 4
+
+ - name: ds
+ module: ds18b
+ type: DS18S20
+ address: 000803702e49
+```
+
+## sensor_modules.* :id=sensor_modules-star
+
+*sensor_modules*.*****
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+## name :id=sensor_modules-star-name
+
+*sensor_modules.**.**name**
+
+Your name for this configuration of the module. Will be referred to by entries
+in the `sensor_inputs` section.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## module :id=sensor_modules-star-module
+
+*sensor_modules.**.**module**
+
+Name of the module in the code. This is listed in the README's
+"Supported Hardware" section in brackets.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## cleanup :id=sensor_modules-star-cleanup
+
+*sensor_modules.**.**cleanup**
+
+Whether to run the module's `cleanup()` method on exit.
+
+```yaml
+Type: boolean
+Required: False
+Default: True
+```
+
diff --git a/docs/release_2.6.0/config/reference/stream_modules/README.md b/docs/release_2.6.0/config/reference/stream_modules/README.md
new file mode 100644
index 00000000..9f4815b7
--- /dev/null
+++ b/docs/release_2.6.0/config/reference/stream_modules/README.md
@@ -0,0 +1,135 @@
+# stream_modules :id=stream_modules
+
+List of stream modules to configure.
+
+```yaml
+Type: list
+Required: False
+Default: []
+```
+
+?> Some modules require extra config entries, specified by the modules themselves.
+Until the documentation is written for the individual modules, please refer to the
+`CONFIG_SCHEMA` value of the module's code in
+[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).
+TODO: Link this to the pending wiki pages on each module's requirements.
+
+
+**Example**:
+
+```yaml
+stream_modules:
+ - name: network_switch
+ module: serial
+ device: /dev/ttyUSB1
+ baud: 115200
+ interval: 10
+
+ - name: ups
+ module: serial
+ type: /dev/ttyUSB0
+ baud: 9600
+ interval: 1
+```
+
+## stream_modules.* :id=stream_modules-star
+
+*stream_modules*.*****
+
+```yaml
+Type: dict
+Required:
+Unlisted entries accepted: True
+```
+
+## name :id=stream_modules-star-name
+
+*stream_modules.**.**name**
+
+Your name for this configuration of the module. Will be used in the topic on
+which the stream's data is published and the topic on which messages can be
+sent for writing to the stream.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## module :id=stream_modules-star-module
+
+*stream_modules.**.**module**
+
+Name of the module in the code. This is listed in the README's
+"Supported Hardware" section in brackets.
+
+
+```yaml
+Type: string
+Required: True
+```
+
+## cleanup :id=stream_modules-star-cleanup
+
+*stream_modules.**.**cleanup**
+
+Whether to run the module's `cleanup()` method on exit.
+
+```yaml
+Type: boolean
+Required: False
+Default: True
+```
+
+## retain :id=stream_modules-star-retain
+
+*stream_modules.**.**retain**
+
+Whether to set the `retain` flag on MQTT messages publishing data received
+from the stream.
+
+
+```yaml
+Type: boolean
+Required: False
+Default: False
+```
+
+## read_interval :id=stream_modules-star-read_interval
+
+*stream_modules.**.**read_interval**
+
+How long to wait between polling the stream for new data.
+
+```yaml
+Type: float
+Required: False
+Unit: seconds
+Default: 60
+```
+
+## read :id=stream_modules-star-read
+
+*stream_modules.**.**read**
+
+Whether to poll this stream for incoming data and publish it on an MQTT topic.
+
+
+```yaml
+Type: boolean
+Required: False
+Default: True
+```
+
+## write :id=stream_modules-star-write
+
+*stream_modules.**.**write**
+
+Whether to subscribe to MQTT messages on a topic and write messages received on it to the stream.
+
+```yaml
+Type: boolean
+Required: False
+Default: True
+```
+
diff --git a/docs/release_2.6.0/config/scenarios.md b/docs/release_2.6.0/config/scenarios.md
new file mode 100644
index 00000000..d30507f4
--- /dev/null
+++ b/docs/release_2.6.0/config/scenarios.md
@@ -0,0 +1,154 @@
+# Usage Scenarios
+
+Since MQTT IO 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, MQTT IO 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**
+
+```yaml
+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:
+
+```yaml
+home/livingroom/sockets/output/socket1/set: ON
+```
+
+```yaml
+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**
+
+```yaml
+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:
+
+```yaml
+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**
+
+```yaml
+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:
+
+```yaml
+home/rainwater/input/tank: ok
+home/rainwater/input/tank: full
+```
+
+
+
+
+
+
diff --git a/docs/release_2.6.0/config/v2-changes.md b/docs/release_2.6.0/config/v2-changes.md
new file mode 100644
index 00000000..cdf27cc3
--- /dev/null
+++ b/docs/release_2.6.0/config/v2-changes.md
@@ -0,0 +1,48 @@
+# V2 Config Changes
+
+If you've been running MQTT IO (PI MQTT GPIO) before 07-03-2021 then you may need to make some changes to your config file to use with the new version.
+
+Config files from the v0.x version are mostly compatible, but some sections have been reorganised:
+
+## MQTT section
+
+- Moved the Home Assistant discovery options into their own [`ha_discovery` section](https://flyte.github.io/mqtt-io/#/config/reference/mqtt/?id=mqtt-ha_discovery).
+
+## All IO sections
+
+_These changes affect the `digital_inputs`, `digital_outputs` and `sensor_inputs` sections._
+
+- Added `.*.ha_discovery` section as documented [here (`digital_inputs`)](https://flyte.github.io/mqtt-io/#/config/reference/digital_inputs/?id=digital_inputs-star-ha_discovery), [here (`digital_outputs`)](https://flyte.github.io/mqtt-io/#/config/reference/digital_outputs/?id=digital_outputs-star-ha_discovery) and [here (`sensor_inputs`)](https://flyte.github.io/mqtt-io/#/config/reference/sensor_inputs/?id=sensor_inputs-star-ha_discovery).
+
+## Sensors
+
+- Removed `sensor_inputs.*.unit_of_measurement` in favour of adding it implicitly `sensor_inputs.*.ha_discovery.unit_of_measurement`
+- Moved `sensor_inputs.*.expire_after` explicitly to `sensor_inputs.*.ha_discovery.expire_after`.
+
+## Streams
+
+- Removed `stream_reads` and `stream_writes` in favour of just listing streams in `stream_modules` with `read` and `write` booleans to enable or disable the functionality.
+
+## Logging
+
+If you use a `logging` section, you'll need to update the `mqtt_gpio` logger to `mqtt_io` as the module name has changed.
+
+```yaml
+logging:
+ version: 1
+ handlers:
+ console:
+ class: logging.StreamHandler
+ formatter: default
+ level: DEBUG
+ formatters:
+ default:
+ format: "%(asctime)s %(name)s [%(levelname)-8s] %(message)s"
+ datefmt: "%Y-%m-%d %H:%M:%S"
+ loggers:
+ mqtt_io: # <-- this!
+ level: DEBUG
+ handlers:
+ - console
+ propagate: yes
+```
diff --git a/docs/release_2.6.0/deployment/docker.md b/docs/release_2.6.0/deployment/docker.md
new file mode 100644
index 00000000..5532304f
--- /dev/null
+++ b/docs/release_2.6.0/deployment/docker.md
@@ -0,0 +1,31 @@
+# Deployment with Docker
+
+_Current state: experimental and unmaintained_
+
+Two images have been created for Docker. One using the x86_64 architecture (for Intel and AMD CPUs) and one for the ARM architecture (for Raspberry Pi etc.). The tags of the images are therefore `flyte/mqtt-gpio:x86_64` and `flyte/mqtt-gpio:armv7l`. These are the outputs of `uname -m` on the two platforms they've been built on. For the following examples I'll assume you're running on Raspberry Pi.
+
+You may also run this software using Docker. You must create your config file as above, then run the docker image:
+
+```
+docker run -ti --rm -v /path/to/your/config.yml:/config.yml flyte/mqtt-gpio:armv7l
+```
+
+Or to run in the background:
+
+```
+docker run -d --name mqtt-gpio -v /path/to/your/config.yml:/config.yml flyte/mqtt-gpio:armv7l
+```
+
+You'll most likely want to use some hardware devices in your config, since that's what this project is all about. For example, if you wish to use the i2c bus, pass it through with a `--device` parameter:
+
+```
+docker run -ti --rm -v /path/to/your/config.yml:/config.yml --device /dev/i2c-0 flyte/mqtt-gpio:armv7l
+```
+
+If you aren't able to find the exact device path to use, then you can also run the docker container in `--privileged` mode which will pass all of the devices through from the host:
+
+```
+docker run -ti --rm -v /path/to/your/config.yml:/config.yml --privileged flyte/mqtt-gpio:armv7l
+```
+
+_Please raise an issue on Github if you find that any of this information is incorrect._
diff --git a/docs/release_2.6.0/deployment/supervisor.md b/docs/release_2.6.0/deployment/supervisor.md
new file mode 100644
index 00000000..c87973fc
--- /dev/null
+++ b/docs/release_2.6.0/deployment/supervisor.md
@@ -0,0 +1,46 @@
+# Deployment using Supervisor
+
+MQTT IO is not tied to any specific deployment method, but one recommended way is to use `virtualenv` and `supervisor`. This will launch the project at boot time and handle restarting and log file rotation. It's quite simple to set up:
+
+If using Raspbian, install `supervisor` with `apt`.
+
+```bash
+sudo apt-get update
+sudo apt-get install supervisor
+```
+
+Not strictly necessary, but it's recommended to install the project into a [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/#lower-level-virtualenv).
+
+```bash
+sudo apt-get install python-venv
+cd /home/pi
+python3 -m venv ve
+. ve/bin/activate
+pip install mqtt-io
+```
+
+Create yourself a config file, following instructions and examples above, and save it somewhere, such as `/home/pi/mqtt-io.yml`.
+
+Create a [supervisor config file](http://supervisord.org/configuration.html#program-x-section-settings) in /etc/supervisor/conf.d/mqtt-io.conf something along the lines of the following:
+
+```ini
+[program:mqtt_io]
+command = /home/pi/ve/bin/python -m mqtt_io mqtt-io.yml
+directory = /home/pi
+redirect_stderr = true
+stdout_logfile = /var/log/mqtt-io.log
+```
+
+Save the file and then run the following to update supervisor and start the program running.
+
+```bash
+sudo supervisorctl update
+```
+
+Check the status of your new supervisor job:
+
+```bash
+sudo supervisorctl status
+```
+
+Check the [supervisor docs](http://supervisord.org/running.html#supervisorctl-command-line-options) for more `supervisorctl` commands.
diff --git a/docs/release_2.6.0/dev/config_schema.md b/docs/release_2.6.0/dev/config_schema.md
new file mode 100644
index 00000000..3aaec928
--- /dev/null
+++ b/docs/release_2.6.0/dev/config_schema.md
@@ -0,0 +1,9 @@
+# Config Schema
+
+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](https://docs.python-cerberus.org/en/stable/) 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 `mqtt_io/config/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.
\ No newline at end of file
diff --git a/docs/release_2.6.0/dev/modules/README.md b/docs/release_2.6.0/dev/modules/README.md
new file mode 100644
index 00000000..d6748a11
--- /dev/null
+++ b/docs/release_2.6.0/dev/modules/README.md
@@ -0,0 +1,238 @@
+# Hardware Support Modules
+
+In order to support as much hardware as possible without changing the project's core code, every supported piece of hardware will have a relevant GPIO, sensor or stream module. These reside in `mqtt_io/modules` within the `gpio`, `sensor` and `stream` folders respectively. In each of these folders is an `__init__.py` file which contains the base class for each type of module. The hardware modules must include a class which overrides this base class and is named `GPIO`, `Sensor` or `Stream`.
+
+## Requirements
+
+In order for a module to specify its requirements, a module-level constant is used which lists them in the same format as the `pip install` command.
+
+[mqtt_io.modules.gpio.raspberrypi:REQUIREMENTS](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/raspberrypi.py#L13):
+
+```python
+REQUIREMENTS = ("RPi.GPIO",)
+```
+
+## Config Schema
+
+Along with the base module schema in `mqtt_io/config/config.schema.yml`, each hardware support module is able to specify extra config schema to allow the user to provide the details needed for using the specific hardware.
+
+To specify extra schema for the module-level config sections (`gpio_modules`, `sensor_modules`, `stream_modules`), a _module-level_ constant called `CONFIG_SCHEMA` is set, containing the [Cerberus Schema](https://docs.python-cerberus.org/en/stable/schemas.html) to add to the base schema.
+
+To specify extra schema for the pin-level config sections (`digital_inputs`, `digital_outputs`, `sensor_inputs` etc.), a _class-level_ constant called `PIN_SCHEMA` is set on the module's main class (`GPIO`, `Sensor`, `Stream`), containing the [Cerberus Schema](https://docs.python-cerberus.org/en/stable/schemas.html) to add to the base schema.
+
+If the pin-level schema only applies to an input or an output (in the case of a GPIO module), then instead of setting it on the `PIN_SCHEMA` class-level constant, use `INPUT_SCHEMA` or `OUTPUT_SCHEMA` respectively.
+
+[mqtt_io.modules.gpio.gpiod:CONFIG_SCHEMA](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/gpiod.py#L18):
+
+```python
+CONFIG_SCHEMA = {
+ "chip": {"type": "string", "required": False, "default": "/dev/gpiochip0"}
+}
+```
+
+## GPIO Modules
+
+...
+
+### Lifecycle
+
+#### `setup_module()`
+
+During software startup, each GPIO module's `setup_module()` method will be called once per instance of the module in the `gpio_modules` section of the config file.
+
+[mqtt_io.modules.gpio:GenericGPIO.setup_module](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/__init__.py#L109):
+
+```python
+def setup_module(self) -> None:
+ """
+ Called on initialisation of the GPIO module during the startup phase.
+
+ The module's config from the `gpio_modules` section of the config file is stored
+ in `self.config`.
+ """
+```
+
+For example, the `pcf8574` module's `setup_module()` method will be called twice given the following config:
+
+```yaml
+gpio_modules:
+ - name: pcf1
+ module: pcf8574
+ i2c_bus_num: 1
+ chip_addr: 0x20
+
+ - name: pcf2
+ module: pcf8574
+ i2c_bus_num: 1
+ chip_addr: 0x21
+```
+
+Within this method we import any Python dependencies. It's important to not do this at module level, so that the core code is able to import the module before its dependencies are installed.
+
+The GPIO library is then initialised and an object may be stored (usually at `self.io`) for use by the module during runtime.
+
+It may be appropriate to build mappings of pin directions (input, output), pullups (up, down, off) and interrupt edges (rising, falling, both) if appropriate for this hardware. The base GenericGPIO class uses its own constants to refer to these, so the mappings translate the base GenericGPIO's constants to ones used by the hardware's Python library.
+
+[mqtt_io.modules.gpio:PinDirection](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/__init__.py#L22):
+
+```python
+class PinDirection(Enum):
+ """
+ Whether the GPIO pin is an input or an output.
+ """
+
+ INPUT = auto()
+ OUTPUT = auto()
+```
+
+[mqtt_io.modules.gpio:PinPUD](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/__init__.py#L31):
+
+```python
+class PinPUD(Enum):
+ """
+ Whether the GPIO pin should be pulled up, down or not anywhere.
+ """
+
+ OFF = auto()
+ UP = auto()
+ DOWN = auto()
+```
+
+[mqtt_io.modules.gpio:InterruptEdge](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/__init__.py#L41):
+
+```python
+class InterruptEdge(Enum):
+ """
+ Whether to trigger an interrupt on rising edge, falling edge or both.
+ """
+
+ RISING = auto()
+ FALLING = auto()
+ BOTH = auto()
+```
+
+The `raspberrypi` GPIO module is a good example of the above:
+
+[mqtt_io.modules.gpio.raspberrypi:GPIO.setup_module](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/raspberrypi.py#L23):
+
+```python
+def setup_module(self) -> None:
+ # pylint: disable=import-outside-toplevel,import-error
+ import RPi.GPIO as gpio # type: ignore
+
+ self.io = gpio
+ self.direction_map = {PinDirection.INPUT: gpio.IN, PinDirection.OUTPUT: gpio.OUT}
+
+ self.pullup_map = {
+ PinPUD.OFF: gpio.PUD_OFF,
+ PinPUD.UP: gpio.PUD_UP,
+ PinPUD.DOWN: gpio.PUD_DOWN,
+ }
+
+ self.interrupt_edge_map = {
+ InterruptEdge.RISING: gpio.RISING,
+ InterruptEdge.FALLING: gpio.FALLING,
+ InterruptEdge.BOTH: gpio.BOTH,
+ }
+
+ gpio.setmode(gpio.BCM)
+```
+
+#### Polling Loop
+
+If a digital input is not configured as an [interrupt](config/interrupts.md) (or even [sometimes if it is](config/reference/digital_inputs/?id=digital_inputs-star-interrupt_for)), then a loop will be created which polls the pin's current value and publishes a `DigitalInputChangedEvent` event when it does. As part of the initialisation of each pin, a callback function to publish the new value on MQTT will be subscribed to this event.
+
+[mqtt_io.server.MqttIo._init_digital_inputs](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/server.py#L377):
+
+```python
+def _init_digital_inputs(self) -> None:
+ async def publish_callback(event: DigitalInputChangedEvent) -> None:
+ in_conf = self.digital_input_configs[event.input_name]
+ value = event.to_value != in_conf["inverted"]
+ val = in_conf["on_payload"] if value else in_conf["off_payload"]
+ self.mqtt_task_queue.put_nowait(
+ PriorityCoro(
+ self._mqtt_publish(
+ MQTTMessageSend(
+ "/".join(
+ (
+ self.config["mqtt"]["topic_prefix"],
+ INPUT_TOPIC,
+ event.input_name,
+ )
+ ),
+ val.encode("utf8"),
+ retain=in_conf["retain"],
+ )
+ ),
+ MQTT_PUB_PRIORITY,
+ )
+ )
+ self.event_bus.subscribe(DigitalInputChangedEvent, publish_callback)
+```
+
+#### `setup_pin()`
+
+For each of the entries in `digital_inputs` and `digital_outputs`, `setup_pin()` will be called. This step is for configuring the hardware's pins to be input or outputs, or anything else that must be set at pin level.
+
+[mqtt_io.modules.gpio:GenericGPIO.setup_pin](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/__init__.py#L118):
+
+```python
+def setup_pin(
+ self,
+ pin: PinType,
+ direction: PinDirection,
+ pullup: PinPUD,
+ pin_config: ConfigType,
+ initial: Optional[str] = None,
+) -> None:
+ """
+ Called on initialisation of each pin of the GPIO module during the startup phase.
+
+ The `pin_config` passed in here is the pin's entry in the `digital_inputs` or
+ `digital_outputs` section of the config file.
+ """
+```
+
+For example, it would be called three times given the following configuration:
+
+```yaml
+digital_inputs:
+ - name: doorbell
+ module: raspberrypi
+ pin: 1
+
+ - name: lightswitch
+ module: raspberrypi
+ pin: 2
+
+digital_outputs:
+ - name: lights
+ module: raspberrypi
+ pin: 3
+```
+
+Here's the `raspberrypi` GPIO module's `setup_pin()` implementation:
+
+[mqtt_io.modules.gpio.raspberrypi:GPIO.setup_pin](https://github.com/flyte/mqtt-io/blob/e5fdf95146b7e26a5d98d83e3a2558f8837a6038/mqtt_io/modules/gpio/raspberrypi.py#L44):
+
+```python
+def setup_pin(
+ self,
+ pin: PinType,
+ direction: PinDirection,
+ pullup: PinPUD,
+ pin_config: ConfigType,
+ initial: Optional[str] = None,
+) -> None:
+ direction = self.direction_map[direction]
+ pullup = self.pullup_map[pullup]
+
+ initial_int = {None: -1, "low": 0, "high": 1}[initial]
+ self.io.setup(pin, direction, pull_up_down=pullup, initial=initial_int)
+```
+
+
+## TODO
+
+- Define when 'inverted' values are expected to be inverted or raw.
\ No newline at end of file
diff --git a/docs/release_2.6.0/dev/modules/README.md.j2 b/docs/release_2.6.0/dev/modules/README.md.j2
new file mode 100644
index 00000000..f009c9fe
--- /dev/null
+++ b/docs/release_2.6.0/dev/modules/README.md.j2
@@ -0,0 +1,166 @@
+# Hardware Support Modules
+
+In order to support as much hardware as possible without changing the project's core code, every supported piece of hardware will have a relevant GPIO, sensor or stream module. These reside in `mqtt_io/modules` within the `gpio`, `sensor` and `stream` folders respectively. In each of these folders is an `__init__.py` file which contains the base class for each type of module. The hardware modules must include a class which overrides this base class and is named `GPIO`, `Sensor` or `Stream`.
+
+## Requirements
+
+In order for a module to specify its requirements, a module-level constant is used which lists them in the same format as the `pip install` command.
+
+{{ source(
+ "mqtt_io.modules.gpio.raspberrypi",
+ "//Assign[targets/Name/id='REQUIREMENTS']",
+ "mqtt_io.modules.gpio.raspberrypi:REQUIREMENTS"
+)}}
+
+## Config Schema
+
+Along with the base module schema in `mqtt_io/config/config.schema.yml`, each hardware support module is able to specify extra config schema to allow the user to provide the details needed for using the specific hardware.
+
+To specify extra schema for the module-level config sections (`gpio_modules`, `sensor_modules`, `stream_modules`), a _module-level_ constant called `CONFIG_SCHEMA` is set, containing the [Cerberus Schema](https://docs.python-cerberus.org/en/stable/schemas.html) to add to the base schema.
+
+To specify extra schema for the pin-level config sections (`digital_inputs`, `digital_outputs`, `sensor_inputs` etc.), a _class-level_ constant called `PIN_SCHEMA` is set on the module's main class (`GPIO`, `Sensor`, `Stream`), containing the [Cerberus Schema](https://docs.python-cerberus.org/en/stable/schemas.html) to add to the base schema.
+
+If the pin-level schema only applies to an input or an output (in the case of a GPIO module), then instead of setting it on the `PIN_SCHEMA` class-level constant, use `INPUT_SCHEMA` or `OUTPUT_SCHEMA` respectively.
+
+{{ source(
+ "mqtt_io.modules.gpio.gpiod",
+ "//Assign[targets/Name/id='CONFIG_SCHEMA']",
+ "mqtt_io.modules.gpio.gpiod:CONFIG_SCHEMA"
+)}}
+
+## GPIO Modules
+
+...
+
+### Lifecycle
+
+#### `setup_module()`
+
+During software startup, each GPIO module's `setup_module()` method will be called once per instance of the module in the `gpio_modules` section of the config file.
+
+{{ source(
+ "mqtt_io.modules.gpio",
+ "//ClassDef[name='GenericGPIO']//FunctionDef[name='setup_module']",
+ "mqtt_io.modules.gpio:GenericGPIO.setup_module"
+) }}
+
+For example, the `pcf8574` module's `setup_module()` method will be called twice given the following config:
+
+```yaml
+gpio_modules:
+ - name: pcf1
+ module: pcf8574
+ i2c_bus_num: 1
+ chip_addr: 0x20
+
+ - name: pcf2
+ module: pcf8574
+ i2c_bus_num: 1
+ chip_addr: 0x21
+```
+
+Within this method we import any Python dependencies. It's important to not do this at module level, so that the core code is able to import the module before its dependencies are installed.
+
+The GPIO library is then initialised and an object may be stored (usually at `self.io`) for use by the module during runtime.
+
+It may be appropriate to build mappings of pin directions (input, output), pullups (up, down, off) and interrupt edges (rising, falling, both) if appropriate for this hardware. The base GenericGPIO class uses its own constants to refer to these, so the mappings translate the base GenericGPIO's constants to ones used by the hardware's Python library.
+
+{{ source(
+ "mqtt_io.modules.gpio",
+ "//ClassDef[name='PinDirection']",
+ "mqtt_io.modules.gpio:PinDirection"
+) }}
+
+{{ source(
+ "mqtt_io.modules.gpio",
+ "//ClassDef[name='PinPUD']",
+ "mqtt_io.modules.gpio:PinPUD"
+) }}
+
+{{ source(
+ "mqtt_io.modules.gpio",
+ "//ClassDef[name='InterruptEdge']",
+ "mqtt_io.modules.gpio:InterruptEdge"
+) }}
+
+The `raspberrypi` GPIO module is a good example of the above:
+
+{{ source(
+ "mqtt_io.modules.gpio.raspberrypi",
+ "//ClassDef[name='GPIO']//FunctionDef[name='setup_module']",
+ "mqtt_io.modules.gpio.raspberrypi:GPIO.setup_module"
+) }}
+
+#### Polling Loop
+
+If a digital input is not configured as an [interrupt](config/interrupts.md) (or even [sometimes if it is](config/reference/digital_inputs/?id=digital_inputs-star-interrupt_for)), then a loop will be created which polls the pin's current value and publishes a `DigitalInputChangedEvent` event when it does. As part of the initialisation of each pin, a callback function to publish the new value on MQTT will be subscribed to this event.
+
+{{ source_link(
+ "mqtt_io.server",
+ "//ClassDef[name='MqttIo']//FunctionDef[name='_init_digital_inputs']",
+ "mqtt_io.server.MqttIo._init_digital_inputs"
+)}}
+
+```python
+{{ sources_raw(
+ [
+ (
+ "mqtt_io.server",
+ "//ClassDef[name='MqttIo']//FunctionDef[name='_init_digital_inputs']",
+ "//ClassDef[name='MqttIo']//FunctionDef[name='_init_digital_inputs']/body/*[1]"
+ ),
+ (
+ "mqtt_io.server",
+ "//ClassDef[name='MqttIo']//FunctionDef[name='_init_digital_inputs']//AsyncFunctionDef[name='publish_callback']",
+ None
+ ),
+ (
+ "mqtt_io.server",
+ "//ClassDef[name='MqttIo']//FunctionDef[name='_init_digital_inputs']//Call[args//id='DigitalInputChangedEvent']",
+ None
+ )
+ ],
+ dedent=True
+)}}
+```
+
+#### `setup_pin()`
+
+For each of the entries in `digital_inputs` and `digital_outputs`, `setup_pin()` will be called. This step is for configuring the hardware's pins to be input or outputs, or anything else that must be set at pin level.
+
+{{ source(
+ "mqtt_io.modules.gpio",
+ "//ClassDef[name='GenericGPIO']//FunctionDef[name='setup_pin']",
+ "mqtt_io.modules.gpio:GenericGPIO.setup_pin"
+) }}
+
+For example, it would be called three times given the following configuration:
+
+```yaml
+digital_inputs:
+ - name: doorbell
+ module: raspberrypi
+ pin: 1
+
+ - name: lightswitch
+ module: raspberrypi
+ pin: 2
+
+digital_outputs:
+ - name: lights
+ module: raspberrypi
+ pin: 3
+```
+
+Here's the `raspberrypi` GPIO module's `setup_pin()` implementation:
+
+{{ source(
+ "mqtt_io.modules.gpio.raspberrypi",
+ "//ClassDef[name='GPIO']//FunctionDef[name='setup_pin']",
+ "mqtt_io.modules.gpio.raspberrypi:GPIO.setup_pin"
+) }}
+
+
+## TODO
+
+- Define when 'inverted' values are expected to be inverted or raw.
diff --git a/docs/release_2.6.0/generate_docs.py b/docs/release_2.6.0/generate_docs.py
new file mode 100644
index 00000000..96d95698
--- /dev/null
+++ b/docs/release_2.6.0/generate_docs.py
@@ -0,0 +1,455 @@
+import ast
+import json
+import os
+import pathlib
+import re
+import shutil
+import textwrap
+from contextlib import contextmanager
+from importlib import import_module
+from os import environ as env
+from os.path import join
+from tempfile import TemporaryDirectory
+from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
+
+import semver
+import yaml # type: ignore
+from ast_to_xml import module_source
+from git import Repo
+from jinja2 import Template
+
+from mqtt_io.types import ConfigType
+
+GITHUB_REPO = "https://github.com/flyte/mqtt-io"
+
+WORKSPACE_DIR = pathlib.Path(__file__).parent.parent.absolute()
+
+CONFIG_SCHEMA_PATH = join(WORKSPACE_DIR, "mqtt_io/config/config.schema.yml")
+README_TEMPLATE = join(WORKSPACE_DIR, "README.md.j2")
+MODULES_DIR = join(WORKSPACE_DIR, "mqtt_io/modules")
+
+DOCS_SRC_DIR = join(WORKSPACE_DIR, "docs_src")
+
+DOCS_DIR = join(WORKSPACE_DIR, "docs")
+
+SIDEBAR_TEMPLATE = join(DOCS_SRC_DIR, "_sidebar.md.j2")
+CONTENT_TEMPLATE = join(DOCS_SRC_DIR, "config/reference.md.j2")
+MODULES_DOC_TEMPLATE = join(DOCS_SRC_DIR, "dev/modules/README.md.j2")
+VERSIONS_TEMPLATE = join(DOCS_SRC_DIR, "versions.md.j2")
+
+MAIN_INDEX = join(DOCS_DIR, "index.html")
+VERSIONS_FILE = join(DOCS_DIR, "versions.md")
+
+REF_ENTRIES: List[Dict[str, Any]] = []
+
+REPO = Repo(str(WORKSPACE_DIR))
+# REPO_WAS_DIRTY = REPO.is_dirty()
+
+
+def head() -> Any:
+ try:
+ ret = REPO.active_branch
+ except TypeError:
+ ret = next((tag for tag in REPO.tags if tag.commit == REPO.head.commit), None)
+ if ret is None:
+ ret = REPO.head
+ return ret
+
+
+HEAD = head()
+REF_NAME = str(HEAD)
+
+
+def get_build_dir() -> str:
+ docs_dir = join(DOCS_DIR, REF_NAME)
+ os.makedirs(docs_dir, exist_ok=True)
+ return docs_dir
+
+
+BUILD_DIR = get_build_dir()
+
+
+@contextmanager
+def gh_pages_branch() -> Iterator[None]:
+ previous_head = head()
+ repo_was_dirty = REPO.is_dirty()
+ if repo_was_dirty:
+ print("Stashing dirty repo...")
+ REPO.git.stash()
+ print("Checking out 'gh-pages'...")
+ REPO.heads["gh-pages"].checkout(force=True)
+ try:
+ yield
+ finally:
+ print(f"Checking out '{previous_head}'...")
+ REPO.git.checkout(REF_NAME)
+ if repo_was_dirty:
+ print("Popping stashed changes...")
+ REPO.git.stash("pop")
+
+
+def get_version_list() -> List[str]:
+ with gh_pages_branch():
+ return next(os.walk(DOCS_DIR))[1]
+
+
+def commit_to_gh_pages_branch(
+ docs_path: str, versions_contents: str, main_index_contents: Optional[str]
+) -> None:
+ with gh_pages_branch():
+ print("Pulling gh-pages branch...")
+ REPO.git.pull()
+ print("Writing versions file...")
+ with open(VERSIONS_FILE, "w") as versions_file:
+ versions_file.write(versions_contents)
+ if main_index_contents is not None:
+ print("Writing main index file...")
+ with open(MAIN_INDEX, "w") as main_index_file:
+ main_index_file.write(main_index_contents)
+ shutil.rmtree(BUILD_DIR)
+ shutil.copytree(docs_path, BUILD_DIR)
+ for path in (BUILD_DIR, MAIN_INDEX, VERSIONS_FILE):
+ print(f"Adding '{path}' to git index...")
+ REPO.index.add([path])
+ print("Committing...")
+ REPO.index.commit(f"Generate {REF_NAME} docs")
+ print("Pushing gh-pages branch to origin...")
+ REPO.remotes.origin.push()
+
+
+def copy_docs_src(docs_path: str) -> None:
+ shutil.copytree(DOCS_SRC_DIR, docs_path, dirs_exist_ok=True)
+
+
+def sort_semver_versions(versions: Set[str]) -> List[str]:
+ semver_versions = set()
+ for v in versions:
+ try:
+ semver_versions.add(semver.VersionInfo.parse(v))
+ except ValueError:
+ continue
+ return list(map(str, sorted(list(semver_versions), reverse=True)))
+
+
+def generate_main_index(versions: Set[str]) -> str:
+ semver_versions = sort_semver_versions(versions)
+ try:
+ highest_version = semver_versions[0]
+ except IndexError:
+ highest_version = REF_NAME
+ print(f"Generating main index to redirect to {highest_version}")
+ return f"""\
+
+
+
+
+
+
+
+ Redirecting to '{highest_version}' documentation version...
+
+
+
+
+
+"""
+
+
+def title_id(entry_name: str, parents: List[str]) -> str:
+ tid = ""
+ if parents:
+ tid += ("-".join(parents)) + "-"
+ tid += entry_name
+ return tid.replace("*", "star")
+
+
+class ConfigSchemaParser:
+ @staticmethod
+ def parse_schema_section(
+ section: ConfigType,
+ container: List[Dict[str, Any]],
+ parents: Optional[List[str]] = None,
+ ) -> None:
+ if parents is None:
+ parents = []
+ else:
+ parents = parents.copy()
+
+ child_schema = section.get("schema")
+ if child_schema:
+ parents.append("*")
+ ConfigSchemaParser.parse_schema_section(child_schema, container, parents)
+ return
+
+ for entry_name in section.keys():
+ entry: ConfigType = section[entry_name]
+ ConfigSchemaParser.parse_cerberus_section(
+ entry_name, entry, container, parents
+ )
+
+ @staticmethod
+ def parse_cerberus_section(
+ entry_name: str,
+ section: ConfigType,
+ container: List[Dict[str, Any]],
+ parents: List[str],
+ ) -> None:
+ parents = parents.copy()
+ child_schema = section.get("schema")
+
+ children: List[Dict[str, Any]] = []
+ toplevel_name = parents[0] if parents else entry_name
+
+ depth = len([x for x in parents if x != "*"])
+ tid = title_id(entry_name, parents)
+ section.setdefault("meta", {})["title_id"] = tid
+ path = f"config/reference/{toplevel_name}/"
+ if parents:
+ path += f"?id={tid}"
+ parents_str = ".".join(parents).replace("*", "*")
+ entry_str = entry_name.replace("*", "*")
+ subtitle = f"*{parents_str}*.**{entry_str}**" if parents else None
+
+ entry = dict(
+ toplevel_name=toplevel_name,
+ title=entry_name if entry_name != "*" else f"{parents[-1]}.*",
+ subtitle=subtitle,
+ entry_name=entry_name,
+ element_id=tid,
+ depth=depth,
+ path=path,
+ schema=section,
+ meta=section.get("meta", {}),
+ )
+ container.append(entry)
+ REF_ENTRIES.append(entry)
+
+ if child_schema:
+ parents.append(entry_name)
+ if "type" in child_schema:
+ ConfigSchemaParser.parse_cerberus_section(
+ "*", child_schema, children, parents
+ )
+ else:
+ ConfigSchemaParser.parse_schema_section(child_schema, children, parents)
+
+
+def generate_readmes(docs_path: str) -> None:
+ blacklist = ("__init__", "mock", "stdio")
+ modules_and_titles = (
+ ("gpio", "GPIO Modules"),
+ ("sensor", "Sensors"),
+ ("stream", "Streams"),
+ )
+ module_strings: Dict[str, Dict[str, str]] = {}
+ for module_type, title in modules_and_titles:
+ for file_name, ext in [
+ os.path.splitext(x) for x in os.listdir(join(MODULES_DIR, module_type))
+ ]:
+ if ext != ".py" or file_name in blacklist:
+ continue
+ with open(join(MODULES_DIR, module_type, file_name + ext)) as module_file:
+ parsed = ast.parse(module_file.read())
+ expr = parsed.body[0]
+ assert (
+ expr.lineno == 1
+ and isinstance(expr, ast.Expr)
+ and hasattr(expr, "value")
+ and isinstance(expr.value, ast.Constant)
+ and isinstance(expr.value.value, str)
+ ), f"The {module_type}.{file_name} module should have a docstring at the top"
+ module_strings.setdefault(title, {})[file_name] = expr.value.value.strip()
+
+ with open(README_TEMPLATE) as readme_template_file:
+ readme_template: Template = Template(readme_template_file.read())
+
+ ctx = dict(supported_hardware=module_strings, version=REF_NAME)
+
+ with open(join(WORKSPACE_DIR, "README.md"), "w") as readme_file:
+ readme_file.write(readme_template.render(dict(**ctx, repo=True)))
+
+ with open(join(docs_path, "README.md"), "w") as readme_file:
+ readme_file.write(readme_template.render(dict(**ctx, repo=False)))
+
+
+def generate_changelog(docs_path: str) -> None:
+ print("Copying changelog...")
+ shutil.copyfile(join(WORKSPACE_DIR, "CHANGELOG.md"), join(docs_path, "CHANGELOG.md"))
+
+
+def document_gpio_module() -> None:
+ # TODO: Tasks pending completion -@flyte at 07/03/2021, 11:19:04
+ # Continue writing this to document the modules in some way.
+ module = import_module("mqtt_io.modules.gpio.raspberrypi")
+ requirements = getattr(module, "REQUIREMENTS", None)
+ config_schema = getattr(module, "CONFIG_SCHEMA", None)
+ interrupt_support = getattr(module.GPIO, "INTERRUPT_SUPPORT", None)
+ pin_schema = getattr(module.GPIO, "PIN_SCHEMA", None)
+ input_schema = getattr(module.GPIO, "INPUT_SCHEMA", None)
+ output_schema = getattr(module.GPIO, "OUTPUT_SCHEMA", None)
+
+
+# def get_source(path: str) -> str:
+# module_path, member_path = re.match(r"([\w\.]+):?(.*)", path).groups()
+# module = import_module(module_path)
+# module_filepath = pathlib.Path(module.__file__)
+# url = f"https://github.com/flyte/mqtt-io/blob/develop/{module_filepath.relative_to(THIS_DIR)}"
+# if not member_path:
+# return f"[`{path}`]({url}):\n\n```python\n{inspect.getsource(module)}```"
+# target = module
+# for member in member_path.split("."):
+# target = getattr(target, member)
+# _, lineno = inspect.getsourcelines(target)
+# url += f"#L{lineno}"
+# return f"[`{path}`]({url}):\n\n```python\n{dedent(inspect.getsource(target))}```"
+
+
+def get_source(module_path: str, xpath: str, title: str) -> str:
+ module = import_module(module_path)
+ module_filepath = pathlib.Path(module.__file__)
+ src, attrib = module_source(module, xpath)[0]
+ url = "%s/blob/%s/%s#L%s" % (
+ GITHUB_REPO,
+ HEAD.commit.hexsha,
+ module_filepath.relative_to(WORKSPACE_DIR),
+ attrib["lineno"],
+ )
+ return f"[{title}]({url}):\n\n```python\n{src.rstrip()}\n```"
+
+
+def get_source_link(module_path: str, xpath: str, title: str) -> str:
+ module = import_module(module_path)
+ module_filepath = pathlib.Path(module.__file__)
+ _, attrib = module_source(module, xpath)[0]
+ url = "%s/blob/%s/%s#L%s" % (
+ GITHUB_REPO,
+ HEAD.commit.hexsha,
+ module_filepath.relative_to(WORKSPACE_DIR),
+ attrib["lineno"],
+ )
+ return f"[{title}]({url}):"
+
+
+def get_source_raw(
+ module_path: str, xpath: str, until_xpath: Optional[str] = None, dedent: bool = False
+) -> str:
+ module = import_module(module_path)
+ src, _ = module_source(module, xpath, until_xpath=until_xpath, dedent=dedent)[0]
+ return src
+
+
+def get_sources_raw(
+ sources_spec: List[Tuple[str, str, Optional[str]]], dedent: bool = False
+) -> str:
+ src = "\n".join(get_source_raw(*x, dedent=False) for x in sources_spec)
+ if dedent:
+ src = textwrap.dedent(src)
+ return src
+
+
+def generate_modules_doc(docs_path: str) -> None:
+ ctx = dict(
+ source=get_source,
+ source_link=get_source_link,
+ source_raw=get_source_raw,
+ sources_raw=get_sources_raw,
+ )
+
+ print("Loading modules doc template...")
+ with open(MODULES_DOC_TEMPLATE) as modules_doc_template_file:
+ modules_doc_template: Template = Template(modules_doc_template_file.read())
+
+ print("Generating modules doc...")
+ modules_dir = join(docs_path, "dev/modules")
+ os.makedirs(modules_dir, exist_ok=True)
+ with open(join(modules_dir, "README.md"), "w") as readme_file:
+ readme_file.write(modules_doc_template.render(ctx))
+
+
+def generate_versions(versions: Set[str]) -> str:
+ release_versions = sort_semver_versions(versions)
+ other_versions_set = set()
+ for version in versions:
+ if version not in release_versions:
+ other_versions_set.add(version)
+ other_versions: List[str] = sorted(list(other_versions_set))
+
+ ctx = dict(releases=release_versions, other_versions=other_versions)
+
+ with open(VERSIONS_TEMPLATE) as versions_template_file:
+ versions_template: Template = Template(versions_template_file.read())
+
+ return versions_template.render(ctx)
+
+
+def generate_docs(docs_path: str) -> None:
+ print(f"Loading YAML config schema from '{CONFIG_SCHEMA_PATH}'...")
+ with open(CONFIG_SCHEMA_PATH, "r") as config_schema_file:
+ config_schema: ConfigType = yaml.safe_load(config_schema_file)
+
+ print(f"Loading sidebar template from '{SIDEBAR_TEMPLATE}'...")
+ with open(SIDEBAR_TEMPLATE, "r") as sidebar_template_file:
+ sidebar_template: Template = Template(sidebar_template_file.read())
+
+ print(f"Loading content template from '{CONTENT_TEMPLATE}'...")
+ with open(CONTENT_TEMPLATE, "r") as content_template_file:
+ content_template: Template = Template(content_template_file.read())
+
+ top_level_section_names: List[str] = list(config_schema.keys())
+ ConfigSchemaParser.parse_schema_section(config_schema, [])
+
+ versions = set(get_version_list())
+ versions.add(REF_NAME)
+
+ copy_docs_src(docs_path)
+
+ main_sidebar_path = join(docs_path, "_sidebar.md")
+ print(f"Writing main sidebar file '{main_sidebar_path}'...")
+ with open(main_sidebar_path, "w") as main_sidebar_file:
+ main_sidebar_file.write(
+ sidebar_template.render(
+ dict(ref_sections=[x for x in REF_ENTRIES if x["depth"] == 0])
+ )
+ )
+
+ for tl_section in top_level_section_names:
+ section_path = join(docs_path, f"config/reference/{tl_section}")
+ md_path = join(section_path, "README.md")
+
+ print(f"Making directory (if not exists) '{section_path}'...")
+ os.makedirs(section_path, exist_ok=True)
+
+ print(f"Making section markdown file '{md_path}'...")
+ with open(md_path, "w") as md_file:
+ md_file.write(
+ content_template.render(
+ dict(ref_sections=REF_ENTRIES, section=tl_section)
+ )
+ )
+
+ json_schema_path = join(docs_path, "schema.json")
+ print(f"Making JSON config schema file '{json_schema_path}'...")
+ with open(json_schema_path, "w") as json_schema_file:
+ json.dump(config_schema, json_schema_file, indent=2)
+
+ # generate_module_docs()
+ generate_readmes(docs_path)
+ generate_changelog(docs_path)
+ generate_modules_doc(docs_path)
+
+ versions_contents = generate_versions(versions)
+ main_index_contents = generate_main_index(versions)
+
+ commit_to_gh_pages_branch(docs_path, versions_contents, main_index_contents)
+
+
+def main() -> None:
+ with TemporaryDirectory() as tempdir:
+ generate_docs(tempdir)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs/release_2.6.0/index.html b/docs/release_2.6.0/index.html
new file mode 100644
index 00000000..b82747f0
--- /dev/null
+++ b/docs/release_2.6.0/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/release_2.6.0/schema.json b/docs/release_2.6.0/schema.json
new file mode 100644
index 00000000..4f2cde88
--- /dev/null
+++ b/docs/release_2.6.0/schema.json
@@ -0,0 +1,998 @@
+{
+ "mqtt": {
+ "meta": {
+ "description": "Contains the configuration data used for connecting to an MQTT server.\n",
+ "yaml_example": "mqtt:\n host: test.mosquitto.org\n port: 8883\n topic_prefix: mqtt_io\n ha_discovery:\n enabled: yes\n tls:\n enabled: yes\n ca_certs: mosquitto.org.crt\n certfile: client.crt\n keyfile: client.key\n",
+ "title_id": "mqtt"
+ },
+ "type": "dict",
+ "required": true,
+ "schema": {
+ "host": {
+ "meta": {
+ "description": "Host name or IP address of the MQTT server.",
+ "title_id": "mqtt-host"
+ },
+ "type": "string",
+ "empty": false,
+ "required": true
+ },
+ "port": {
+ "meta": {
+ "description": "Port number to connect to on the MQTT server.",
+ "title_id": "mqtt-port"
+ },
+ "type": "integer",
+ "min": 1,
+ "max": 65535,
+ "required": false,
+ "default": 1883
+ },
+ "user": {
+ "meta": {
+ "description": "Username to authenticate with on the MQTT server.",
+ "title_id": "mqtt-user"
+ },
+ "type": "string",
+ "required": false,
+ "default": ""
+ },
+ "password": {
+ "meta": {
+ "description": "Password to authenticate with on the MQTT server.",
+ "title_id": "mqtt-password"
+ },
+ "type": "string",
+ "required": false,
+ "default": ""
+ },
+ "client_id": {
+ "meta": {
+ "description": "[MQTT client ID](https://www.cloudmqtt.com/blog/2018-11-21-mqtt-what-is-client-id.html) to use on the MQTT server.\n",
+ "title_id": "mqtt-client_id"
+ },
+ "type": "string",
+ "required": false,
+ "default": ""
+ },
+ "topic_prefix": {
+ "meta": {
+ "description": "Prefix to use for all topics.",
+ "extra_info": "For example, a `topic_prefix` of `home/livingroom` would make a digital input\ncalled \"doorbell\" publish its changes to the `home/livingroom/input/doorbell`\ntopic.\n",
+ "title_id": "mqtt-topic_prefix"
+ },
+ "type": "string",
+ "required": false,
+ "default": "",
+ "coerce": "rstrip_slash"
+ },
+ "clean_session": {
+ "meta": {
+ "description": "Whether or not to start a\n[clean MQTT session](https://www.hivemq.com/blog/mqtt-essentials-part-7-persistent-session-queuing-messages/)\non every MQTT connection.\n",
+ "title_id": "mqtt-clean_session"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "protocol": {
+ "meta": {
+ "description": "Version of the MQTT protocol to use.",
+ "extra_info": "This renders in the documentation as a float, but should always be set within quotes.\n",
+ "title_id": "mqtt-protocol"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "coerce": "tostring",
+ "default": "3.1.1",
+ "allowed": [
+ "3.1",
+ "3.1.1"
+ ]
+ },
+ "keepalive": {
+ "meta": {
+ "description": "How frequently in seconds to send\n[ping packets](https://www.hivemq.com/blog/mqtt-essentials-part-10-alive-client-take-over/)\nto the MQTT server.\n",
+ "unit": "seconds",
+ "title_id": "mqtt-keepalive"
+ },
+ "type": "integer",
+ "min": 1,
+ "required": false,
+ "default": 10
+ },
+ "status_topic": {
+ "meta": {
+ "description": "Topic on which to send messages about the running status of this software.",
+ "extra_info": "Sends the payloads configured in `status_payload_running`,\n`status_payload_stopped` and `status_payload_dead`.\n",
+ "title_id": "mqtt-status_topic"
+ },
+ "type": "string",
+ "required": false,
+ "default": "status"
+ },
+ "status_payload_running": {
+ "meta": {
+ "description": "Payload to send on the status topic when the software is running.",
+ "title_id": "mqtt-status_payload_running"
+ },
+ "type": "string",
+ "required": false,
+ "default": "running"
+ },
+ "status_payload_stopped": {
+ "meta": {
+ "description": "Payload to send on the status topic when the software has exited cleanly.",
+ "title_id": "mqtt-status_payload_stopped"
+ },
+ "type": "string",
+ "required": false,
+ "default": "stopped"
+ },
+ "status_payload_dead": {
+ "meta": {
+ "description": "Payload to send on the status topic when the software has exited unexpectedly.",
+ "extra_info": "Uses [MQTT Last Will and Testament](https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament/)\nto make the server automatically send this payload if our connection fails.\n",
+ "title_id": "mqtt-status_payload_dead"
+ },
+ "type": "string",
+ "required": false,
+ "default": "dead"
+ },
+ "client_module": {
+ "meta": {
+ "description": "MQTT Client implementation module path.",
+ "extra_info": "There's currently only one implementation, which uses the\n[aiomqtt](https://github.com/sbtinstruments/aiomqtt/) client.\n",
+ "title_id": "mqtt-client_module"
+ },
+ "type": "string",
+ "required": false,
+ "default": "mqtt_io.mqtt.aiomqtt"
+ },
+ "ha_discovery": {
+ "type": "dict",
+ "required": false,
+ "schema": {
+ "enabled": {
+ "meta": {
+ "description": "Enable [Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)\nof our configured devices.\n",
+ "title_id": "mqtt-ha_discovery-enabled"
+ },
+ "type": "boolean",
+ "required": true
+ },
+ "prefix": {
+ "meta": {
+ "description": "Prefix for the Home Assistant MQTT discovery topic.",
+ "title_id": "mqtt-ha_discovery-prefix"
+ },
+ "type": "string",
+ "required": false,
+ "default": "homeassistant",
+ "coerce": "rstrip_slash"
+ },
+ "name": {
+ "meta": {
+ "description": "Name to identify this \"device\" in Home Assistant.",
+ "title_id": "mqtt-ha_discovery-name"
+ },
+ "type": "string",
+ "required": false,
+ "default": "MQTT IO"
+ }
+ },
+ "meta": {
+ "title_id": "mqtt-ha_discovery"
+ }
+ },
+ "tls": {
+ "meta": {
+ "description": "TLS/SSL settings for connecting to the MQTT server over an encrypted connection.\n",
+ "yaml_example": "mqtt:\n host: localhost\n tls:\n enabled: yes\n ca_certs: mosquitto.org.crt\n certfile: client.crt\n keyfile: client.key\n",
+ "title_id": "mqtt-tls"
+ },
+ "type": "dict",
+ "required": false,
+ "schema": {
+ "enabled": {
+ "meta": {
+ "description": "Enable a secure connection to the MQTT server.",
+ "extra_info": "Most of these options map directly to the\n[`tls_set()` arguments](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)\non the Paho MQTT client.\n",
+ "title_id": "mqtt-tls-enabled"
+ },
+ "type": "boolean",
+ "required": true
+ },
+ "ca_certs": {
+ "meta": {
+ "description": "Path to the Certificate Authority certificate files that are to be treated\nas trusted by this client.\n[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)\n",
+ "title_id": "mqtt-tls-ca_certs"
+ },
+ "type": "string",
+ "required": false
+ },
+ "certfile": {
+ "meta": {
+ "description": "Path to the PEM encoded client certificate.\n[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)\n",
+ "title_id": "mqtt-tls-certfile"
+ },
+ "type": "string",
+ "required": false
+ },
+ "keyfile": {
+ "meta": {
+ "description": "Path to the PEM encoded client private key.\n[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)\n",
+ "title_id": "mqtt-tls-keyfile"
+ },
+ "type": "string",
+ "required": false
+ },
+ "cert_reqs": {
+ "meta": {
+ "description": "Defines the certificate requirements that the client imposes on the MQTT server.\n[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)\n",
+ "extra_info": "By default this is `CERT_REQUIRED`, which means that the broker must provide a certificate.\n",
+ "title_id": "mqtt-tls-cert_reqs"
+ },
+ "type": "string",
+ "required": false,
+ "allowed": [
+ "CERT_NONE",
+ "CERT_OPTIONAL",
+ "CERT_REQUIRED"
+ ],
+ "default": "CERT_REQUIRED"
+ },
+ "tls_version": {
+ "meta": {
+ "description": "Specifies the version of the SSL/TLS protocol to be used.\n[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)\n",
+ "extra_info": "By default the highest TLS version is detected.\n",
+ "title_id": "mqtt-tls-tls_version"
+ },
+ "type": "string",
+ "required": false
+ },
+ "ciphers": {
+ "meta": {
+ "description": "Which encryption ciphers are allowable for this connection.\n[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-set)\n",
+ "title_id": "mqtt-tls-ciphers"
+ },
+ "type": "string",
+ "required": false
+ },
+ "insecure": {
+ "meta": {
+ "description": "Configure verification of the server hostname in the server certificate.\n[More info](https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#tls-insecure-set)\n",
+ "extra_info": "If set to true, it is impossible to guarantee that the host you are\nconnecting to is not impersonating your server. This can be useful in\ninitial server testing, but makes it possible for a malicious third party\nto impersonate your server through DNS spoofing, for example.\nDo not use this function in a real system. Setting value to true means there\nis no point using encryption.\n",
+ "title_id": "mqtt-tls-insecure"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ }
+ }
+ },
+ "reconnect_delay": {
+ "meta": {
+ "description": "Time in seconds to wait between reconnect attempts.\n",
+ "title_id": "mqtt-reconnect_delay"
+ },
+ "type": "integer",
+ "required": false,
+ "default": 2,
+ "min": 1
+ },
+ "reconnect_count": {
+ "meta": {
+ "description": "Max number of retries of connections before giving up and exiting.\nNull value means infinite reconnects (default).\nThe counter is reset when the connection is reestablished successfully.\n",
+ "title_id": "mqtt-reconnect_count"
+ },
+ "type": "integer",
+ "required": false,
+ "default": null,
+ "nullable": true,
+ "min": 0
+ }
+ }
+ },
+ "gpio_modules": {
+ "meta": {
+ "description": "List of GPIO modules to configure for use with inputs and/or outputs.\n",
+ "extra_info": "Some modules require extra config entries, specified by the modules themselves.\nUntil the documentation is written for the individual modules, please refer to the\n`CONFIG_SCHEMA` value of the module's code in\n[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).\nTODO: Link this to the pending wiki pages on each module's requirements.\n",
+ "yaml_example": "gpio_modules:\n - name: rpi_gpio\n module: raspberrypi\n \n - name: pcf\n module: pcf8574\n i2c_bus_num: 1\n chip_addr: 0x20\n",
+ "title_id": "gpio_modules"
+ },
+ "type": "list",
+ "required": false,
+ "default": [],
+ "schema": {
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "name": {
+ "meta": {
+ "description": "Your name for this configuration of the module. Will be referred to by entries\nin the `digital_inputs` and/or `digital_outputs` sections.\n",
+ "title_id": "gpio_modules-star-name"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "module": {
+ "meta": {
+ "description": "Name of the module in the code. This is listed in the README's\n\"Supported Hardware\" section in brackets.\n",
+ "title_id": "gpio_modules-star-module"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "cleanup": {
+ "meta": {
+ "description": "Whether to run the module's `cleanup()` method on exit.",
+ "title_id": "gpio_modules-star-cleanup"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": true
+ }
+ },
+ "meta": {
+ "title_id": "gpio_modules-star"
+ }
+ }
+ },
+ "sensor_modules": {
+ "meta": {
+ "description": "List of sensor modules to configure for use with sensor inputs.",
+ "extra_info": "Some modules require extra config entries, specified by the modules themselves.\nUntil the documentation is written for the individual modules, please refer to the\n`CONFIG_SCHEMA` value of the module's code in\n[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).\nTODO: Link this to the pending wiki pages on each module's requirements.\n",
+ "yaml_example": "sensor_modules:\n - name: dht\n module: dht22\n type: AM2302\n pin: 4\n \n - name: ds\n module: ds18b\n type: DS18S20\n address: 000803702e49\n",
+ "title_id": "sensor_modules"
+ },
+ "type": "list",
+ "required": false,
+ "default": [],
+ "schema": {
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "name": {
+ "meta": {
+ "description": "Your name for this configuration of the module. Will be referred to by entries\nin the `sensor_inputs` section.\n",
+ "title_id": "sensor_modules-star-name"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "module": {
+ "meta": {
+ "description": "Name of the module in the code. This is listed in the README's\n\"Supported Hardware\" section in brackets.\n",
+ "title_id": "sensor_modules-star-module"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "cleanup": {
+ "meta": {
+ "description": "Whether to run the module's `cleanup()` method on exit.",
+ "title_id": "sensor_modules-star-cleanup"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": true
+ }
+ },
+ "meta": {
+ "title_id": "sensor_modules-star"
+ }
+ }
+ },
+ "stream_modules": {
+ "meta": {
+ "description": "List of stream modules to configure.",
+ "extra_info": "Some modules require extra config entries, specified by the modules themselves.\nUntil the documentation is written for the individual modules, please refer to the\n`CONFIG_SCHEMA` value of the module's code in\n[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).\nTODO: Link this to the pending wiki pages on each module's requirements.\n",
+ "yaml_example": "stream_modules:\n - name: network_switch\n module: serial\n device: /dev/ttyUSB1\n baud: 115200\n interval: 10\n\n - name: ups\n module: serial\n type: /dev/ttyUSB0\n baud: 9600\n interval: 1\n",
+ "title_id": "stream_modules"
+ },
+ "type": "list",
+ "required": false,
+ "default": [],
+ "schema": {
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "name": {
+ "meta": {
+ "description": "Your name for this configuration of the module. Will be used in the topic on\nwhich the stream's data is published and the topic on which messages can be\nsent for writing to the stream.\n",
+ "title_id": "stream_modules-star-name"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "module": {
+ "meta": {
+ "description": "Name of the module in the code. This is listed in the README's\n\"Supported Hardware\" section in brackets.\n",
+ "title_id": "stream_modules-star-module"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "cleanup": {
+ "meta": {
+ "description": "Whether to run the module's `cleanup()` method on exit.",
+ "title_id": "stream_modules-star-cleanup"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": true
+ },
+ "retain": {
+ "meta": {
+ "description": "Whether to set the `retain` flag on MQTT messages publishing data received\nfrom the stream.\n",
+ "title_id": "stream_modules-star-retain"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "read_interval": {
+ "meta": {
+ "description": "How long to wait between polling the stream for new data.",
+ "unit": "seconds",
+ "title_id": "stream_modules-star-read_interval"
+ },
+ "type": "float",
+ "required": false,
+ "default": 60,
+ "min": 0.01
+ },
+ "read": {
+ "meta": {
+ "description": "Whether to poll this stream for incoming data and publish it on an MQTT topic.\n",
+ "title_id": "stream_modules-star-read"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": true
+ },
+ "write": {
+ "meta": {
+ "description": "Whether to subscribe to MQTT messages on a topic and write messages received on it to the stream.",
+ "title_id": "stream_modules-star-write"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": true
+ }
+ },
+ "meta": {
+ "title_id": "stream_modules-star"
+ }
+ }
+ },
+ "digital_inputs": {
+ "meta": {
+ "description": "List of digital inputs to configure.",
+ "extra_info": "Some modules require extra config entries, specified by the modules themselves.\nUntil the documentation is written for the individual modules, please refer to the\n`PIN_SCHEMA` and `INPUT_SCHEMA` values of the module's code in\n[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).\nTODO: Link this to the pending wiki pages on each module's requirements.\n",
+ "yaml_example": "gpio_modules:\n - name: rpi\n module: raspberrypi\n\ndigital_inputs:\n - name: gpio0\n module: rpi\n pin: 0\n\n - name: gpio1\n module: rpi\n pin: 1\n",
+ "title_id": "digital_inputs"
+ },
+ "type": "list",
+ "required": false,
+ "default": [],
+ "schema": {
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "name": {
+ "meta": {
+ "description": "Name of the input. Used in the MQTT topic when publishing input changes.\n\nThe topic that input changes will be published to is:\n`/input/`\n",
+ "title_id": "digital_inputs-star-name"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "module": {
+ "meta": {
+ "description": "Name of the module configured in `gpio_modules` that this input is attached to.\n",
+ "title_id": "digital_inputs-star-module"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "pin": {
+ "meta": {
+ "description": "Which of the GPIO module's pins this input refers to.",
+ "extra_info": "Depending on the GPIO module's implementation, this can be either a string\nor an integer.\n",
+ "title_id": "digital_inputs-star-pin"
+ },
+ "type": [
+ "string",
+ "integer"
+ ],
+ "required": true,
+ "empty": false
+ },
+ "on_payload": {
+ "meta": {
+ "description": "Payload to be sent when the input changes to what is considered to be \"on\".\nSee `inverted` below for the definition of \"on\" and \"off\".\n",
+ "extra_info": "Make sure to avoid YAML's automatic boolean type conversion when setting this\noption by surrounding potential booleans with quotes.\nSee the \"Regexp\" section of the\n[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that\nwill be parsed as boolean.\n",
+ "title_id": "digital_inputs-star-on_payload"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "default": "ON"
+ },
+ "off_payload": {
+ "meta": {
+ "description": "Payload to be sent when the input changes to what is considered to be \"off\".\nSee `inverted` below for the definition of \"on\" and \"off\".\n",
+ "extra_info": "Make sure to avoid YAML's automatic boolean type conversion when setting this\noption by surrounding potential booleans with quotes.\nSee the \"Regexp\" section of the\n[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that\nwill be parsed as boolean.\n",
+ "title_id": "digital_inputs-star-off_payload"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "default": "OFF"
+ },
+ "inverted": {
+ "meta": {
+ "description": "Invert the logic level so that \"low\" levels are considered to be \"on\" and\n\"high\" levels are considered \"off\".\n",
+ "extra_info": "This can be useful for when an input is pulled \"high\" with a resistor and a\ndevice (like a button or another IC) connects it to ground when it's \"active\".\n",
+ "title_id": "digital_inputs-star-inverted"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "pullup": {
+ "meta": {
+ "description": "Enable the pull-up resistor for this input so that the logic level is pulled\n\"high\" by default.\n",
+ "extra_info": "Not all GPIO modules support pull-up resistors.",
+ "title_id": "digital_inputs-star-pullup"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "pulldown": {
+ "meta": {
+ "description": "Enable the pull-down resistor for this input so that the logic level is pulled\n\"low\" by default.\n",
+ "extra_info": "Not all GPIO modules support pull-down resistors.",
+ "title_id": "digital_inputs-star-pulldown"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "interrupt": {
+ "meta": {
+ "description": "Configure this pin to trigger an interrupt when the logic level is \"rising\",\n\"falling\" or \"both\".\n",
+ "extra_info": "Not all GPIO modules support interrupts, and those that do may do so in\nvarious ways.\nTODO: Add link to interrupt documentation.\n",
+ "title_id": "digital_inputs-star-interrupt"
+ },
+ "type": "string",
+ "required": false,
+ "allowed": [
+ "rising",
+ "falling",
+ "both"
+ ]
+ },
+ "interrupt_for": {
+ "meta": {
+ "description": "List of other pin names that this pin is an interrupt for.\n\nThis is generally used on GPIO modules that provide software callbacks on\ninterrupts, so that we can attach another \"remote\" module's interrupt output\npin (one that changes logic level when one of its pins triggers an interrupt)\nto this input and use the callback to get the value of the \"remote\" pin and\npublish it on MQTT.\n\nTODO: Add link to interrupt documentation.\n",
+ "title_id": "digital_inputs-star-interrupt_for"
+ },
+ "type": "list",
+ "required": false,
+ "minlength": 1,
+ "schema": {
+ "type": "string",
+ "required": true,
+ "meta": {
+ "title_id": "digital_inputs-star-interrupt_for-star"
+ }
+ }
+ },
+ "bouncetime": {
+ "meta": {
+ "description": "Don't trigger interrupts more frequently than once per `bouncetime`.\n",
+ "unit": "milliseconds",
+ "title_id": "digital_inputs-star-bouncetime"
+ },
+ "type": "integer",
+ "required": false,
+ "default": 100,
+ "min": 1
+ },
+ "retain": {
+ "meta": {
+ "description": "Set the retain flag on MQTT messages published on input change.",
+ "title_id": "digital_inputs-star-retain"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "poll_interval": {
+ "meta": {
+ "description": "How long to wait between checking the value of this input.",
+ "unit": "seconds",
+ "extra_info": "When the pin is configured as an interrupt, the pin is no longer polled.\nThe only exception to this is if the pin is configured as an interrupt for\nanother pin. In this case, whether or not we poll is decided by the\n`poll_when_interrupt_for` setting below.\n",
+ "title_id": "digital_inputs-star-poll_interval"
+ },
+ "type": "float",
+ "required": false,
+ "default": 0.1
+ },
+ "poll_when_interrupt_for": {
+ "meta": {
+ "description": "Poll this pin when it's configured as an interrupt for another pin.",
+ "extra_info": "Polling the pin when it's configured as an interrupt for another pin is useful\nin order to make sure that if we somehow miss an interrupt on this pin (the\nremote module's interrupt output pin goes low (\"triggered\")), we\ndon't end up stuck in that state where we don't handle the remote module's\ninterrupt at all. If we poll the \"triggered\" value on this pin and our\ninterrupt handling hasn't dealt with it, then we'll handle it here.\n",
+ "title_id": "digital_inputs-star-poll_when_interrupt_for"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": true
+ },
+ "ha_discovery": {
+ "meta": {
+ "description": "Configures the\n[Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)\nfor this pin.\n\nAny values entered into this section will be sent as part of the discovery\nconfig payload. See the above link for documentation.\n",
+ "yaml_example": "digital_inputs:\n - name: livingroom_motion\n module: rpi\n ha_discovery:\n component: binary_sensor\n name: Living Room Motion\n device_class: motion\n",
+ "title_id": "digital_inputs-star-ha_discovery"
+ },
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "component": {
+ "meta": {
+ "description": "Type of component to report this input as to Home Assistant.",
+ "title_id": "digital_inputs-star-ha_discovery-component"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "default": "binary_sensor"
+ }
+ }
+ }
+ },
+ "meta": {
+ "title_id": "digital_inputs-star"
+ }
+ }
+ },
+ "digital_outputs": {
+ "meta": {
+ "description": "List of digital outputs to configure.",
+ "extra_info": "Some modules require extra config entries, specified by the modules themselves.\nUntil the documentation is written for the individual modules, please refer to the\n`PIN_SCHEMA` and `OUTPUT_SCHEMA` values of the module's code in\n[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).\nTODO: Link this to the pending wiki pages on each module's requirements.\n",
+ "yaml_example": "gpio_modules:\n - name: rpi\n module: raspberrypi\n\ndigital_outputs:\n - name: gpio0\n module: rpi\n pin: 0\n\n - name: gpio1\n module: rpi\n pin: 1\n",
+ "title_id": "digital_outputs"
+ },
+ "type": "list",
+ "required": false,
+ "default": [],
+ "schema": {
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "name": {
+ "meta": {
+ "description": "Name of the output. Used in the MQTT topics that are subscribed to in order to\nchange the output value according to received MQTT messages, as well as in the\nMQTT topic for publishing output changes.\n\nThe topics subscribed to for each output are:\n- `/output//set`\n- `/output//set_on_ms`\n- `/output//set_off_ms`\n\nThe topic that output changes will be published to is:\n`/output/`\n",
+ "title_id": "digital_outputs-star-name"
+ },
+ "type": "string",
+ "required": true
+ },
+ "module": {
+ "meta": {
+ "description": "Name of the module configured in `gpio_modules` that this output is attached to.\n",
+ "title_id": "digital_outputs-star-module"
+ },
+ "type": "string",
+ "required": true
+ },
+ "pin": {
+ "meta": {
+ "description": "Which of the GPIO module's pins this output refers to.",
+ "extra_info": "Depending on the GPIO module's implementation, this can be either a string\nor an integer.\n",
+ "title_id": "digital_outputs-star-pin"
+ },
+ "type": [
+ "string",
+ "integer"
+ ],
+ "required": true,
+ "empty": false
+ },
+ "on_payload": {
+ "meta": {
+ "description": "Payload to consider as \"on\" when received to the `/set` topic for this output.\nSee `inverted` below for the definition of \"on\" and \"off\".\n",
+ "extra_info": "Make sure to avoid YAML's automatic boolean type conversion when setting this\noption by surrounding potential booleans with quotes.\nSee the \"Regexp\" section of the\n[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that\nwill be parsed as boolean.\n",
+ "title_id": "digital_outputs-star-on_payload"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "default": "ON"
+ },
+ "off_payload": {
+ "meta": {
+ "description": "Payload to consider as \"off\" when received to the `/set` topic for this output.\nSee `inverted` below for the definition of \"on\" and \"off\".\n",
+ "extra_info": "Make sure to avoid YAML's automatic boolean type conversion when setting this\noption by surrounding potential booleans with quotes.\nSee the \"Regexp\" section of the\n[YAML bool docs](https://yaml.org/type/bool.html) for all of the values that\nwill be parsed as boolean.\n",
+ "title_id": "digital_outputs-star-off_payload"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "default": "OFF"
+ },
+ "inverted": {
+ "meta": {
+ "description": "Invert the logic level so that \"low\" levels are considered to be \"on\" and\n\"high\" levels are considered \"off\".\n",
+ "extra_info": "This can be useful for when an output turns something on when its output is\n\"low\".\n",
+ "title_id": "digital_outputs-star-inverted"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "timed_set_ms": {
+ "meta": {
+ "description": "How long to set an output to the desired value on receipt of an MQTT message\nto the `/set` topic before then setting it back to the opposite value.\n",
+ "unit": "milliseconds",
+ "extra_info": "This may be useful if the output controls a device where leaving the ouput\n\"on\" for too long would be detrimental. Using this option means that you don't\nhave to rely on a second \"off\" message getting through MQTT for the output to\nreturn to a safe state.\n",
+ "title_id": "digital_outputs-star-timed_set_ms"
+ },
+ "type": "integer",
+ "required": false,
+ "empty": true
+ },
+ "initial": {
+ "meta": {
+ "description": "Set the output to an initial \"high\" or \"low\" state when the software starts.\n",
+ "title_id": "digital_outputs-star-initial"
+ },
+ "type": "string",
+ "required": false,
+ "allowed": [
+ "high",
+ "low"
+ ]
+ },
+ "publish_initial": {
+ "meta": {
+ "description": "Whether to publish an MQTT message for the initial \"high\" or \"low\" state set\nabove.\n",
+ "title_id": "digital_outputs-star-publish_initial"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "retain": {
+ "meta": {
+ "description": "Set the retain flag on MQTT messages published on output change.",
+ "title_id": "digital_outputs-star-retain"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "ha_discovery": {
+ "meta": {
+ "description": "Configures the\n[Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)\nfor this pin.\n\nAny values entered into this section will be sent as part of the discovery\nconfig payload. See the above link for documentation.\n",
+ "yaml_example": "digital_outputs:\n - name: garage_door1\n module: rpi\n ha_discovery:\n component: switch\n name: Ferrari Garage Door\n device_class: garage_door\n",
+ "title_id": "digital_outputs-star-ha_discovery"
+ },
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "component": {
+ "meta": {
+ "description": "Type of component to report this output as to Home Assistant.",
+ "title_id": "digital_outputs-star-ha_discovery-component"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "default": "switch"
+ }
+ }
+ }
+ },
+ "meta": {
+ "title_id": "digital_outputs-star"
+ }
+ }
+ },
+ "sensor_inputs": {
+ "meta": {
+ "description": "List of sensor inputs to configure.",
+ "extra_info": "Some modules require extra config entries, specified by the modules themselves.\nUntil the documentation is written for the individual modules, please refer to the\n`MODULE_SCHEMA` values of the module's code in\n[the repository](https://github.com/flyte/pi-mqtt-gpio/tree/feature/asyncio/mqtt_io/modules).\nTODO: Link this to the pending wiki pages on each module's requirements.\n",
+ "yaml_example": "sensor_modules:\n - name: dht\n module: dht22\n type: AM2302\n pin: 4\n\nsensor_inputs:\n - name: workshop_temp\n module: dht\n type: temperature\n interval: 30\n\n - name: workshop_humidity\n module: dht\n type: humidity\n interval: 60\n",
+ "title_id": "sensor_inputs"
+ },
+ "type": "list",
+ "required": false,
+ "default": [],
+ "schema": {
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "name": {
+ "meta": {
+ "description": "Name of the sensor. Used in the MQTT topic when publishing sensor values.\n\nThe topic that sensor values will be published to is:\n`/sensor/`\n",
+ "title_id": "sensor_inputs-star-name"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "module": {
+ "meta": {
+ "description": "Name of the module configured in `sensor_modules` that this sensor reading\ncomes from.\n",
+ "title_id": "sensor_inputs-star-module"
+ },
+ "type": "string",
+ "required": true,
+ "empty": false
+ },
+ "retain": {
+ "meta": {
+ "description": "Set the retain flag on MQTT messages published on sensor read.",
+ "title_id": "sensor_inputs-star-retain"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": false
+ },
+ "interval": {
+ "meta": {
+ "description": "How long to wait between checking the value of this sensor.",
+ "unit": "seconds",
+ "title_id": "sensor_inputs-star-interval"
+ },
+ "type": "integer",
+ "required": false,
+ "default": 60,
+ "min": 1
+ },
+ "digits": {
+ "meta": {
+ "description": "How many decimal places to round the sensor reading to.",
+ "title_id": "sensor_inputs-star-digits"
+ },
+ "type": "integer",
+ "required": false,
+ "default": 2,
+ "min": 0
+ },
+ "ha_discovery": {
+ "meta": {
+ "description": "Configures the\n[Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/)\nfor this sensor.\n\nAny values entered into this section will be sent as part of the discovery\nconfig payload. See the above link for documentation.\n",
+ "yaml_example": "sensor_inputs:\n - name: workshop_temp\n module: dht\n type: temperature\n ha_discovery:\n name: Workshop Temperature\n device_class: temperature\n\n - name: workshop_humidity\n module: dht\n type: humidity\n ha_discovery:\n name: Workshop Humidity\n device_class: humidity\n",
+ "title_id": "sensor_inputs-star-ha_discovery"
+ },
+ "type": "dict",
+ "allow_unknown": true,
+ "schema": {
+ "component": {
+ "meta": {
+ "description": "Type of component to report this sensor as to Home Assistant.",
+ "title_id": "sensor_inputs-star-ha_discovery-component"
+ },
+ "type": "string",
+ "required": false,
+ "empty": false,
+ "default": "sensor"
+ },
+ "expire_after": {
+ "meta": {
+ "description": "How long after receiving a sensor update to declare it invalid.",
+ "extra_info": "Defaults to `interval` * 2 + 5\n",
+ "title_id": "sensor_inputs-star-ha_discovery-expire_after"
+ },
+ "type": "integer",
+ "required": false,
+ "min": 1
+ }
+ }
+ }
+ },
+ "meta": {
+ "title_id": "sensor_inputs-star"
+ }
+ }
+ },
+ "logging": {
+ "meta": {
+ "description": "Config to pass directly to\n[Python's logging module](https://docs.python.org/3/library/logging.config.html#logging-config-dictschema)\nto influence the logging output of the software.\n",
+ "title_id": "logging"
+ },
+ "type": "dict",
+ "required": false,
+ "allow_unknown": true,
+ "default": {
+ "version": 1,
+ "handlers": {
+ "console": {
+ "class": "logging.StreamHandler",
+ "formatter": "default",
+ "level": "INFO"
+ }
+ },
+ "formatters": {
+ "default": {
+ "format": "%(asctime)s %(name)s [%(levelname)s] %(message)s",
+ "datefmt": "%Y-%m-%d %H:%M:%S"
+ }
+ },
+ "loggers": {
+ "mqtt_io": {
+ "level": "INFO",
+ "handlers": [
+ "console"
+ ],
+ "propagate": true
+ }
+ }
+ }
+ },
+ "reporting": {
+ "meta": {
+ "description": "Configuration for reporting back to the developers using\n[Sentry](https://sentry.io/welcome/) to help diagnose issues.\n\n*This is **not** enabled by default*\n",
+ "extra_info": "Your config file is included in the report, but has the host, port and username\nhashed and the password removed. Sentry's SDK automatically attempts to remove\npassword data, but the other values may still be exposed within the Python traceback\ncontext.\n",
+ "yaml_example": "reporting:\n enabled: yes\n issue_id: 123\n",
+ "title_id": "reporting"
+ },
+ "type": "dict",
+ "required": false,
+ "schema": {
+ "enabled": {
+ "meta": {
+ "description": "Enable the sending of error reports to the developers if the software crashes.\n",
+ "title_id": "reporting-enabled"
+ },
+ "type": "boolean",
+ "required": true
+ },
+ "issue_id": {
+ "meta": {
+ "description": "The GitHub Issue ID that the specific error relates to.",
+ "extra_info": "This is useful if you've reported a specific issue on the project repository and\nwant to provide additional context to help the developers diagnose the issue.\n",
+ "title_id": "reporting-issue_id"
+ },
+ "type": "integer",
+ "required": false
+ }
+ }
+ },
+ "options": {
+ "meta": {
+ "description": "Miscellaneous options regarding the runtime behaviour of MQTT IO.",
+ "yaml_example": "options:\n install_requirements: no\n",
+ "title_id": "options"
+ },
+ "type": "dict",
+ "required": false,
+ "default": {},
+ "schema": {
+ "install_requirements": {
+ "meta": {
+ "description": "Whether to install missing module packages on startup.",
+ "title_id": "options-install_requirements"
+ },
+ "type": "boolean",
+ "required": false,
+ "default": true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/release_2.6.0/versions.md.j2 b/docs/release_2.6.0/versions.md.j2
new file mode 100644
index 00000000..b8b06f9d
--- /dev/null
+++ b/docs/release_2.6.0/versions.md.j2
@@ -0,0 +1,14 @@
+# Documentation Versions
+
+## Releases
+
+{% for ver in releases %}
+- {{ ver }}
+{%- endfor %}
+
+## Other Versions
+
+{% for ver in other_versions %}
+- {{ ver }}
+{%- endfor %}
+
diff --git a/docs/versions.md b/docs/versions.md
index 62397034..e1a35ba4 100644
--- a/docs/versions.md
+++ b/docs/versions.md
@@ -52,4 +52,5 @@
- release-2.4.2
- release-2.4.4
- release-2.5.1
+- release_2.6.0
- revert-342-docker-slim