diff --git a/upsmon.py b/upsmon.py new file mode 100644 index 0000000..0269363 --- /dev/null +++ b/upsmon.py @@ -0,0 +1,111 @@ +import sys +import json +import subprocess +import logging +from time import sleep +from random import randint + +import paho.mqtt.client as mqtt +import paho.mqtt.enums as mqtt_enums + +# Logger options +LOGFILE = '/mnt/logs/UPSmon.log' + +#CONSOLE_DEBUG = logging.DEBUG +CONSOLE_DEBUG = logging.INFO +#FILE_DEBUG = logging.ERROR +#FILE_DEBUG = logging.WARNING +FILE_DEBUG = logging.INFO +#FILE_DEBUG = logging.DEBUG + +LOG_FORMAT = ('%(asctime)s| %(levelname)-7s|%(funcName)-10s|%(lineno)-3d: %(message)-50s') +LOG_TIME_FORMAT = ('%m-%d %H:%M:%S') +LOGGER: logging.Logger + +# UPSmon Config +CMD: str = 'apcaccess' +T_LONG: float = 15.0 +T_SHORT: float = 5.0 +EXPORT_KEYS: list[str] = [ + 'UPSNAME', 'STATUS', 'LINEV', 'LOADPCT', 'BCHARGE', 'TIMELEFT', 'OUTPUTV', 'ITEMP', 'BATTV', 'LINEFREQ' +] + +def clean_data(v: str) -> float | str: + if v.isalpha(): + return v + return float(v.split(' ')[0]) + +def main() -> int: + i: int = 0 + client: mqtt.Client = mqtt.Client(callback_api_version=mqtt_enums.CallbackAPIVersion.VERSION2, + client_id=f"upsmon_{randint(1,100)}") + + while client.connect(host='10.0.2.249', port=1883) != mqtt_enums.MQTTErrorCode.MQTT_ERR_SUCCESS: + LOGGER.warning("MQTT Client retry connect") + sleep(10.0) + + LOGGER.info("MQTT Client connected") + client.loop_start() + + while client.is_connected(): + try: + rv: bytes = subprocess.check_output(CMD, timeout=2.0) + data_raw: list[list[str]] = [ + l.split(':', 1) for l in rv.decode(encoding='ascii').splitlines() + ] + data = { + k.strip().lower():clean_data(v.strip()) for k,v in data_raw if k.strip() in EXPORT_KEYS + } + LOGGER.debug(f"[{i}] {json.dumps(data, indent=2)}") + + tag: str = str(data.pop('upsname')) + client.publish(topic='monitoring/ups/status', + payload=json.dumps( + obj=(data, {"upsname":tag}) + )) + + sleep(T_LONG if data.get("STATUS", "ONLINE") == "ONLINE" else T_SHORT) + i += 1 + except FileNotFoundError as f: + LOGGER.error(f"{CMD} executable not found: {f}") + return f.errno if f.errno else -1 + except subprocess.CalledProcessError as e: + LOGGER.error(f"{CMD} failed with: {e}") + return e.returncode + except KeyboardInterrupt: + LOGGER.warning("Stopping...") + break + except Exception as ee: # default case catch all + LOGGER.error(f"Unexpected Exception: {ee}") + return -2 + + LOGGER.info("MQTT Client Disconnected") + client.disconnect() + return 0 + + +if __name__ == "__main__": + # Enabling Logger + LOGGER = logging.getLogger(__name__) + LOGGER.setLevel(logging.DEBUG) + LOGGER.propagate = False + formatter = logging.Formatter(LOG_FORMAT,LOG_TIME_FORMAT) + + # File logging + fh = logging.FileHandler(LOGFILE) + fh.setLevel(FILE_DEBUG) + fh.setFormatter(formatter) + LOGGER.addHandler(fh) + + # Console logging + cl = logging.StreamHandler(sys.stdout) + cl.setLevel(CONSOLE_DEBUG) + cl.setFormatter(formatter) + LOGGER.addHandler(cl) + + LOGGER.warning("UPSmon started") + + while main() != 0: + LOGGER.warning("Restarting...") + sleep(30.0) + sys.exit(0)