Browse Source

2022-11-18:
- basic support for sensors sending values
- small changes to config structure (TX_interface_prefer moved to cul section)

FloKra 1 year ago
parent
commit
f76ae9fbd7
4 changed files with 103 additions and 60 deletions
  1. 4 0
      CHANGELOG.txt
  2. 75 54
      cul2mqtt.py
  3. 8 0
      cul2mqtt_devices_example_value.yml
  4. 16 6
      cul2mqtt_example.ini

+ 4 - 0
CHANGELOG.txt

@@ -1,3 +1,7 @@
+2022-11-18:
+  - basic support for sensors sending values
+  - small changes to config structure (TX_interface_prefer moved to cul section)
+
 2021-03-14:
   - added support for using Tasmota ESP8266 firmware as a Serial Bridge for building an MQTT-CUL rather than my own outdated, instable and never published MQTT-Serial-firmware
     tested with Tasmota v9.3.1

+ 75 - 54
cul2mqtt.py

@@ -51,7 +51,9 @@ mqtt_port = config['mqtt'].getint('port')
 mqtt_user = config['mqtt'].get('user')
 mqtt_password = config['mqtt'].get('password')
 
-TX_interface_prefer = config['main'].get('TX_interface_prefer') # UART or MQTT
+TX_interface_prefer = config['cul'].get('TX_interface_prefer') # UART or MQTT
+if TX_interface_prefer is None: # compatibility with old ini file structure
+    TX_interface_prefer = config['main'].get('TX_interface_prefer') # UART or MQTT
 
 repeat_received_commands = False # not yet implemented
 
@@ -189,7 +191,7 @@ try:
                         if debug:
                             log_write(str(key) + "->" + str(value))
                         if value.startswith("i"):
-                            # is InterTechno RX code
+                            # is Intertechno RX code
                             if value in RXcodesToDevFunction_IT.keys():
                                 log_write("")
                                 log_write("")
@@ -355,25 +357,30 @@ def on_message(client, userdata, msg):
                                 mqttc.publish(res[topic], res[payload], qos=0, retain=False)
 
                     
-def publish_device_statusupdate(device, cmd):
+def publish_device_statusupdate(device, cmd, value):
     if device in devdata.keys():
         if 'statTopic' in devdata[device].keys():
             statTopic = devdata[device].get('statTopic')
-            if verbose: log_write("MQTT publish: '" + cmd + "' -> '" + statTopic + "'")
-            mqttc.publish(statTopic, cmd, qos=0, retain=False)
+            
+            if value is not None and cmd == "%VALUE%": 
+                if verbose: log_write("    MQTT publish: '" + value + "' -> '" + statTopic + "'")
+                mqttc.publish(statTopic, value, qos=0, retain=False)
+            else: 
+                if verbose: log_write("    MQTT publish: '" + cmd + "' -> '" + statTopic + "'")
+                mqttc.publish(statTopic, cmd, qos=0, retain=False)
         
         if 'add_statTopics_on' in devdata[device].keys():
-            if verbose: log_write("  MQTT publish add_statTopics_on:")
+            if verbose: log_write("    MQTT publish add_statTopics_on:")
             for res in devdata[device].get('add_statTopics_on'):
                 if 'on_payload' in res and 'topic' in res and 'payload' in res:
                     if cmd == res['on_payload']:
-                        if verbose: log_write("    on '" + res['on_payload'] + "' -> publish '" + res['payload'] + "' on topic '" + res['topic'] + "'")
+                        if verbose: log_write("        on '" + res['on_payload'] + "' -> publish '" + res['payload'] + "' on topic '" + res['topic'] + "'")
                         mqttc.publish(res['topic'], res['payload'], qos=0, retain=False)
                         
         if 'add_statTopics' in devdata[device].keys():
-            if verbose: log_write("  MQTT publish on add_statTopics:")
+            if verbose: log_write("    MQTT publish on add_statTopics:")
             for res in devdata[device]['add_statTopics']:
-                if verbose: log_write("    '" + cmd + "' -> '" + res + "'")
+                if verbose: log_write("        '" + cmd + "' -> '" + res + "'")
                 mqttc.publish(res, cmd, qos=0, retain=False)
         
 
@@ -381,58 +388,72 @@ def publish_device_statusupdate(device, cmd):
 def parseRXCode(rx_code, source_cul):
     receivedForDevice = None
     receivedCmnd = None
