Compare commits

...

2 Commits

Author SHA1 Message Date
4d2208f961 inital version with mqtt 2025-10-04 09:42:11 +02:00
eec17edf93 reorga code 2025-09-21 00:23:33 +02:00
3 changed files with 106 additions and 76 deletions

View File

@@ -3,12 +3,13 @@
"""dependencys:
- pip3 install nagiosplugin
- pip3 install argparse
- pip3 install paho-mqtt
- pip3 install git+https://github.com/heinemml/CO2Meter
or
- pip3 install git+https://git.ao-it.net/python/CO2Meter
"""
__version__ = '0.3.3'
__version__ = '0.5.0'
__author__ = 'anima'
# imports
@@ -19,7 +20,6 @@ from time import sleep
from os import path
import re
import subprocess
import socket
import threading
import nagiosplugin
@@ -27,64 +27,19 @@ import nagiosplugin
logging.basicConfig(format='[%(asctime)s] %(levelname)s %(message)s', level=logging.INFO)
class AirQ:
def __init__(self, run_mode: str = 'local', server: str = '127.0.0.1', port: int = 4554, device: str = None):
class AirQBase:
def __init__(self):
self.temp = None
self.co2 = None
self.device = device
self.server = server
self.port = port
self.threads = list()
match run_mode:
case 'local':
logging.debug(f'start AirQ in {run_mode} mode')
class AirQLocal(AirQBase):
def __init__(self, device: str = None):
super().__init__()
self.device = device
logging.debug(f'start AirQ in local mode')
if self.get_usb_dev():
self.get_local_data()
case '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)
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((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():
self.temp = data['temp']
def get_usb_dev(self) -> bool:
"""search the airq usb device
@@ -98,10 +53,8 @@ class AirQ:
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()
# 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{1,2}')
## get kernel logs
@@ -128,10 +81,6 @@ class AirQ:
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)
from CO2Meter import CO2Meter
@@ -151,6 +100,78 @@ class AirQ:
return True
class AirQServer(AirQLocal):
def __init__(self, port: int = 4554, device: str = None, ):
super().__init__(device = device)
self.threads = list()
self.port = port
logging.debug(f'start AirQ in server 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()
sleep(5)
thread_server.start()
self.threads.append(thread_data)
self.threads.append(thread_server)
def stop_treads(self):
for thread in self.threads:
logging.debug(f'stop threads')
thread.join()
def get_local_data_loop(self):
while True:
self.get_local_data()
def run_server(self):
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="AirQServer")
client.connect("localhost", self.port)
logging.debug(f'run AirQ server on port {self.port}')
while True:
sleep(10)
logging.debug(f'set {self.temp=}, {self.co2=}')
client.publish("airq/temp", self.temp, retain=True)
client.publish("airq/co2", self.co2, retain=True)
class AirQClient(AirQBase):
def __init__(self, server: str = 'localhost', port: int = 4554, mode = None):
super().__init__()
self.server = server
self.port = port
logging.debug(f'start AirQ in client mode')
self.received = {}
self.mode = mode
self.run_client()
def on_message(self, client, userdata, msg):
self.received[msg.topic] = msg.payload.decode()
logging.debug(f"get {msg.topic=} with {self.received[msg.topic]=}")
client.disconnect() # end with first response
def run_client(self):
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="AirQClient")
client.on_message = self.on_message
client.connect(self.server, self.port)
client.subscribe(f"airq/{self.mode}")
client.loop_start()
sleep(1)
client.loop_stop()
if self.mode == 'co2':
self.co2 = self.received[f"airq/{self.mode}"]
elif self.mode == 'temp':
self.temp = self.received[f"airq/{self.mode}"]
class AirQCO2Resource(nagiosplugin.Resource):
def __init__(self, airq) -> None:
self.airq = airq
@@ -228,11 +249,17 @@ def main():
if args.verbose >= 3:
logging.getLogger().setLevel(logging.DEBUG)
# dice which check will be run bases on check_mode
if args.check_mode != 'server':
airq = AirQ(run_mode=args.airq_mode, server=args.airq_server, port=args.airq_port, device=args.airq_device)
if args.airq_mode == 'local':
airq = AirQLocal(device=args.airq_device)
elif args.airq_mode == 'client':
airq = AirQClient(server=args.airq_server, port=int(args.airq_port), mode=args.check_mode)
else:
logging.error('Unknown airq mode')
exit()
check = None
# dice which check will be run bases on check_mode
match args.check_mode:
case 'co2':
check = nagiosplugin.Check(
@@ -248,7 +275,7 @@ def main():
check.name = "Temperature"
case 'server':
try:
airq = AirQ(run_mode=args.check_mode, server=args.airq_server, port=args.airq_port, device=args.airq_device)
airq = AirQServer(port=int(args.airq_port), device=args.airq_device)
except KeyboardInterrupt:
airq.stop_treads()
case _:

View File

@@ -1,8 +1,11 @@
## for all checks
nagiosplugin
## for api checks
## for api checks & notifications
requests
## for snmp checks
easysnmp
## for mqtt checks
paho-mqtt

View File

@@ -1,2 +1,2 @@
## for airq
pip3 install git+https://github.com/heinemml/CO2Meter
git+https://github.com/heinemml/CO2Meter