From 493cfb1b886344147d984a7a9ad93e1e84895003 Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Thu, 2 Oct 2025 22:27:49 +0200 Subject: [PATCH] Merge branch 'influxdb3' --- .vscode/launch.json | 16 ++- docker/build.sh | 19 ++++ docker/docker-compose.yaml | 102 ++++++++++++----- pyutils/utils.py | 37 +++++++ routermon/routermon.Dockerfile | 5 +- routermon/routermon.py | 118 ++++++++++---------- upsmon/dataexchange.txt | 26 +++++ upsmon/sniffer.py | 16 +++ upsmon/ups.py | 197 +++++++++++++++++++++------------ upsmon/upsmon.Dockerfile | 5 +- 10 files changed, 369 insertions(+), 172 deletions(-) create mode 100755 docker/build.sh create mode 100644 pyutils/utils.py create mode 100644 upsmon/dataexchange.txt create mode 100644 upsmon/sniffer.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 6c3ad72..a08d8af 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,10 +9,9 @@ "console": "integratedTerminal", "env": { "INTERVAL" : "5", - "INFLUXDB_TOKEN":"b46xoZRVsQvyGnv8TVMmTDmxT8LZZsi7O_u776MHIxiE9CPh7yHi0iHMOerUT5o1y65MMxeKKA7S-ijQ3elK-g==", - "INFLUXDB_URL" : "http://localhost:8085", - "INFLUXDB_ORG" : "edelweiss", - "INFLUXDB_BUCKET" : "theatre", + "INFLUXDB_TOKEN":"apiv3_tbEpA8JmIRTfr8Wbw9npD79BcMlFsnV4_jhdt-CdUP53Mos61KBODGaggl2g5oKZZvZrZu3e6mpob6zorhEdbg", + "INFLUXDB_URL" : "http://edelweiss-srv:8181", + "INFLUXDB_DATABASE" : "edelweiss", "MIKROTIK_IP": "192.168.31.1", "MIKROTIK_USER": "service", "MIKROTIK_PASSWORD": "dataservice", @@ -29,11 +28,10 @@ "console": "integratedTerminal", "env": { "INTERVAL" : "5", - "INFLUXDB_TOKEN":"rg5dZrBHQxJTH4Etq2jmDduggCC28QaWcua0VVvW4hjsEVhy_JUpVhcyg-aLAbM-TXv92pTB7IGJlyAPvi7Kvw==", - "INFLUXDB_URL" : "http://edelweiss-srv:4567", - "INFLUXDB_ORG" : "edelweiss", - "INFLUXDB_BUCKET" : "ups", - "PORT": "/dev/ttyUSB1", + "INFLUXDB_TOKEN":"apiv3_tbEpA8JmIRTfr8Wbw9npD79BcMlFsnV4_jhdt-CdUP53Mos61KBODGaggl2g5oKZZvZrZu3e6mpob6zorhEdbg", + "INFLUXDB_URL" : "http://edelweiss-srv:8181", + "INFLUXDB_DATABASE" : "edelweiss", + "PORT": "/dev/ttyS0", "BAUD": "2400", "LOG_FILE": "/tmp/upsmon.log", "LOG_FILE_LVL": "WARNING", diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 0000000..8500c4e --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +GIT_HASH=$(git rev-parse --short HEAD) + +docker-compose stop routermon +docker-compose stop upsmon +docker container prune + +echo "Building UPSmon" +cp ../pyutils/utils.py ../upsmon/ +docker build --build-arg BUILD_VER=${GIT_HASH} -f ../upsmon/upsmon.Dockerfile -t upsmon:influx3 ../upsmon +rm ../upsmon/utils.py + +echo "Building Routermon" +cp ../pyutils/utils.py ../routermon/ +docker build --build-arg BUILD_VER=${GIT_HASH} -f ../routermon/routermon.Dockerfile -t routermon:influx3 ../routermon +rm ../routermon/utils.py + +docker-compose up -d diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index ba2a8fd..f5680b2 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -1,18 +1,26 @@ services: - influxdb2: - image: influxdb:2-alpine - container_name: edx-influxdb + influxdb3: + image: influxdb:3-core + container_name: edx-influxdb3 restart: unless-stopped ports: - - 8086:8086 + - 8181:8181 + command: + - influxdb3 + - serve + - --node-id=influxb-node0 + - --object-store=file + - --data-dir=/var/lib/influxdb3 volumes: - type: volume - source: influxdb2-data - target: /var/lib/influxdb2 - - type: volume - source: influxdb2-config - target: /etc/influxdb2 - + source: influxdb3-data + target: /var/lib/influxdb3 + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" + grafana: image: grafana/grafana:latest container_name: edx-grafana @@ -22,40 +30,53 @@ services: volumes: - grafana-data:/var/lib/grafana depends_on: - - influxdb2 + - influxdb3 + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" upsmon: - image: upsmon:latest + image: upsmon:influx3 container_name: edx-upsmon restart: unless-stopped depends_on: - - influxdb2 + - influxdb3 devices: - - /dev/ttyUSB1:/dev/ttyUSB1 + - /dev/ttyS0:/dev/ttyS0 + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" environment: - INTERVAL=5 - - INFLUXDB_TOKEN=b46xoZRVsQvyGnv8TVMmTDmxT8LZZsi7O_u776MHIxiE9CPh7yHi0iHMOerUT5o1y65MMxeKKA7S-ijQ3elK-g== - - INFLUXDB_URL=http://influxdb2:8086 - - INFLUXDB_ORG=edelweiss - - INFLUXDB_BUCKET=theatre - - PORT=/dev/ttyUSB1 + - INFLUXDB_TOKEN=apiv3_tbEpA8JmIRTfr8Wbw9npD79BcMlFsnV4_jhdt-CdUP53Mos61KBODGaggl2g5oKZZvZrZu3e6mpob6zorhEdbg + - INFLUXDB_URL=http://influxdb3:8181 + - INFLUXDB_DATABASE=edelweiss + - PORT=/dev/ttyS0 - BAUD=2400 - LOG_FILE=/tmp/upsmon.log - LOG_FILE_LVL=WARNING - LOG_CLI_LVL=INFO - + routermon: - image: routermon:latest + image: routermon:influx3 container_name: edx-routermon restart: unless-stopped depends_on: - - influxdb2 + - influxdb3 + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" environment: - INTERVAL=5 - - INFLUXDB_TOKEN=b46xoZRVsQvyGnv8TVMmTDmxT8LZZsi7O_u776MHIxiE9CPh7yHi0iHMOerUT5o1y65MMxeKKA7S-ijQ3elK-g== - - INFLUXDB_URL=http://influxdb2:8086 - - INFLUXDB_ORG=edelweiss - - INFLUXDB_BUCKET=theatre + - INFLUXDB_TOKEN=apiv3_tbEpA8JmIRTfr8Wbw9npD79BcMlFsnV4_jhdt-CdUP53Mos61KBODGaggl2g5oKZZvZrZu3e6mpob6zorhEdbg + - INFLUXDB_URL=http://influxdb3:8181 + - INFLUXDB_DATABASE=edelweiss - MIKROTIK_IP=192.168.31.1 - MIKROTIK_USER=service - MIKROTIK_PASSWORD=dataservice @@ -63,8 +84,33 @@ services: - LOG_FILE_LVL=WARNING - LOG_CLI_LVL=INFO +# unifi-controller: +# image: lscr.io/linuxserver/unifi-controller:latest +# container_name: edx-unifi +# environment: +# - PUID=1000 +# - PGID=1000 +# - MEM_LIMIT=1024 #optional +# - MEM_STARTUP=1024 #optional +# volumes: +# - /var/lib/unifi:/config +# ports: +# - 8443:8443 +# - 3478:3478/udp +# - 10001:10001/udp +# - 8080:8080 +# - 1900:1900/udp #optional +# - 8843:8843 #optional +# - 8880:8880 #optional +# - 6789:6789 #optional +# - 5514:5514/udp #optional +# restart: unless-stopped +# logging: +# driver: "json-file" +# options: +# max-size: "10m" +# max-file: "5" volumes: - influxdb2-data: - influxdb2-config: grafana-data: + influxdb3-data: diff --git a/pyutils/utils.py b/pyutils/utils.py new file mode 100644 index 0000000..d90ec35 --- /dev/null +++ b/pyutils/utils.py @@ -0,0 +1,37 @@ +import signal +from logging import Logger +from influxdb_client_3 import Point + +class SignalHandler: + running: bool + logger: Logger + + def __init__(self, logger): + self.running: bool = True + self.logger: Logger = logger + signal.signal(signal.SIGINT, self._handle_sigint) + signal.signal(signal.SIGTERM, self._handle_sigint) + + def _handle_sigint(self, signum, frame): + self.logger.info(f"Received SIGNAL: {signal.strsignal(signum)}") + self.running = False + +def dict2Point(measurement: str, fields: dict, tags: dict | None = None) -> Point: + p = Point(measurement) + for k,v in fields.items(): + p.field(k,v) + if tags: + for k,v in tags.items(): + p.tag(k,v) + return p + +def convertInt(d: dict) -> dict: + for k,v in d.items(): + if str.isdecimal(v): + d[k] = int(v) + return d + +def convertIntList(l: list[dict]) -> list[dict]: + for n,d in enumerate(l): + l[n] = convertInt(d) + return l \ No newline at end of file diff --git a/routermon/routermon.Dockerfile b/routermon/routermon.Dockerfile index e91c2b9..c1476e8 100644 --- a/routermon/routermon.Dockerfile +++ b/routermon/routermon.Dockerfile @@ -1,9 +1,12 @@ FROM python:3.12-alpine +ARG BUILD_VER RUN apk update && apk upgrade --no-cache -RUN pip install --no-cache-dir RouterOS-API influxdb-client +RUN pip install --no-cache-dir RouterOS-API influxdb3-python COPY ./routermon.py /home/routermon.py +COPY ./utils.py /home/pyutils/utils.py +ENV VER=${BUILD_VER} CMD [ "python", "/home/routermon.py" ] diff --git a/routermon/routermon.py b/routermon/routermon.py index 8549350..2eea851 100644 --- a/routermon/routermon.py +++ b/routermon/routermon.py @@ -2,88 +2,81 @@ import os import sys import time import logging -import signal import json import routeros_api -from influxdb_client.client.write.point import Point -from influxdb_client.client.influxdb_client import InfluxDBClient -from influxdb_client.client.write_api import ASYNCHRONOUS, SYNCHRONOUS +from pyutils.utils import * +from influxdb_client_3 import InfluxDBClient3, Point # Get environment variables env = dict(os.environ) LOGGER: logging.Logger -class SignalHandler: - running: bool - - def __init__(self): - self.running: bool = True - signal.signal(signal.SIGINT, self._handle_sigint) - signal.signal(signal.SIGTERM, self._handle_sigint) - - def _handle_sigint(self, signum, frame): - self.running = False - - -def main(): +################## +###### MAIN ###### +################## +def main() -> int: INTERVAL = int(env['INTERVAL']) - # Init InfluxDB - write_client = InfluxDBClient(url=env['INFLUXDB_URL'], + try: + # Init InfluxDB-v3 Client + write_client = InfluxDBClient3(host=env['INFLUXDB_URL'], token=env['INFLUXDB_TOKEN'], - org=env['INFLUXDB_ORG']) - write_api = write_client.write_api(write_options=ASYNCHRONOUS) - # Init routerOS API - connection = routeros_api.RouterOsApiPool(env['MIKROTIK_IP'], - username=env['MIKROTIK_USER'], - password=env['MIKROTIK_PASSWORD'], - plaintext_login=True) - api = connection.get_api() + database=env['INFLUXDB_DATABASE']) + # Init routerOS API + connection = routeros_api.RouterOsApiPool(env['MIKROTIK_IP'], + username=env['MIKROTIK_USER'], + password=env['MIKROTIK_PASSWORD'], + plaintext_login=True) + api = connection.get_api() + except Exception as e: + LOGGER.error(e) + return 1 + finally: + LOGGER.info(f"InfluxDB Connected: [{env['INFLUXDB_URL']}/{env['INFLUXDB_DATABASE']}]") + LOGGER.info(f"Mikrotik Connected: [{env['MIKROTIK_IP']}]") - run: SignalHandler = SignalHandler() - last = 0 - if_points = [] - if_stats_old = api.get_resource('/interface/ethernet').call('print', {'proplist': 'name,rx-bytes,tx-bytes'}) - for n,d in enumerate(if_stats_old): - for k,v in d.items(): - if str.isdecimal(v): - if_stats_old[n][k] = int(v) - ### MAIN LOOP ### - while run: + ############################## + ########## MAIN LOOP ######### + ############################## + last: float = 0 + handler: SignalHandler = SignalHandler(LOGGER) + if_stats_old = convertIntList(api.get_resource('/interface/ethernet').call('print', {'proplist': 'name,rx-bytes,tx-bytes'})) + while handler.running: try: - now = time.time() - if_stats = api.get_resource('/interface/ethernet').call('print', {'proplist': 'name,rx-bytes,tx-bytes'}) - hw_stats = api.get_resource('/system/resource').call('print', {'proplist':'uptime,cpu-load'})[0] - # calcolo della velocita' interfaccia a ogni ciclo - for n,d in enumerate(if_stats): - for k,v in d.items(): - if str.isdecimal(v): - if_stats[n][k] = int(v) - if_stats[n]['rx-rate'] = int((if_stats[n]['rx-bytes']-if_stats_old[n]['rx-bytes'])/(now-last)) - if_stats[n]['tx-rate'] = int((if_stats[n]['tx-bytes']-if_stats_old[n]['tx-bytes'])/(now-last)) + now:float = time.time() + if_points: list[Point] = [] + if_stats: list[dict] = convertIntList(api.get_resource('/interface/ethernet').call('print', {'proplist': 'name,rx-bytes,tx-bytes'})) + hw_stats: dict[str,str] = convertInt(api.get_resource('/system/resource').call('print', {'proplist':'uptime,cpu-load,total-memory,free-memory'})[0]) + hw_stats['temperature'] = convertInt(api.get_resource('/system/health').call('print')[1])['value'] + # Calcolo della velocita' interfaccia a ogni ciclo + for n, _ in enumerate(if_stats): + if_name = if_stats[n].pop('name') + if_stats[n]['rx-rate'] = int((if_stats[n]['rx-bytes']-if_stats_old[n]['rx-bytes'])/(INTERVAL)) + if_stats[n]['tx-rate'] = int((if_stats[n]['tx-bytes']-if_stats_old[n]['tx-bytes'])/(INTERVAL)) if_points.append( - Point("interfaces") - .tag("interface", d['name']) - .field('rx-rate', if_stats[n]['rx-rate']) - .field('tx-rate', if_stats[n]['tx-rate']) - ) - rs1 = write_api.write(bucket=env['INFLUXDB_BUCKET'], org=env['INFLUXDB_ORG'], - record=if_points) - rs2 = write_api.write(bucket=env['INFLUXDB_BUCKET'], org=env['INFLUXDB_ORG'], - record=Point('resources').field('cpu',int(hw_stats['cpu-load'])) - ) - LOGGER.debug(f"InfluxWrite: W1:{rs1}, W2:{rs2}") + dict2Point('interfaces', if_stats[n], {'interface': if_name}) + ) + write_client.write(record=if_points) + + # Risorse del router + write_client.write(record=dict2Point('resources',hw_stats)) + + # Salvo ultimo punto per il giro successivo if_stats_old = if_stats - last = time.time() LOGGER.debug(f"\nInterfaces: {json.dumps(if_stats, indent = 2)}") LOGGER.debug(f"\nResources: {json.dumps(hw_stats, indent = 2)}") - time.sleep(INTERVAL) + last: float = time.time() + cycle_time: float = last - now + LOGGER.debug(f"Cycle Time: {cycle_time:4.3f}") + time.sleep(INTERVAL-cycle_time) except Exception as e: print(f"Unexpected exception: [{e}]") return 1 - ### END MAIN LOOP ### - + ############################## + ###### END MAIN LOOP ######### + ############################## connection.disconnect() + write_client.close() return 0 if __name__ == "__main__": @@ -111,6 +104,7 @@ if __name__ == "__main__": LOGGER.addHandler(cl) LOGGER.warning(f"Routermon started on: {time.asctime()}") + LOGGER.info(f"Routermon BUILD: {env.get("VER", "Test")}") while main(): LOGGER.error("Main thread exited unexpectedly") diff --git a/upsmon/dataexchange.txt b/upsmon/dataexchange.txt new file mode 100644 index 0000000..1841429 --- /dev/null +++ b/upsmon/dataexchange.txt @@ -0,0 +1,26 @@ +VP req: QMD +UPS resp: (#######WPHVT2K0 ###2000 80 1/1 230 230 04 12.0 +VP req: QRI +UPS resp: (230.0 008 048.0 50.0 +VP req: QHE +UPS resp: (242 218 +VP req: QRI +UPS resp: (230.0 008 048.0 50.0 +VP req: QGS +UPS resp: (230.9 50.0 230.7 50.1 001.0 013 373.7 379.4 054.4 ---.- 027.3 100000000001 +VP req: QMOD +UPS resp: (L +VP req: QWS +UPS resp: (0000000000000000000000000000000000000000000000000000000000000000 +VP req: QBV +UPS resp: (054.5 04 04 096 568 +VP req: QSK1 +UPS resp: (NAK +VP req: QSK2 +UPS resp: (NAK +VP req: QFLAG +UPS resp: (EpbrashczDovegfm +VP req: QBYV +UPS resp: (264 170 +VP req: QBYF +UPS resp: (53.0 47.0 \ No newline at end of file diff --git a/upsmon/sniffer.py b/upsmon/sniffer.py new file mode 100644 index 0000000..9abd153 --- /dev/null +++ b/upsmon/sniffer.py @@ -0,0 +1,16 @@ +import serial + +ups_port = "/dev/ttyUSB1" +usb_fake = "/dev/ttyUSBf" + +ups = serial.Serial(port=ups_port, baudrate=2400, parity="N", bytesize=8, stopbits=1) +vp = serial.Serial(port=usb_fake, baudrate=2400, parity="N", bytesize=8, stopbits=1) + +while True: + vp_request = vp.read_until(b'\r') + print(f'VP req: {vp_request.decode('ascii').strip()}') + ups.write(vp_request) + ups.flush() + ups_response = ups.read_until(b'\r') + print(f'UPS resp: {ups_response.decode('ascii').strip()}') + vp.write(ups_response) \ No newline at end of file diff --git a/upsmon/ups.py b/upsmon/ups.py index d14a751..ba3e2be 100644 --- a/upsmon/ups.py +++ b/upsmon/ups.py @@ -1,121 +1,176 @@ import os import sys import time -import string import serial import logging -import signal -import json -from influxdb_client.client.write.point import Point -from influxdb_client.client.influxdb_client import InfluxDBClient -from influxdb_client.client.write_api import ASYNCHRONOUS, SYNCHRONOUS +from copy import deepcopy +from pyutils.utils import * +from dataclasses import dataclass +from influxdb_client_3 import InfluxDBClient3, WriteOptions # Get environment variables env = dict(os.environ) LOGGER: logging.Logger -class SignalHandler: - running: bool +class UPScommand: - def __init__(self): - self.running: bool = True - signal.signal(signal.SIGINT, self._handle_sigint) - signal.signal(signal.SIGTERM, self._handle_sigint) + def send(self, port:serial.Serial, cmd: str): + port.write((cmd+'\r').encode('ascii')) + port.flush() - def _handle_sigint(self, signum, frame): - self.running = False + def receive(self, port:serial.Serial, cmd: str) -> str: + resp = port.read_until(expected=b'\r').decode('ascii').rstrip() + LOGGER.debug(f"{cmd} : {resp}") + return resp -def send(port: serial.Serial, d: str): - port.write((d+'\r').encode('ascii')) - port.flush() + def request(self, port:serial.Serial, cmd: str) -> list[str]: + self.send(port, cmd) + return self.receive(port, cmd).lstrip('(').rstrip().split() + + def asDict(self): + return deepcopy(self.__dict__) -def receive(port: serial.Serial, d: str) -> str: - r = port.read_until(b'\r').decode('ascii').rstrip() - LOGGER.debug(f"{d} : {r}") - return r +@dataclass +class UPSstatus(UPScommand): + inV: float + inF: float + outV: float + outF: float + current: float + loadPct: int + battV: float + temp: float + onLine: bool + onBatt: bool + ecoMode: bool -def bruteforceCommands(port: serial.Serial): - # T and S cause unwanted shutdown - letters = string.ascii_uppercase.replace('T','').replace('S','') - LOGGER.debug(f"Test commands: {letters}") - for c in letters: - send(port, c) - receive(port, c) - for n in range(10): - d = c+f"{n:1d}" - send(port, d) - receive(port, d) - for n in range(100): - d = c+f"{n:02d}" - send(port, d) - receive(port, d) + def __init__(self, port: serial.Serial): + self.update(port) + return -def main(): + def update(self, port:serial.Serial): + data = self.request(port=port, cmd="QGS") + self.inV = float(data[0]) + self.inF = float(data[1]) + self.outV = float(data[2]) + self.outF = float(data[3]) + self.current = float(data[4]) + self.loadPct = int(data[5]) + self.battV = float(data[8]) + self.temp = float(data[10]) + self.onLine = True if data[11].startswith('1') else False + self.onBatt = True if data[11].endswith('0') else False + self.ecoMode = True if data[11][4] == '1' else False + +@dataclass +class UPSbattery(UPScommand): + battV: float + battPct: int + timeLeft: float + + def __init__(self, port: serial.Serial): + self.update(port) + return + + def update(self, port: serial.Serial): + data = self.request(port=port, cmd="QBV") + self.battV = float(data[0]) + self.battPct = int(data[3]) + self.timeLeft = round(int(data[4]) / 60.0, 1) + +################## +###### MAIN ###### +################## +def main() -> int: INTERVAL = int(env['INTERVAL']) - LOGGER.debug(json.dumps(env, indent=2)) - run: SignalHandler = SignalHandler() - port = serial.Serial(port=env['PORT'], baudrate=int(env['BAUD']), bytesize=8, parity='N', stopbits=1) - write_client = InfluxDBClient(url=env['INFLUXDB_URL'], - token=env['INFLUXDB_TOKEN'], - org=env['INFLUXDB_ORG']) - write_api = write_client.write_api(write_options=ASYNCHRONOUS) + UPS_COMMAND = "Q1" + UPS_STATUS = "QGS" # (Vin Fin Vout Fout Aout Load% ?1 ?2 VbatInt VbatExt Temp Flags + UPS_BATTERY = "QBV" # (Vbat n1 n2 Charge% RunMins + UPS_MODE = "QMOD" # (Mode [Echo, Line, Battery?] + + try: + # Init InfluxDB-v3 Client + write_client = InfluxDBClient3(host=env['INFLUXDB_URL'], + token=env['INFLUXDB_TOKEN'], + database=env['INFLUXDB_DATABASE']) + # Init Serial port + port = serial.Serial(port=env['PORT'], baudrate=int(env['BAUD']), bytesize=8, parity='N', stopbits=1) + port.flush() + + # Init Dataclasses + run: SignalHandler = SignalHandler(LOGGER) + status = UPSstatus(port) + battery = UPSbattery(port) + except Exception as e: + LOGGER.error(e) + return 1 + finally: + LOGGER.info(f"InfluxDB Connected: [{env['INFLUXDB_URL']}/{env['INFLUXDB_DATABASE']}]") + LOGGER.info(f"Serial Port Open: [{env['PORT']}]") + + ############################## + ########## MAIN LOOP ######### + ############################## while run.running: try: - send(port, "Q1") - data = receive(port, "Q1").lstrip('(').split() - if len(data) < 8: - LOGGER.error(f"Incomplete data: {data}") - continue - values = { - 'inV': float(data[0]), - 'outV': float(data[2]), - 'loadPercent': float(data[3]), - 'lineFreq': float(data[4]), - 'timeLeft': float(data[5]), - 'onBatt': True if str(data[7]).startswith('1') else False, - 'onLine': True if str(data[7]).endswith('1') else False, - } - LOGGER.debug(f"UPS Status: \n{json.dumps(values, indent=2)}") - if values['onBatt']: - LOGGER.info(f"OnBattery\n{json.dumps(values,indent=2)}") - p = Point('ups') - for k,v in values.items(): - p.field(k,v) - write_api.write(bucket=env['INFLUXDB_BUCKET'], org=env['INFLUXDB_ORG'], record=p) + # Update data + status.update(port) + LOGGER.debug(f"{repr(status)}") + battery.update(port) + LOGGER.debug(f"{repr(battery)}") + + # Debug status Information when running on batteries + if status.onBatt: + LOGGER.info(f" Status:\n{repr(status)}") + LOGGER.info(f"Battery:\n{repr(battery)}") + + # Write datapoint to Influx merging measurements + datapoint = status.asDict() | battery.asDict() + write_client.write(record=dict2Point(measurement='ups', + fields=datapoint + )) + LOGGER.debug(f"Influx Write: {datapoint}") + # Sleep and repeat time.sleep(INTERVAL) except Exception as e: print(f"Unexpected exception: [{e}]") return 1 +############################## +###### END MAIN LOOP ######### +############################## port.close() - + write_client.close() + LOGGER.warning("Main thread exited normally") + return 0 if __name__ == "__main__": - # Logger Constants + # Logger Constants LOG_FORMAT = '%(asctime)s| %(levelname)-7s|%(funcName)-10s|%(lineno)-3d: %(message)-50s' - # Enabling Logger + # Enabling Logger LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) LOGGER.propagate = False formatter = logging.Formatter(LOG_FORMAT, None) levels = logging.getLevelNamesMapping() - # File logging + # File logging log_name = os.path.abspath(env['LOG_FILE']) fh = logging.FileHandler(log_name) fh.setLevel(levels[env['LOG_FILE_LVL']]) fh.setFormatter(formatter) LOGGER.addHandler(fh) - # Console logging + # Console logging cl = logging.StreamHandler(sys.stdout) cl.setLevel(levels[env['LOG_CLI_LVL']]) cl.setFormatter(formatter) LOGGER.addHandler(cl) LOGGER.warning(f"UPSmon started on: {time.asctime()}") - + LOGGER.info(f"UPSmon BUILD: {env.get("VER", "Test")}") + while main(): LOGGER.error("Main thread exited unexpectedly") time.sleep(15) diff --git a/upsmon/upsmon.Dockerfile b/upsmon/upsmon.Dockerfile index 70a707f..0d182a5 100644 --- a/upsmon/upsmon.Dockerfile +++ b/upsmon/upsmon.Dockerfile @@ -1,9 +1,12 @@ FROM python:3.12-alpine +ARG BUILD_VER RUN apk update && apk upgrade --no-cache -RUN pip install --no-cache-dir pyserial RouterOS-API influxdb-client +RUN pip install --no-cache-dir pyserial RouterOS-API influxdb3-python COPY ./ups.py /home/ups.py +COPY ./utils.py /home/pyutils/utils.py +ENV VER=${BUILD_VER} CMD [ "python", "/home/ups.py" ]