#! /usr/bin/python3 import time import sys from paho.mqtt import client as mqtt_client import os import serial import logging MAXIMUM_COMMUNICATION_FAILURE=1000 TEMPERATUR_MIN_DIFFERENCE = 2 TARGET_MIN_DIFFERENCE = 1 RPM_MIN_DIFFERENCE = 50 COMMAND_PREFIX = "ollpe" # MQTT Settings mqtt_server=os.environ['MQTT_SERVER'] mqtt_topic=os.environ['MQTT_TOPIC'] # SERIAL Port serial_device=os.environ['SERIAL_DEVICE'] # Logging try: if (os.environ['PY_LOGGING']): customLevel = os.environ['PY_LOGGING'].upper() except (KeyError): customLevel = 'WARNING' logging.basicConfig( level=customLevel, filename="/var/log/app.log", filemode="w", format="%(asctime)s - %(levelname)s - %(message)s" ) logging.debug("Start Serial connect") # Start Serial communication ser = serial.Serial(serial_device, 115200, timeout=1) # open serial port if (ser is None): logging.error("Cannot open device" + str(serial_device)) sys.exit() # The callback for when a PUBLISH message is received from the server. def on_message(client, userdata, msg): payload = msg.payload.decode("utf-8") if ((mqtt_topic + "cmd/rpm") == msg.topic): if (ser.is_open): command = COMMAND_PREFIX+"f{:02x}".format(int(payload)) ser.write(command.encode('utf-8')) else: client.publish(mqtt_topic + "cmd/exception", "Serial Connection closed") # clear exception elif ((mqtt_topic + "cmd/led") == msg.topic): command = COMMAND_PREFIX+"l" + payload ser.write(command.encode('utf-8')) else: logging.warning(msg.topic+" "+str(msg.payload)) # Handle MQTT Commands def on_connect(client, obj, flags, reason_code, properties): client.subscribe(mqtt_topic + "cmd/led") client.subscribe(mqtt_topic + "cmd/rpm") client.publish(mqtt_topic + "online", payload="true", qos=1, retain=True) logging.debug("Connected: " + str(reason_code)) # Start mqtt client_id = f'python-fan-ctrl' client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2) client.on_message = on_message client.on_connect = on_connect if (not (mqtt_server)): logging.error("MQTT_SERVER is not set") if (not (mqtt_topic)): logging.error("MQTT_TOPIC is not set") # Start MQTT Client client.will_set(mqtt_topic + "online", payload="false", qos=1, retain=True) client.connect(mqtt_server, 1883) client.loop_start() client.publish(mqtt_topic + "device", ser.name) # check which port was really used # Reset Serial Communication statistic SerialCommunicationFailureCounter=0 client.publish(mqtt_topic + "serialcom/failure", str(SerialCommunicationFailureCounter)) # store last published value lastTemperatur = -1 lastRPM = -1 lastTarget = -1 # Endless Loop try: while ((mqtt_topic is not None) and (mqtt_server is not None)): try: if (not (ser.is_open)): ser.open() continue except (serial.SerialException): logging.error("Could not open serial connection") client.publish(mqtt_topic + "exception", "serial reopen") # check which port was really used time.sleep(3.0) try: line = ser.readline() # read a '\n' terminated line except (serial.SerialException): SerialCommunicationFailureCounter = SerialCommunicationFailureCounter + 1 client.publish(mqtt_topic + "serialcom/failure", str(SerialCommunicationFailureCounter)) line=None if (SerialCommunicationFailureCounter > MAXIMUM_COMMUNICATION_FAILURE): raise serial.SerialException("device reports readiness / device disconnected " + str(MAXIMUM_COMMUNICATION_FAILURE) + " times") if (line) : client.publish(mqtt_topic + "exception", "") # clear exception # activate debug code, by default temperatur = None percent = None rpm = None # convert byte array to string l = line.decode("utf-8") if (COMMAND_PREFIX in l): logging.debug("Skip '" + str(l) + "', found " + str(COMMAND_PREFIX)) continue try: # Parse the controller information # e.g. : 22.00;0%;0RPM temperatur, percent, rpm = l.split(";") except (ValueError): logging.debug("Unparsable '" + str(l) + "'") if ((temperatur) and (percent) and (rpm)): parseError=False try: currentTemperatur = float(temperatur) except (ValueError): logging.error("Temperatur Error: could not convert string to float:" + temperatur) currentTemperatur = lastTemperatur if (abs(currentTemperatur - lastTemperatur) > TEMPERATUR_MIN_DIFFERENCE): client.publish(mqtt_topic + "temperatur", str(currentTemperatur)) lastTemperatur = currentTemperatur if (percent.endswith('/100')): try: currentTarget = int(percent[:-4]) except (ValueError): logging.error("Percent Error: could not convert string to int:" + (percent[:-4])) currentTarget = lastTarget if (abs(lastTarget - currentTarget) > TARGET_MIN_DIFFERENCE): client.publish(mqtt_topic + "target", str(currentTarget)) lastTarget = currentTarget else: client.publish(mqtt_topic + "debug", "target%% invalid:" + str(percent)) parseError=True # remove new line for last element rpm = rpm.replace('\r','').replace('\n','') if (rpm.endswith('RPM')): try: currentRPM = int(rpm[:-3]) except (ValueError): logging.error("RPM Error: could not convert string to int:" + (rpm[:-3])) currentRPM = lastRPM if (abs(currentRPM-lastRPM) > RPM_MIN_DIFFERENCE): client.publish(mqtt_topic + "rpm", str(currentRPM)) lastRPM = currentRPM else: client.publish(mqtt_topic + "debug", "RPM invalid:" + str(rpm)) parseError=True # cleanup debugging if (parseError == False): client.publish(mqtt_topic + "debug", None) else: client.publish(mqtt_topic + "debug", line) # line was invalid and could not be parsed else: time.sleep(0.5) except (KeyboardInterrupt, SystemExit): logging.error("User aborted service ...") client.publish(mqtt_topic + "debug", "User aborted service") time.sleep(0.5) logging.debug("service Dead") client.loop_stop()