#!/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)