+    receivedValue = None
     
     if rx_code.startswith("i"):
-        # parse InterTechno RX code
-        if debug: log_write("INTERTECHNO PROTOCOL")
+        # parse Intertechno RX code
+        if debug: log_write("    PROTOCOL: Intertechno")
         sucessfullyParsedITCode = False
         
         # look if this code is in the device_config
         if rx_code in RXcodesToDevFunction_IT:
+            if debug: log_write("    code found in device config")
             receivedForDevice = RXcodesToDevFunction_IT[rx_code][0]
             receivedCmnd = RXcodesToDevFunction_IT[rx_code][1]
             sucessfullyParsedITCode = True
-            if debug: log_write("DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
+            if debug: log_write("    DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
             if verbose: 
-                log_write("")
-                log_write("CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
+                #log_write("")
+                log_write("    CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
         
-        # if this had no result -> try again with removed last 2 chars as it is in many cases omitted as its only RSSI and not part of the code
-        rx_code = rx_code[0:-2]
-        if not sucessfullyParsedITCode and rx_code in RXcodesToDevFunction_IT:
-            receivedForDevice = RXcodesToDevFunction_IT[rx_code][0]
-            receivedCmnd = RXcodesToDevFunction_IT[rx_code][1]
+        # if this had no result -> try again with last 16 bit stripped, as in many cases this is only RSSI and not part of the code
+        # in this case treat this last 16bit as sensor value
+        rx_code_stripped = rx_code[0:-2]
+        if not sucessfullyParsedITCode and rx_code_stripped in RXcodesToDevFunction_IT:
+            if debug: log_write("    code found in device config with last 16 bit stripped")
+            receivedForDevice = RXcodesToDevFunction_IT[rx_code_stripped][0]
+            receivedCmnd = RXcodesToDevFunction_IT[rx_code_stripped][1]
+            
+            receivedValue = rx_code[-2:]
+            
+            # convert value to decimal if enabled for this device
+            if "convertValueToDecimal" in devdata[receivedForDevice]:
+                if devdata[receivedForDevice]["convertValueToDecimal"] == True:
+                    if debug: log_write("    converting value to decimal")
+                    receivedValue = str(int(receivedValue, base=16))
+            
             sucessfullyParsedITCode = True
-            if debug: log_write("DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
+            if debug: log_write("    DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", VALUE: " + receivedValue + ", RX: " + rx_code_stripped)
             if verbose: 
-                log_write("")
-                log_write("CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
+                #log_write("")
+                log_write("    CUL '" + source_cul + "' received '" + rx_code_stripped + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", VALUE: " + receivedValue)
         
-        # if this also did not work, try to parse it as "default/cheap" IT code... 
+        # if this also did not work, try to parse it as "default/cheap" Intertechno code... 
         if not sucessfullyParsedITCode:
-            receivedForDevice, receivedCmnd = decodeInterTechnoRX(rx_code)
+            if debug: log_write("    code parsed as 'default/cheap' Intertechno code")
+            receivedForDevice, receivedCmnd = decodeInterTechnoRX(rx_code_stripped)
             if debug: 
                 log_write(str(receivedForDevice) + ", " + str(receivedCmnd))
             
     else:
         # parse other/RAW RX code
-        if debug: log_write("OTHER/RAW PROTOCOL")
+        if debug: log_write("    PROTOCOL: OTHER/RAW")
         if rx_code in RXcodesToDevFunction_RAW:
             receivedForDevice = RXcodesToDevFunction_RAW[rx_code][0]
             receivedCmnd = RXcodesToDevFunction_RAW[rx_code][1]
-            if debug: log_write("DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
+            if debug: log_write("    DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
             if verbose: 
-                log_write("")
-                log_write("CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
+                #log_write("")
+                log_write("    CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
     
-    if debug: 
-        log_write("DEV: " + str(receivedForDevice) + ", CMD: " + str(receivedCmnd) + ", RX: " + rx_code)
+    #if debug: 
+    #    log_write("    DEV: " + str(receivedForDevice) + ", CMD: " + str(receivedCmnd) + ", RX: " + rx_code)
     
     if receivedForDevice != None and receivedCmnd != None and receivedForDevice != False and receivedCmnd != False:
-        publish_device_statusupdate(receivedForDevice, receivedCmnd)
+        publish_device_statusupdate(receivedForDevice, receivedCmnd, receivedValue)
 
 def decodeInterTechnoRX(rx_code):
-    # decode old fixed code from InterTechno remotes
+    # decode old fixed code from Intertechno remotes
     _housecode = None
     _devaddr = None
     _command = None
@@ -549,11 +570,11 @@ def encodeInterTechnoRX(itname, cmd):
             return _rxcode
             
         else:
-            if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
+            if debug: log_write("    unknown or invalid IT code '" + rx_code + "'")
             return False
         
     else:
-        if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
+        if debug: log_write("    unknown or invalid IT code '" + rx_code + "'")
 
 def cul_received(payload, source_cul):
     global lastReceivedTime, lastReceivedMaxAge
@@ -561,27 +582,27 @@ def cul_received(payload, source_cul):
     if payload[:2] == 'is': # msg is reply from CUL to raw send command
         pass
         
-    elif payload[:1] == 'i': # is a IT compatible command - so look it up in the code table
+    elif payload[:1] == 'i': # is a IT compatible code - so look it up in the code table
         
         if culSendsRSSI:
-            inCmd = payload[:-2] #strip last 2 chars, depending on used CUL receive mode - if enabled this is only RSSI
+            inCmd = payload[:-2] # strip last 2 chars, depending on used CUL receive mode - if enabled this is only RSSI
         else:
             inCmd = payload
             
         if verbose:
-            log_write("inCmd: " + inCmd + ", receiving CUL: " + source_cul)
+            log_write("    inCmd: " + inCmd + ", receiving CUL: " + source_cul)
         
-        # filter fast repeated commands (strip first char on IT commands as the repetation will come in as RAW without "i" prefix)
+        # filter fast repeated codes (strip first char on Intertechno codes as the repetation will come in as RAW without "i" prefix)
         ignoreCommand = False
         if inCmd in lastReceivedTime.keys():
             lastTime = int(lastReceivedTime[inCmd])
             now = int(round(time.time() * 1000))
             tdelta = (now - lastTime)
-            if debug: log_write("TDELTA = " + str(tdelta))
-            if debug: log_write("lastTime = " + str(lastTime))
+            if debug: log_write("    TDELTA = " + str(tdelta))
+            if debug: log_write("    lastTime = " + str(lastTime))
             
             if tdelta < lastReceivedMaxAge:
-                if verbose: log_write("ignoring command from CUL '" + source_cul + "', CMD: '" + inCmd + "' - already received " + str(tdelta) + " ms ago")
+                if verbose: log_write("    ignoring code from CUL '" + source_cul + "', CMD: '" + inCmd + "' - already received " + str(tdelta) + " ms ago")
                 ignoreCommand = True
         
         if not ignoreCommand:
@@ -593,37 +614,37 @@ def cul_received(payload, source_cul):
         # split string and extract last row as we dont need the rest
         splitPayload = payload.split(' ')
         actualPayload = splitPayload[len(splitPayload)-1]
-        if debug: log_write("actualPayload: '" + actualPayload)
+        if debug: log_write("    actualPayload: '" + actualPayload)
                 
         ignoreCommand = False
                 
-        # handle/filter repetations of IT commands
+        # handle/filter repetations of Intertechno codes
         isITrepetation = False
         if ('i'+actualPayload) in lastReceivedTime.keys():
             isITrepetation = True
-            if debug: log_write("IS IT REPETATION")
+            if debug: log_write("    skipping repeated Intertechno code")
             lastTime = int(lastReceivedTime['i'+actualPayload])
             now = int(round(time.time() * 1000))
             tdelta = (now - lastTime)
-            if debug: log_write("TDELTA = " + str(tdelta))
+            if debug: log_write("    TDELTA = " + str(tdelta))
             if tdelta < lastReceivedMaxAge:
-                if verbose: log_write("ignoring command from CUL '" + source_cul + "', CMD: '" + 'i'+actualPayload + "' - already received " + str(tdelta) + " ms ago")
+                if verbose: log_write("    ignoring code from CUL '" + source_cul + "', CMD: '" + 'i'+actualPayload + "' - already received " + str(tdelta) + " ms ago")
                 ignoreCommand = True
                 
-        # filter fast repeated commands
+        # filter fast repeated codes
         if not isITrepetation:
             if actualPayload in lastReceivedTime.keys():
                 lastTime = int(lastReceivedTime[actualPayload])
                 now = int(round(time.time() * 1000))
                 tdelta = (now - lastTime)
-                if debug: log_write("TDELTA = " + str(tdelta))
+                if debug: log_write("    TDELTA = " + str(tdelta))
                 if tdelta < lastReceivedMaxAge:
-                    if verbose: log_write("ignoring command from CUL '" + source_cul + "', CMD: '" + actualPayload + "' - already received " + str(tdelta) + " ms ago")
+                    if verbose: log_write("    ignoring code from CUL '" + source_cul + "', CMD: '" + actualPayload + "' - already received " + str(tdelta) + " ms ago")
                     ignoreCommand = True
         
         if not ignoreCommand:
             if isITrepetation:
-                # treat as IT command
+                # treat as Intertechno code
                 lastReceivedTime['i'+actualPayload] = int(round(time.time() * 1000))
                 parseRXCode('i'+actualPayload, source_cul)
             else:
@@ -773,7 +794,7 @@ def cul_send(device, cmd):
         else:
             log_write("WARNING: CUL send command repeated too quickly.")
             
-    publish_device_statusupdate(device, cmd)
+    publish_device_statusupdate(device, cmd, None)
 
 
 # main
@@ -794,7 +815,7 @@ if receive_from_serial_cul or send_on_serial_cul:
                 log_write("")
                 log_write("")
     else:
-        log_write("opening connection to serial CUL...")
+        log_write("opening connection to UART-CUL...")
         serLine = ""
         ser = serial.Serial(port=serialPort,baudrate=serialBaudrate,parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=serialTimeout)
         sleep(serialCulInitTimeout)
@@ -809,7 +830,7 @@ if receive_from_serial_cul or send_on_serial_cul:
             ser.write(culInitCmd.encode('ascii'))  # initialize CUL in normal receive mode
             sleep(0.5)
         else:
-            log_write("WARNING: could not connect serial CUL")
+            log_write("WARNING: could not connect UART-CUL")
             receive_from_serial_cul = False
             send_on_serial_cul = False
             serialCULAvailable = False
@@ -845,7 +866,7 @@ while True:
             else:
                 if verbose:
                     log_write("")
-                    log_write("Serial-CUL RX: '" + recvCmd + "'")
+                    log_write("UART-CUL RX: '" + recvCmd + "'")
                 cul_received(recvCmd, "UART")
         
     sleep(0.05)

+ 8 - 0
cul2mqtt_devices_example_value.yml

@@ -0,0 +1,8 @@
+"GASZ":
+  name: "Gaszaehler Impuls"
+  "RX":
+    "%VALUE%": "i203C"
+    #"IMP": "i203C00"
+    #"BATT": "i203C64"
+  "statTopic": "D9/Gaszaehler/Impuls"
+  "convertValueToDecimal": True

+ 16 - 6
cul2mqtt_example.ini

@@ -4,9 +4,6 @@ log_path = /home/pi/logs/cul2mqtt
 log_enable = true
 filterSelfSentIncomingTimeout = 100
 
-# UART or MQTT
-TX_interface_prefer = MQTT
-
 # also used as min repeat time, in ms
 lastReceivedMaxAge = 500
 
@@ -17,14 +14,27 @@ serialPort = /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
 serialBaudrate = 38400
 serialTimeout = 1
 serialCulInitTimeout = 3
-forceSerialCULConnected = False
+
+# enable UART CUL
 receive_from_serial_cul = True
 send_on_serial_cul = True
-culInitCmd = X05
-culSendsRSSI = False
+
+# enable MQTT connected CUL
 receive_from_mqtt_cul = True
 send_on_mqtt_cul = True
 
+# prefer UART or MQTT CUL for TX (UART, MQTT or both)
+TX_interface_prefer = UART
+
+# force usage of UART CUL (program will exit if connect fails, so it can be restarted by systemd)
+forceSerialCULConnected = False
+
+# CUL init sequence - normally X21 (Intertechno only) or X05 (includes RAW/non-Intertechno devices)
+culInitCmd = X05
+
+# attention - will break sensors which send values if set to True
+culSendsRSSI = False
+
 # for "classic" MQTT-CUL
 #mqtt_cul_topic_received = MQTTCUL/received
 #mqtt_cul_topic_send = MQTTCUL/send