Weather Station
Architecture
Hardware Platform
- Raspberry Pi 3B
- X702 UPS Board with two 18650 cells (TODO which brand)
- Transcend 128GB SSD
Operating System Configuration
Install Raspbian OS Lite 32-bit
Install, then update/ugprade
sudo apt update
sudo apt upgrade -yReferences:
Check Distro for Later Reference
$ lsb_release -d
Description: Raspbian GNU/Linux 10 (buster)Enable SSD Boot
This is a one-off change - now that it has been done, this Raspberry Pi is permanently configured to allow SSD boot.
Enable I2C
Edit /boot/config.txt and uncomment the line to enable I2C:
# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
References:
Docker
Install Docker using the convenience script from the Docker site:
sudo apt-get remove docker docker-engine docker.io containerd runc
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.shInstall docker-compose:
# Dependencies
sudo apt-get install -y libffi-dev libssl-dev
sudo apt install -y python3-dev
sudo apt-get install -y python3 python3-pip
# docker-compose
sudo pip3 install docker-composeReferences:
Prometheus Exporters
See als_sys_container_telemetry for details of how to set these up.
Exporters installed:
- rpi_exporter
- node_exporter
- Docker Engine
- cAdvisor (TODO: note arm7 version not yet available)
Foundational Tools
git
sudo apt install -y gitI2C Utilities
sudo apt install -y i2c-toolstmux
sudo apt install -y tmuxMQTT / Mosquitto Tools
Note we need to install the mosquitto package even though we don't need the Mosquitto server on the host itself (since we run in Docker) but this package is also where mosquitto_passwd comes from.
wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
sudo apt-key add mosquitto-repo.gpg.key
rm mosquitto-repo.gpg.key
cd /etc/apt/sources.list.d/
# Ensure this is the matching distro
sudo wget http://repo.mosquitto.org/debian/mosquitto-buster.list
sudo apt-get update
sudo apt-get install -y \
mosquitto \
mosquitto-clientsDisable the service so that we're not wasting resources, and so we can later bind the port to the Docker container:
sudo systemctl stop mosquitto
sudo systemctl disable mosquittoClone Weather Station Repository
Clone the repo from GitHub:
https://github.com/brendonmatheson/weather-station.gitMQTT Local Broker
Introduction
The weather station runs an MQTT broker locally so that a local InfluxDB can capture measurements at the same time as those messages are forwarded via a bridge to a central MQTT broker for on-prem and on-cloud persistence.
The broker component provides a Docker Compose configuration for running the local broker.
The MQTT broker is running on the official eclipse-mosquitto Docker image, and the version is pinned and should be updated regularly.
Running the Local Broker
Start the broker using the provided convenience script:
./start.shStop the broker using the provided convenience script:
./stop.shConfiguration
Configuration for the local broker is under config/ in the broker component.
| Setting | Value | Notes |
|---|---|---|
| listener | 1883 0.0.0.0 | This is set explicitly because by default Moquitto will bind only to loopback. See this StackOverflow thread for explanation. |
| log_dest | file /mosquitto/log/mosquitto.log | Location for the log which is in-turn mapped to a volume in docker-compose.yaml. |
| persistence | true | Enables persistence. |
| persistence_location | /mosquitto/data/ | Location for persistence which is in-turn mapped to a volume in docker-compose.yaml. |
| password_file | /mosquitto/config/users | Location for users which is in-turn mapped from the file in the broker component files. |
References:
Authentication
Users are stored in config/users
Use the mosquitto_passwd command to add or alter users. See Appendix A for more details.
Testing
To test the local broker, open two shells and in the first one subscribe to a "test" topic:
mosquitto_sub -t "test" -u "hea92weather01" -P "password"Then in the other shell publish a message to that "test" topic:
mosquitto_pub -t "test" -m "message" -p 1883 -u "hea92weather01" -P "password"If the broker is configured correctly and working, then you will see "message" echoed in the subscriber's shell.
References:
Design
The MQTT processing loop is based on explanation and code samples from Steve's Internet Guide (see the references below).
Removing the sensor reader code, the MQTT skeleton looks like this:
class SensorReader:
def __init__(self):
self.stopped = False
def run(self):
broker = "broker_mosquitto_1"
port = 1883
client_id = "hea92weather01"
username = "hea92weather01"
password = "password"
def mqtt_on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT broker")
client.connected_flag = True
else:
print("Failed to connect")
def mqtt_on_disconnect(client, userdata, rc):
print("Disconnecting reason " + str(rc))
client.connected_flag = False
client.disconnect_flag = True
client = mqtt.Client(client_id)
client.on_connect = mqtt_on_connect
client.on_disconnect = mqtt_on_disconnect
client.connected_flag = False
client.username_pw_set(username, password)
client.connect(broker, port)
client.loop_start()
while not self.stopped and not client.connected_flag:
print("Waiting for connection")
sleep(1)
while not self.stopped:
if (client.connected_flag):
# Get and publish sensor readings
sleep(1)
else:
print("Connection lost. Waiting for reconnection")
sleep(5)
client.loop_stop()
client.disconnect()
def stop(self, signal, frame):
print("Stopping SensorReader")
self.stopped = True
def main():
sensorReader = SensorReader()
signal.signal(signal.SIGINT, sensorReader.stop)
signal.signal(signal.SIGTERM, sensorReader.stop)
sensorReader.run()
if __name__ == "__main__":
main()References:
- Python MQTT Client Connections– Working with Connections
- Paho Python MQTT Client – Understanding Callbacks
- Paho Python MQTT Client-Understanding The Loop
InfluxDB Local Storage
Introduction
The weather station stores data locally in addition to that data being shipped out via MQTT for external processing and storage.
The storage component provides a Docker Compose configuration for running InfluxDB 1.8 for storage and Telegraf for data-shipping. Note that we are using 1.8 for the local storage because it is the latest version for which an official image supporting arm7 architecture is maintained by Influx.
References:
Running the Local Storage
Start the database using the provided convenience script:
./start.shStop the database using the provided convenience script:
./stop.shFirst Run User Confguration
If this is the first time that the stack has been run on a node, or if the data volume was dropped, then users need to be created.
- First disable authentication by setting
auth-enabled = falsein confg/influxb.conf - Launch the stack with
./start.sh - Open an Influx shell with
./influx.sh - Create the
weatherdatabase:
CREATE DATABASE "weather"
# Select the database - required for the following CREATE USER steps
USE weather
- Create the required users:
# admin user - used by the influx.sh shell script
CREATE USER admin WITH PASSWORD 'password' WITH ALL PRIVILEGES
# telegraf - used by Telegraf for writing data to InfluxDB
CREATE USER telegraf WITH PASSWORD 'password'
GRANT ALL ON weather to telegraf
# grafana - used by Grafana dashboards for reading data
CREATE USER grafana WITH PASSWORD 'password'
GRANT READ ON weather to grafana
References:
Telgraf Debugging
To debug Telgraf you can:
- Enable debug-level logging by uncommenting the
debug=trueline in config/telegraf.conf - Send messages to file (as well as influx) by uncommenting the
outputs.fileblock
Sensor Reader
Running Directly
To run the reader directly on the host, first install required libraries:
pip3 install \
RPi.bme280 \
paho-mqtt \
SI1145Then launch the process:
python3 main.pyRunning in Docker
sensor-reader is intended to normally be run as a Docker container.
Use the provided convenience script to build the Docker image:
./build.shTo test, use the provided convenience script:
./run.shDesign
The sensor component collects measurements from it's attached sensors every second, formats those measurements into an Influx line protocol messages, and sends the message to the local broker.
Note that the configuration of the mqtt input plugin on Telegraf in the storage component is set to expect influx line format messages.
Note that the configuration of the mqtt input plugin on Telegraf in the local-influx component is set to expect influx line format messages.
References:
Testing
mosquitto_sub -t "hea92weather01/humidity" -u "hea92weather01" -P "password"
mosquitto_sub -t "hea92weather01/pressure" -u "hea92weather01" -P "password"
mosquitto_sub -t "hea92weather01/temperature" -u "hea92weather01" -P "password"References:
BME280 Temperature / Humidity / Pressure Sensor
References:
GY1145 Light Sensor
The weather station has a GY1145 sensor and uses the SI1145 Python library by Joe Gutting on GitHub.
References:
Home Assistant Configuration
MQTT Bridge Connection
We need to tell Home Assistant how to connect to our on-prem MQTT server by adding the mqttelement to confguration.yaml:
mqtt:
broker: "10.80.2.60"
port: 1883
client_id: "bkk80ha"
username: "bkk80ha"
password: "password"MQTT Sensor Definitions
We need to add MQTT sensor definitions to Home Assistant's configuration.yaml with an entry for each value that we want to be able to track and visualize in HA.
The fundamental definition declares the MQTT sensor, gives it a name and maps in the topic from which the value will be obtained:
- platform: "mqtt"
name: "Home Temperature"
state_topic: "weather/home/temperature"On top of this basic definition we can also use the device_class to identify the type of the value. Home Assistant supports the following device classes:
- battery
- carbon_dioxide
- carbon_monoxide
- humidity
- illuminance
- signal_strength
- temperature
- timestamp
- power
- pressure
- current
- energy
- power_factor
- voltage
We can also specify a unit_of_measurement which is just a string appended to the value when presented. Finally since the values emitted from the sensor-reader script are long floating point numbers so we can round these up to two decimal places using the round() function.
Putting these together, here is the full set of MQTT sensor definitions covering readings from the BME280 atmospheric sensor and the SI1145 light sensor:
- platform: "mqtt"
name: "HEA92 Temperature"
state_topic: "weather/hea92weather01/temperature"
device_class: "temperature"
unit_of_measurement: "°C"
value_template: "{{ value_json | round(2) }}"
- platform: "mqtt"
name: "HEA92 Humidity"
state_topic: "weather/hea92weather01/humidity"
device_class: "humidity"
unit_of_measurement: "%"
value_template: "{{ value_json | round(2) }}"
- platform: "mqtt"
name: "HEA92 Pressure"
state_topic: "weather/hea92weather01/pressure"
device_class: "pressure"
unit_of_measurement: "mbar"
value_template: "{{ value_json | round(2) }}"
- platform: "mqtt"
name: "HEA92 Visible Light"
state_topic: "weather/hea92weather01/visible"
device_class: "illuminance"
- platform: "mqtt"
name: "HEA92 UV"
state_topic: "weather/hea92weather01/uv"
device_class: "illuminance"
- platform: "mqtt"
name: "HEA92 IR"
state_topic: "weather/hea92weather01/ir"
device_class: "illuminance"References:
Appendix A - Useful Commands
i2cdetect
This is from the i2c-tools package, and will probe the bus for I2C devices. For example the following verifies that there is a device at address 0x76 which is our BME280 sensor:
$ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --mosquitto_passwd
Used to generate a password database file for the Mosquitto server process.
To add / update a user:
mosquitto_passwd config/users hea92weather01
References:
Appendix B - Technical References
Raspberry Pi GPIO Header
Appendix C - Troubleshooting
AttributeError: module 'bme280' has no attribute 'load_calibration_params'
Once I got the I2C bus working, I next got the following Python error:
AttributeError: module 'bme280' has no attribute 'load_calibration_params'The root cause was that I had named my script bme280.py and so the import bme280 was resolving to my script rather than the actual BME280 library. After renaming my script to sensor_loop.py the import was resolved correctly.
Appendix D - Third-Party Weather Station Products
Gaia Air Quality Monitor
Appendix E - Sensors
SDS011 Air Quality Sensor
PMS3003 Air Quality Sensor
TODO
PMS5003 Air Quality Sensor
TODO
PMS7003 Air Quality Sensor
TODO
ZH03B Air Quality Sensor
TODO
PMSA0003 Air Quality Sensor
TODO
MH-Z19 MH-Z19B NDIR CO2 Sensor Module
TODO

