summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerg <uinarf@autistici.org>2024-01-27 19:30:14 +0100
committererg <uinarf@autistici.org>2024-01-27 19:30:14 +0100
commit83c4d2e1b9213c78b0b472a1ed4484cf2590531f (patch)
treed3a0b0e93dfe607f96f91d20f67baca6d3f5cb75
downloadMQTT_for_pie-83c4d2e1b9213c78b0b472a1ed4484cf2590531f.tar.gz
MQTT_for_pie-83c4d2e1b9213c78b0b472a1ed4484cf2590531f.tar.bz2
MQTT_for_pie-83c4d2e1b9213c78b0b472a1ed4484cf2590531f.zip
Initial commit
-rw-r--r--README.md27
-rwxr-xr-xgenerate_rrd_db.sh98
-rw-r--r--mqtt_to_rrd.py160
-rw-r--r--settings.cfg.example25
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