Initial commit
This commit is contained in:
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# MQTT bridge for the KDE System Monitor (ksysguard)
|
||||||
|
|
||||||
|
A short Python script for monitoring arbitrary MQTT topics via [ksysguard](https://userbase.kde.org/KSysGuard).
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
Save the script somewhere sensible in your computer and give it executable permissions. Root is not required.
|
||||||
|
|
||||||
|
# Configure
|
||||||
|
|
||||||
|
Take a look the [example configuration file](./example-config.yaml) and use it as the basis for your own configuration.
|
||||||
|
|
||||||
|
# Use
|
||||||
|
|
||||||
|
Open ksysguard and go to `File` → `Monitor Remote Machine…`. In the dialog box, enter an arbitrary name under “Host”, select “custom command” and in the command edit box enter the full path to the script in your computer, followed by `-c` (or `--config`) and the full path to your configuration file. For instance:
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/navlost/bin/ksysguard-sensor-mqtt.py -c /home/navlost/.config/ksysguard-sensor-mqtt.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Ensure that the sensor browser on the right-hand side of the ksysguard client is visible, if not, slide it into view by clicking near (but not on) the right-hand edge of the window and dragging left. You should now see your configured sensors and be able to drag them into a tab.
|
||||||
|
|
||||||
|

|
||||||
BIN
doc/connect-host.png
Normal file
BIN
doc/connect-host.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
doc/ksysguard-window.png
Normal file
BIN
doc/ksysguard-window.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
22
example-config.yaml
Normal file
22
example-config.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
mqtt:
|
||||||
|
host: mqtt.example.com
|
||||||
|
username: yourusername # Optional
|
||||||
|
password: yourpassword # Optional
|
||||||
|
subscriptions:
|
||||||
|
-
|
||||||
|
topic: "/things/living_room/sensors/temperature/01"
|
||||||
|
monitor: "Living room/temperature"
|
||||||
|
type: float
|
||||||
|
description:
|
||||||
|
name: "Living room temperature"
|
||||||
|
units: "° C"
|
||||||
|
-
|
||||||
|
topic: "/things/living_room/sensors/humidity/01"
|
||||||
|
monitor: "Living room/humidity"
|
||||||
|
type: integer
|
||||||
|
description:
|
||||||
|
name: "Living room relative humidity"
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
units: "%"
|
||||||
148
ksysguard-sensor-mqtt.py
Executable file
148
ksysguard-sensor-mqtt.py
Executable file
@@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
from random import random
|
||||||
|
|
||||||
|
global config
|
||||||
|
|
||||||
|
config_file_path = os.path.dirname(os.path.abspath(__file__))+"/ksysguard-sensor-mqtt.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
# Get and parse command line arguments
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("-c", "--config", required=False, default=None, help="path to the configuration file")
|
||||||
|
args = vars(ap.parse_args())
|
||||||
|
|
||||||
|
if args["config"]:
|
||||||
|
config_file_path = args["config"]
|
||||||
|
|
||||||
|
|
||||||
|
# Read configuration
|
||||||
|
|
||||||
|
with open(config_file_path, "r") as config_file:
|
||||||
|
try:
|
||||||
|
config = yaml.safe_load(config_file)
|
||||||
|
except yaml.YAMLError as err:
|
||||||
|
print("Could not load configuration file")
|
||||||
|
print(err)
|
||||||
|
|
||||||
|
|
||||||
|
# We cache the values received via MQTT, so that they are available whenever
|
||||||
|
# requested by the ksysguard client.
|
||||||
|
|
||||||
|
global values
|
||||||
|
values = dict()
|
||||||
|
|
||||||
|
for subscription in config['mqtt']['subscriptions']:
|
||||||
|
values[subscription['topic']] = None
|
||||||
|
|
||||||
|
|
||||||
|
# Given a monitor name, returns its data if the name matches the list
|
||||||
|
# of monitors in the configuration. If not, returns None.
|
||||||
|
|
||||||
|
def have_monitor(monitor):
|
||||||
|
return next(iter([subscription for subscription in config['mqtt']['subscriptions'] if subscription['monitor'] == monitor]), None)
|
||||||
|
|
||||||
|
|
||||||
|
# Given a monitor name, returns its most recent value. If the monitor
|
||||||
|
# does not exist, returns UNKNOWN COMMAND.
|
||||||
|
|
||||||
|
def monitor_value(monitor):
|
||||||
|
subscription = have_monitor(monitor)
|
||||||
|
if subscription:
|
||||||
|
return values[subscription['topic']]
|
||||||
|
else:
|
||||||
|
return "UNKNOWN COMMAND"
|
||||||
|
|
||||||
|
# Given a monitor name, returns its info string. If the monitor does
|
||||||
|
# not exist, returns UNKNOWN COMMAND.
|
||||||
|
|
||||||
|
def monitor_info(monitor):
|
||||||
|
subscription = have_monitor(monitor)
|
||||||
|
#print("# Monitor info for "+monitor)
|
||||||
|
#print(subscription)
|
||||||
|
if subscription:
|
||||||
|
descr = subscription['description'] # Shorter to type
|
||||||
|
return (descr['name'] + "\t" +
|
||||||
|
(descr['min'] if 'min' in descr else "") + "\t" +
|
||||||
|
(descr['max'] if 'max' in descr else "") + "\t" +
|
||||||
|
descr['units'])
|
||||||
|
else:
|
||||||
|
return "UNKNOWN COMMAND"
|
||||||
|
|
||||||
|
|
||||||
|
# MQTT logic
|
||||||
|
|
||||||
|
# The on_connect handler subscribes to all configured topics
|
||||||
|
# and issues the ksysguard prompt when finished. From that point,
|
||||||
|
# the client can start requesting data (although none may be available
|
||||||
|
# depending on the relative refresh rates of MQTT publishers and the
|
||||||
|
# ksysguard client.
|
||||||
|
|
||||||
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
print("# Connected with result code "+str(rc))
|
||||||
|
for subscription in config['mqtt']['subscriptions']:
|
||||||
|
print("# Subscribing to " + subscription['topic'])
|
||||||
|
client.subscribe(subscription['topic'])
|
||||||
|
print("ksysguardd> ", end="", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# The callback for when a PUBLISH message is received from the server.
|
||||||
|
# It stores the received value under the relevant topic key in the
|
||||||
|
# values dictionary, from where it will be retrieved when requested
|
||||||
|
# by the ksysguard client.
|
||||||
|
|
||||||
|
def on_message(client, userdata, msg):
|
||||||
|
global values
|
||||||
|
values[msg.topic] = msg.payload.decode("utf8").strip()
|
||||||
|
|
||||||
|
|
||||||
|
# Create the MQTT client and assign event handlers
|
||||||
|
|
||||||
|
client = mqtt.Client(client_id="ksysguardd-"+str(random())[2:])
|
||||||
|
client.on_connect = on_connect
|
||||||
|
client.on_message = on_message
|
||||||
|
|
||||||
|
if 'username' in config['mqtt'] and 'password' in config['mqtt']:
|
||||||
|
client.username_pw_set(config['mqtt']['username'], config['mqtt']['password'])
|
||||||
|
client.connect_async(config['mqtt']['host'], config['mqtt']['port'] if 'port' in config['mqtt'] else 1883, 60)
|
||||||
|
|
||||||
|
|
||||||
|
# Ksysguard logic
|
||||||
|
|
||||||
|
# Start by introducing ourselves. The protocol has been mostly deduced
|
||||||
|
# from this link https://techbase.kde.org/Development/Tutorials/Sensors
|
||||||
|
# and from the ksysguardd source code.
|
||||||
|
|
||||||
|
print("ksysguardd 4")
|
||||||
|
|
||||||
|
client.loop_start() # Asynchronously start the MQTT client
|
||||||
|
|
||||||
|
# Process lines of input from the ksysguard client
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
if line == "monitors": # List available sensors
|
||||||
|
for subscription in config['mqtt']['subscriptions']:
|
||||||
|
print(subscription['monitor']+"\t"+subscription['type'])
|
||||||
|
|
||||||
|
elif line == "quit": # Shut down this process
|
||||||
|
print("# Quitting")
|
||||||
|
client.loop_stop()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
elif len(line) and line[-1] == "?": # This is a query for the monitor's information
|
||||||
|
print(monitor_info(line[0:-1]))
|
||||||
|
else: # Assume that this is a query for the monitor's value
|
||||||
|
print("NaN" if monitor_value(line) == None else monitor_value(line))
|
||||||
|
|
||||||
|
print("ksysguardd> ", end="", flush=True)
|
||||||
|
|
||||||
Reference in New Issue
Block a user