diff options
author | erg <uinarf@autistici.org> | 2024-01-27 19:30:14 +0100 |
---|---|---|
committer | erg <uinarf@autistici.org> | 2024-01-27 19:30:14 +0100 |
commit | 83c4d2e1b9213c78b0b472a1ed4484cf2590531f (patch) | |
tree | d3a0b0e93dfe607f96f91d20f67baca6d3f5cb75 | |
download | MQTT_for_pie-83c4d2e1b9213c78b0b472a1ed4484cf2590531f.tar.gz MQTT_for_pie-83c4d2e1b9213c78b0b472a1ed4484cf2590531f.tar.bz2 MQTT_for_pie-83c4d2e1b9213c78b0b472a1ed4484cf2590531f.zip |
Initial commit
-rw-r--r-- | README.md | 27 | ||||
-rwxr-xr-x | generate_rrd_db.sh | 98 | ||||
-rw-r--r-- | mqtt_to_rrd.py | 160 | ||||
-rw-r--r-- | settings.cfg.example | 25 |
4 files changed, 310 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..11997c4 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +Contains two scripts: + - Bash script generating rrd database and generating graphs from it + - Python script readding from MQTT and updating rrd database + - Settings for the Python script + +DEPENDENCIES: + - configparser + - paho + - rrdtool + kernel support ( amongst others (: ): + - CONFIG_USB_VIDEO_CLASS + +Steps to get going: + - Generate rrd file with ./generate_rrd_db.sh -c -i <rrd-file> + - Set up MQTT broker. + - Set up Raspberry Pi pico (Pico project in Micropython section) + - Fill in settings.cfg with your data + - Set up certificates (Check out Pico project for Bash script generating them) + - Run Python script. + - Set up cron job regenerating graphs with frequency to your liking. + +(So that I don't forget:) +What is still missing: + - Python script should run as a daemon. + - Python script could generate more graphs based on averages from rrd database showing long term trends. + - Graph generation could be done with rrdcgi (not sure it is needed though) + - Documentation is lacking detailed step by step setup guide. diff --git a/generate_rrd_db.sh b/generate_rrd_db.sh new file mode 100755 index 0000000..e81b64d --- /dev/null +++ b/generate_rrd_db.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +RRD_DATABASE="" +RRD_GRAPH=w1_graph.png +# Base interval in seconds with which data will be fed into the database: +STEP=15 +# One wire sensor address: +ROM=00-055300000000 # TODO: update me with a real thing! +# Seconds in a year: 31'536'000 +GRAPH=false +CREATE=false + +function Help() { + # Display help + echo "Command generating graph from rrd database" + echo + echo "Syntax: generate_rrd [-h|-g|-c|-o|-i]" + echo "options:" + echo "-h Print this help" + echo "-g Create graph" + echo "-c Create database" + echo "-i Input rrd database file" + echo "-o Output image, defaults to 'w1_graph.png'" + exit 0 +} + + +# Create rrd database if not exist, +# TODO: generate more graphs based on averages +# Keep once in 5 minutes average, min and max for a month, +# Keep once an hour for a year: +function rrd_create() { + if test -f "$RRD_DATABASE"; then + echo "Database already exists, exiting." + exit 1 + else + rrdcreate "$RRD_DATABASE" --step="$STEP" \ + DS:temp:GAUGE:45:U:U \ + RRA:AVERAGE:0.5:1:108000 \ + RRA:AVERAGE:0.5:20:5400 \ + RRA:MIN:0.5:20:5400 \ + RRA:MAX:0.5:20:5400 \ + RRA:AVERAGE:0.5:240:131400 \ + RRA:MAX:0.5:240:131400 \ + RRA:AVERAGE:0.5:240:131400 + fi +} + +# Generate graph image: +rrd_graph() { + echo "Creating graph $RRD_GRAPH" + rrdtool graph "$RRD_GRAPH" --start -86400 --end now \ + --vertical-label "Temperature C" \ + --upper-limit 40 \ + --lower-limit -30 \ + -w 640 -h 480 \ + DEF:temperature="$RRD_DATABASE":temp:AVERAGE \ + LINE1:temperature#00FF00:"temperature C" +} + +while getopts hgci:o: flag; do + case "${flag}" in + h) Help # Display help + exit;; + i) # Input rrd file + RRD_DATABASE=${OPTARG};; + o) # Output image + RRD_GRAPH=${OPTARG};; + c) # Create rrd database + echo "Creating database ..." + CREATE=true;; + g) # Create graph + echo "Creating graph ..." + GRAPH=true;; + \?) # Invalid option + echo "Invalid option" + Help;; + esac +done + +if [ -z "$RRD_DATABASE" ] +then + echo "Please specify rrd database." + Help +fi + +if [[ $CREATE == false ]] && [[ $GRAPH == false ]];then + echo "Specify either database creation (-c) or graph generation (-g) option" + Help +fi + +if $CREATE; then + rrd_create +fi + +if $GRAPH; then + rrd_graph +fi diff --git a/mqtt_to_rrd.py b/mqtt_to_rrd.py new file mode 100644 index 0000000..70818eb --- /dev/null +++ b/mqtt_to_rrd.py @@ -0,0 +1,160 @@ +""" +Script reading from mqtt broker and writing to rrd database. +""" +######################################################### +# IMPORTS # +######################################################### + +import os +import ssl +from configparser import ConfigParser +from time import time +import rrdtool +from paho.mqtt.client import Client as MQTTClient + +######################################################### +# SETTINGS # +######################################################### + +CONFIG_FILE = 'settings.cfg' +USER = os.getlogin() +RRD_DATABASE = f"/home/{USER}/w1_database.rrd" + +######################################################### +# CODE # +######################################################### + + +SSL_SET = None +CERT_DIR = None +CA_CERT = None +CERT_FILE = None +KEY_FILE = None +ONE_WIRE_UPPER = None +ONE_WIRE_LOWER = None +HOSTNAME = None +PORT = None +KEEPALIVE = None +DATA_LENGTH_LIMIT = None +TOPIC = None +TIMEFORMAT = None +Y_LIMIT = None +X_LIMIT = None +INTERVAL = None + + +def get_settings(config_file): + """ + Get settings from configparser. + """ + cfg = ConfigParser(interpolation=None) + cfg.read(config_file) + + global SSL_SET + global CERT_DIR + global CA_CERT + global CERT_FILE + global KEY_FILE + global ONE_WIRE_UPPER + global ONE_WIRE_LOWER + global HOSTNAME + global PORT + global KEEPALIVE + global DATA_LENGTH_LIMIT + global TOPIC + global TIMEFORMAT + global Y_LIMIT + global X_LIMIT + global INTERVAL + # SSL certificates: + SSL_SET = cfg.getboolean('Certificates', 'ssl') + CERT_DIR = cfg.get('Certificates', 'certificate_directory') + CA_CERT_PATH = os.path.join( + CERT_DIR, + cfg.get('Certificates', 'ca_crt') + ) + CERT_FILE_PATH = os.path.join( + CERT_DIR, + cfg.get('Certificates', 'cert_file'), + ) + KEY_FILE_PATH = os.path.join( + CERT_DIR, + cfg.get('Certificates', 'key_file') + ) + CA_CERT = CA_CERT_PATH if CA_CERT_PATH else None + CERT_FILE = CERT_FILE_PATH if CERT_FILE_PATH else None + KEY_FILE = KEY_FILE_PATH if KEY_FILE_PATH else None + + # One wire mapping: + ONE_WIRE_UPPER = cfg.get('Sensor_mapping', 'upper') + ONE_WIRE_LOWER = cfg.get('Sensor_mapping', 'lower') + + # MQTT host: + HOSTNAME = cfg.get('Mqtt', 'hostname') + PORT = cfg.getint('Mqtt', 'port') + KEEPALIVE = cfg.getint('Mqtt', 'keepalive') + + DATA_LENGTH_LIMIT = cfg.get('Main', 'data_length_limit') + TOPIC = cfg.get('Main', 'topic') + TIMEFORMAT = cfg.get('Main', 'timeformat') + + # Plot settings: + Y_LIMIT = ( + cfg.getint('Main', 'y_min'), + cfg.getint('Main', 'y_max') + ) + X_LIMIT = cfg.getint('Main', 'x_limit') + INTERVAL = cfg.getint('Main', 'interval') + + +class MyMQTTClient(MQTTClient): + """ + MQTT client class on_message writing to rrd database. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.data_limit = DATA_LENGTH_LIMIT + if SSL_SET: + self.tls_set( + ca_certs=CA_CERT, + certfile=CERT_FILE, + keyfile=KEY_FILE, + cert_reqs=ssl.CERT_REQUIRED, + ) + self.connect(host=HOSTNAME, port=PORT, keepalive=KEEPALIVE) + self.bare_topic = TOPIC.strip("/#") + self.full_topic = f'{self.bare_topic}/{ONE_WIRE_UPPER}' + self.subscribe(self.full_topic) + self.on_message = self.onmessage + self.on_disconnect = self.ondisconnect + + def ondisconnect(self, client): + print("Disconnected from MQTT") + client.loop_stop() + + def on_connect(self, client, userdata, flags, rc): + print("Connected to MQTT, returncode: ", str(rc)) + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client.subscribe("$SYS/#") + + def onmessage(self, client, userdata, msg): + """ + On message update rrd database. + """ + temperature = msg.payload.decode("utf-8") + if msg.topic == self.full_topic: + now = time() # TODO: pass timestamp in the message body + + rrdtool.update(RRD_DATABASE, '%s:%s' % (now, temperature)) + + +def main(): + """Main loop""" + get_settings(CONFIG_FILE) + client = MyMQTTClient() + client.loop_forever() + + +if __name__ == '__main__': + main() diff --git a/settings.cfg.example b/settings.cfg.example new file mode 100644 index 0000000..0cdbc45 --- /dev/null +++ b/settings.cfg.example @@ -0,0 +1,25 @@ +[Main] +data_length_limit = 10 +timeformat = %Y-%m-%d %H:%M:%S +topic = temperature +y_max = 51 +y_min = -20 +x_limit = 2000 +interval = 5000 + +[Certificates] +ssl = True +certificate_directory = +ca_crt = +cert_file = +key_file = + +[Sensor_mapping] +upper = +lower = + +[Mqtt] +hostname = +port = 8883 +keepalive = 60 +topic = temperature |