From 8011d1282fe13db46c872f757f374bffa8c1119d Mon Sep 17 00:00:00 2001
From: anima
Date: Thu, 27 Feb 2025 22:50:37 +0100
Subject: [PATCH] some optimisations and add logging
---
checks/check_airq.py | 85 ++++++++++++++++++++++++++++++++------------
1 file changed, 63 insertions(+), 22 deletions(-)
diff --git a/checks/check_airq.py b/checks/check_airq.py
index facdb5e..e4586e4 100755
--- a/checks/check_airq.py
+++ b/checks/check_airq.py
@@ -8,58 +8,79 @@ or
- pip3 install git+https://git.ao-it.net/python/CO2Meter
"""
-__version__ = '0.2.0'
+__version__ = '0.3.0'
__author__ = 'anima'
# imports
import logging
import argparse
-import nagiosplugin
+import json
from time import sleep
from os import path
import re
import subprocess
import socket
-import json
+import threading
+import nagiosplugin
# log settings
logging.basicConfig(format='[%(asctime)s] %(levelname)s %(message)s', level=logging.INFO)
class AirQ:
- def __init__(self, run_mode: str, server: str = None, port: int = 4554):
+ def __init__(self, run_mode: str = 'local', server: str = '127.0.0.1', port: int = 4554, device: str = None):
self.temp = None
self.co2 = None
- self.device = None
+ self.device = device
self.server = server
self.port = port
+ self.threads = list()
match run_mode:
case 'local':
- self.get_usb_dev()
- self.get_local_data()
+ logging.debug(f'start AirQ in {run_mode} mode')
+ if self.get_usb_dev():
+ self.get_local_data()
case 'server':
- self.get_usb_dev()
- self.run_server()
+ logging.debug(f'start AirQ in {run_mode} mode')
+ thread_data = threading.Thread(target=self.get_local_data_loop, group=None)
+ thread_server = threading.Thread(target=self.run_server, group=None)
+ if self.get_usb_dev():
+ thread_data.start()
+ thread_server.start()
+ self.threads.append(thread_server)
case 'client':
+ logging.debug(f'start AirQ in {run_mode} mode')
self.run_client()
+ def stop_treads(self):
+ for thread in self.threads:
+ logging.debug(f'stop threads')
+ thread.join()
+
def run_server(self):
s = socket.socket()
s.bind(('', self.port))
s.listen(1)
- while True:
- self.get_local_data()
- c, addr = s.accept()
- data = {"co2": self.co2, "temp": self.temp}
- c.send(str(json.dumps(data)).encode())
- c.close()
- s.close()
+ logging.debug(f'run AirQ server on port {self.port}')
+ try:
+ while True:
+ conn, addr = s.accept()
+ data = {"co2": self.co2, "temp": self.temp}
+ logging.debug(f'connection from {addr}, send {data=}')
+ conn.send(str(json.dumps(data)).encode())
+ conn.close()
+ except KeyboardInterrupt:
+ logging.debug(f'stop AirQ server')
+ s.close()
def run_client(self):
s = socket.socket()
- s.connect(('127.0.0.1', self.port))
+ s.connect((self.server, self.port))
+ logging.debug(f'connected to {self.server=} on {self.port=}')
data = json.loads(s.recv(1024).decode())
+ logging.debug(f'server response: {data=}')
+ s.close()
if 'co2' in data.keys():
self.co2 = data['co2']
if 'temp' in data.keys():
@@ -71,16 +92,22 @@ class AirQ:
Returns:
bool: True if device path found
"""
+ if self.device is not None:
+ if path.exists(self.device):
+ logging.debug(f'device path allready kown: {self.device=}')
+ return True
search_string = '04D9:A052'
# search_string = '04d9:a052' # maybe lowercase on some systems ?
# search_string = '[Holtek USB-zyTemp]' # alt but unknown if all systems print this in log
matches = list()
- command = ["journalctl", "-k", "--no-pager"] # maybe --reverse to get newst first ? # alternative: dmesg
+ # log has con is the log entry rolls out
+ command = ["journalctl", "-k", "--no-pager"] # alternative: dmesg # maybe usefull lsusb
pattern = re.compile(r'hidraw\d{2}')
## get kernel logs
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode != 0:
+ logging.debug(f'search for usb device was not successfull, see {result.returncode=} - {result.stderr=}')
return False
## search in log for usb device
@@ -88,15 +115,22 @@ class AirQ:
for line in lines:
if search_string in line:
matches = pattern.findall(line)
+ logging.debug(f'found usb device mapping in {line=}')
break
## search in found log line the usb name and verify path exists
if len(matches) > 0:
dev_path = f'/dev/{matches[0]}'
if path.exists(f'/dev/{matches[0]}'):
+ logging.debug(f'usb device is in /dev/{matches[0]}')
self.device = f'/dev/{matches[0]}'
return True
- else: return False
+ logging.error(f'usb device not found!')
+ return False
+
+ def get_local_data_loop(self):
+ while True:
+ self.get_local_data()
def get_local_data(self) -> bool:
# load only if need (in client mode not needed)
@@ -107,8 +141,10 @@ class AirQ:
resp = {}
sensor = CO2Meter(self.device)
while not all(key in resp.keys() for key in ['co2', 'temperature']):
- sleep(1)
+ logging.debug(f'collect data ...')
resp = sensor.get_data()
+ logging.debug(f'data collected {resp=}')
+ sleep(1)
self.co2 = resp['co2']
self.temp = resp['temperature']
@@ -181,6 +217,8 @@ def parse_args() -> argparse.Namespace:
help='ip or hostname of airq server')
argp.add_argument('-P', '--airq_port', default=4554,
help='port of airq server')
+ argp.add_argument('-D', '--airq_device', default=None,
+ help='path to AirQ device')
args = argp.parse_args()
return args
@@ -191,7 +229,7 @@ def main():
logging.getLogger().setLevel(logging.DEBUG)
if args.check_mode != 'server':
- airq = AirQ(run_mode=args.airq_mode, server=args.airq_server, port=args.airq_port)
+ airq = AirQ(run_mode=args.airq_mode, server=args.airq_server, port=args.airq_port, device=args.airq_device)
check = None
# dice which check will be run bases on check_mode
@@ -209,7 +247,10 @@ def main():
nagiosplugin.Summary())
check.name = "Temperature"
case 'server':
- airq = AirQ(run_mode=args.check_mode, server=args.airq_server, port=args.airq_port)
+ try:
+ airq = AirQ(run_mode=args.check_mode, server=args.airq_server, port=args.airq_port, device=args.airq_device)
+ except KeyboardInterrupt:
+ airq.stop_treads()
case _:
raise nagiosplugin.CheckError(f'Unknown check mode: {args.check_mode}')