Browse Source

2020-12-09:
- fixed bug: program terminated when UART-CUL enabled/configured but not available
- fix bug in MQTT input when using 'add_statTopics_on'
- added: initialize MQTT-CUL with configured culInitCmd (i.E. "X21", "X05") on MQTT connect
- added support for certain remote controls that aren´t directly supported by a-culfw by parsing RAW data (a-culfw initialized with X05 command)
- added repeated commands
- added ini configuration using configparser
- lots of improvements and fixes
- README updated

FloKra 3 years ago
parent
commit
eb03e5b8bb
7 changed files with 656 additions and 200 deletions
  1. 10 0
      CHANGELOG.txt
  2. 0 0
      CONFIG-EXAMPLE.txt
  3. 25 0
      README.md
  4. 34 0
      src/cul2mqtt.ini
  5. 244 199
      src/cul2mqtt.py
  6. 1 1
      src/cul2mqtt.service
  7. 342 0
      src/cul2mqtt_devices.yml

+ 10 - 0
CHANGELOG.txt

@@ -1,3 +1,13 @@
+2020-12-09:
+  - fixed bug: program terminated when UART-CUL enabled/configured but not available
+  - fix bug in MQTT input when using 'add_statTopics_on'
+  - added: initialize MQTT-CUL with configured culInitCmd (i.E. "X21", "X05") on MQTT connect
+  - added support for certain remote controls that aren´t directly supported by a-culfw by parsing RAW data (a-culfw initialized with X05 command)
+  - added repeated commands
+  - added ini configuration using configparser
+  - lots of improvements and fixes
+  - README updated
+
 2020-11-30:
 2020-11-30:
   - integrated support for InterTechno remotes/devices with fixed code
   - integrated support for InterTechno remotes/devices with fixed code
     no need to specify RX/TX commands, just specify a device ID like IT_A1, IT_C3 etc. 
     no need to specify RX/TX commands, just specify a device ID like IT_A1, IT_C3 etc. 

+ 0 - 0
README.txt → CONFIG-EXAMPLE.txt


+ 25 - 0
README.md

@@ -0,0 +1,25 @@
+# CUL2MQTT
+
+Connector for CUL RF-Transceiver to MQTT written in Python. 
+
+Designed as a bridge between more modern MQTT, HomeAssistant and ESP8266/Tasmota based home automation solutions and old fashioned 433 MHz RF remotes (and also some RF relay plugs). 
+I developed this as I moved to Home Assistant, MQTT and Node-RED for all of my home automation tasks, and still wanted to use these tiny, handy and cheap RF remotes, although none of their counterparts (RF plugs) were still used. Apart from that, there is a number of cheap RF controlled ceiling fan controllers available that can be integrated with this solution. 
+
+
+
+Features:
+
+- integrate 433MHz RF remote controls in MQTT based home automation systems like Home Assistant and Node-RED
+- control RF remote plugs and other devices such as ceiling fan controllers with MQTT messages while getting state updates whenever the original remote is used
+- full integration of InterTechno fixed code protocol (receiving, sending and automatic TX code generation supported)
+- supports InterTechno compatible learning code protocols, i.E. EV1527 based remotes (receiving, sending and automatic TX code generation supported)
+- basic support for other 433 MHz RF remotes - including many self learning models (for receiving only)
+- developed for use with CUL transceivers with firmware **[a-culfw](https://github.com/heliflieger/a-culfw)**
+- supports 2 CUL transceivers concurrently - one connected directly via UART, and another one via MQTT-UART-Bridge (link to my project to be posted), in order to improve site coverage and reliability, where preferred TX interface can be defined per device
+- intended for running on a Raspberry Pi or similar
+- devices configuration using YAML file, base configuration using INI file
+
+
+
+
+

+ 34 - 0
src/cul2mqtt.ini

@@ -0,0 +1,34 @@
+[main]
+devices_config_yml = cul2mqtt_devices.yml
+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
+
+lastSentMinInterval = 1500
+
+[cul]
+serialPort = /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
+serialBaudrate = 38400
+serialTimeout = 1
+serialCulInitTimeout = 3
+forceSerialCULConnected = False
+receive_from_serial_cul = True
+send_on_serial_cul = True
+culInitCmd = X05
+culSendsRSSI = False
+receive_from_mqtt_cul = True
+send_on_mqtt_cul = True
+mqtt_cul_topic_received = MQTTCUL/received
+mqtt_cul_topic_send = MQTTCUL/send
+
+[mqtt]
+server = mqtt.lan
+port = 1883
+user = mqttuser
+password = xxxxxx

+ 244 - 199
src/cul2mqtt.py

@@ -15,57 +15,83 @@ import datetime
 import paho.mqtt.client as mqtt
 import paho.mqtt.client as mqtt
 import json
 import json
 import os
 import os
+import re
+import configparser
 
 
-version = 0.2
+version = 0.3
 
 
+# Change working dir to the same dir as this script
+os.chdir(sys.path[0])
+
+config = configparser.ConfigParser()
+config.read('cul2mqtt.ini')
+
+# global variables
 verbose = False
 verbose = False
 debug = False
 debug = False
+quiet = True
+
+serialCULAvailable = False
+devdata = {}
+RXcodesToDevFunction_IT = {}
+RXcodesToDevFunction_RAW = {}
+InTopicsToDevIds = {}
 
 
-deviceConfigFile = "/home/pi/cul2mqtt_devices.yaml"
 
 
-#Log levels: DEBUG, INFO, WARNING, ERROR or CRITICAL
-#loglevel = "DEBUG"
-log_enable = True
-log_path = '/home/pi/logs/cul2mqtt'
+# config vars
+deviceConfigFile = config['main'].get('devices_config_yml')
 
 
-mqtt_server = "10.1.1.11"
-mqtt_port = 1883
-mqtt_user = "admin"
-mqtt_password = "2Bierum4"
+log_enable = config['main'].getboolean('log_enable')
+log_path = config['main'].get('log_path')
+if not os.path.exists(log_path):
+    os.makedirs(log_path)
 
 
-TX_interface_prefer = "MQTT" # UART or MQTT
+mqtt_server = config['mqtt'].get('server')
+mqtt_port = config['mqtt'].getint('port')
+mqtt_user = config['mqtt'].get('user')
+mqtt_password = config['mqtt'].get('password')
 
 
-repeat_received_commands = True # not yet implemented
+TX_interface_prefer = config['main'].get('TX_interface_prefer') # UART or MQTT
+
+repeat_received_commands = False # not yet implemented
 
 
 if len(sys.argv) >= 2:
 if len(sys.argv) >= 2:
     if sys.argv[1] == "-q":
     if sys.argv[1] == "-q":
         verbose = False
         verbose = False
+        debug = False
+        quiet = True
     elif sys.argv[1] == "-v":
     elif sys.argv[1] == "-v":
         verbose = True
         verbose = True
+        debug = False
+        quiet = False
+    elif sys.argv[1] == "-d":
+        verbose = True
+        debug = True
+        quiet = False
 
 
 # serial (USB) CUL device
 # serial (USB) CUL device
-receive_from_serial_cul = True
-send_on_serial_cul = True
-serialPort = '/dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0'
-serialBaudrate = 38400
-serialTimeout = 1
-culInitCmd = 'X21\r\n' # CUL init command for normal operation
-culInitTimeout = 3
-forceSerialCULConnected = False
-serialCULAvailable = False
+receive_from_serial_cul = config['cul'].getboolean('receive_from_serial_cul')
+send_on_serial_cul = config['cul'].getboolean('send_on_serial_cul')
+serialPort = config['cul'].get('serialPort')
+serialBaudrate = config['cul'].getint('serialBaudrate')
+serialTimeout = config['cul'].getint('serialTimeout')
 
 
-# MQTT CUL
-receive_from_mqtt_cul = True
-send_on_mqtt_cul = True
-mqtt_topic_cul_received = "T5/CUL/received"
-mqtt_topic_cul_send = "T5/CUL/send"
 
 
+# CUL init command for normal operation, i.E. X21, X05 
+culInitCmd = config['cul'].get('culInitCmd') + '\r\n'
+culSendsRSSI = config['cul'].getboolean('culSendsRSSI') # set depending on culInitCmd chosen
 
 
-filterSelfSentIncomingTimeout = 100
+serialCulInitTimeout = config['cul'].getint('serialCulInitTimeout')
+forceSerialCULConnected = config['cul'].getboolean('forceSerialCULConnected')
+
+# MQTT CUL
+receive_from_mqtt_cul = config['cul'].getboolean('receive_from_mqtt_cul')
+send_on_mqtt_cul = config['cul'].getboolean('send_on_mqtt_cul')
+mqtt_cul_topic_received = config['cul'].get('mqtt_cul_topic_received')
+mqtt_cul_topic_send = config['cul'].get('mqtt_cul_topic_send')
+
+filterSelfSentIncomingTimeout = config['main'].get('filterSelfSentIncomingTimeout')
 
 
-devdata = {}
-RXcodesToDevFunction = {}
-InTopicsToDevIds = {}
 
 
 try:
 try:
     from yaml import CLoader as Loader
     from yaml import CLoader as Loader
@@ -98,7 +124,6 @@ def log_start():
             
             
         try:
         try:
             _log_current_date = strftime("%Y%m%d")
             _log_current_date = strftime("%Y%m%d")
-            #_logfilename = _log_current_date + "_" + logfile_name
             _logfilename = _log_current_date + ".log"
             _logfilename = _log_current_date + ".log"
             logfilehandle = open(log_path + '/' + _logfilename, 'a')
             logfilehandle = open(log_path + '/' + _logfilename, 'a')
             log_last_date = _log_current_date
             log_last_date = _log_current_date
@@ -123,7 +148,7 @@ def log_rotate():
             
             
 def log_write(_msg):
 def log_write(_msg):
     global logfilehandle
     global logfilehandle
-    print(_msg)
+    if not quiet: print(_msg)
     log_rotate()
     log_rotate()
     if log_enable:
     if log_enable:
         try:
         try:
@@ -162,14 +187,25 @@ try:
                         log_write("RX codes:")
                         log_write("RX codes:")
                     for key, value in devdata[deviceid]["RX"].items():
                     for key, value in devdata[deviceid]["RX"].items():
                         if debug:
                         if debug:
-                            log_write(key, "->", value)
-                        if value in RXcodesToDevFunction.keys():
-                            log_write("")
-                            log_write("")
-                            log_write("ERROR: RX-string '" + str(value) + "' is already defined for another device! Must be unique.")
-                            raise
+                            log_write(str(key) + "->" + str(value))
+                        if value.startswith("i"):
+                            # is InterTechno RX code
+                            if value in RXcodesToDevFunction_IT.keys():
+                                log_write("")
+                                log_write("")
+                                log_write("ERROR: RX-string '" + str(value) + "' is already defined for another device! Must be unique.")
+                                raise
+                            else:
+                                RXcodesToDevFunction_IT[value] = deviceid, key
                         else:
                         else:
-                            RXcodesToDevFunction[value] = deviceid, key
+                            # is other RX code - lets call it RAW
+                            if value in RXcodesToDevFunction_RAW.keys():
+                                log_write("")
+                                log_write("")
+                                log_write("ERROR: RX-string '" + str(value) + "' is already defined for another device! Must be unique.")
+                                raise
+                            else:
+                                RXcodesToDevFunction_RAW[value] = deviceid, key
             
             
             if "cmdTopic" in devdata[deviceid].keys():
             if "cmdTopic" in devdata[deviceid].keys():
                 if devdata[deviceid]["cmdTopic"] != "":
                 if devdata[deviceid]["cmdTopic"] != "":
@@ -188,8 +224,12 @@ try:
             log_write("")
             log_write("")
             log_write("")
             log_write("")
             log_write("")
             log_write("")
-            log_write("RXcodesToDevFunction:")
-            log_write(RXcodesToDevFunction)
+            log_write("RXcodesToDevFunction_IT:")
+            log_write(RXcodesToDevFunction_IT)
+            log_write("")
+            log_write("")
+            log_write("RXcodesToDevFunction_RAW:")
+            log_write(RXcodesToDevFunction_RAW)
             log_write("")
             log_write("")
             log_write("")
             log_write("")
             log_write("InTopicsToDevIds:")
             log_write("InTopicsToDevIds:")
@@ -200,6 +240,10 @@ try:
             log_write(InTopicsToDevIds.keys())
             log_write(InTopicsToDevIds.keys())
             log_write("")
             log_write("")
             log_write("")
             log_write("")
+            log_write("devdata.keys():")
+            log_write(devdata.keys())
+            log_write("")
+            log_write("")
             log_write("==== parsing config file complete ====")
             log_write("==== parsing config file complete ====")
             log_write("")
             log_write("")
             log_write("")
             log_write("")
@@ -212,36 +256,22 @@ except ConstructorError as err:
     exit(1)
     exit(1)
 except:
 except:
     log_write("ERROR opening configfile")
     log_write("ERROR opening configfile")
+    log_write("Unexpected error: " + str(sys.exc_info()[0]))
     exit(1)
     exit(1)
 
 
 
 
+lastReceivedMaxAge = config['main'].getint('lastReceivedMaxAge') # ignore repeated messages when they are younger than x ms
 
 
-## #mqtt_topic_in = "cul/in"
-## 
-## 
-lastReceived = ["","","","","","","","","",""]
-lastReceivedTime = [0,0,0,0,0,0,0,0,0,0]
-lastReceivedIndex = 0
-lastReceivedMaxAge = 500 # ignore repeated messages when they are younger than x ms
+lastReceivedTime = dict()
 
 
 lastSentTime = {}
 lastSentTime = {}
-lastSentMinInterval = 1500 # ignore repeated messages when they are younger than x ms, should be > 1500 
+lastSentMinInterval = config['main'].getint('lastSentMinInterval') # ignore repeated messages when they are younger than x ms, should be > 1500 
 
 
 lastSentCmd = ""
 lastSentCmd = ""
 lastSentCmdTime = 0
 lastSentCmdTime = 0
 lastSentDev = ""
 lastSentDev = ""
 lastSentDevCmd = ""
 lastSentDevCmd = ""
 
 
-## 
-## 
-## 
-## global lastAction_bwm1
-## global lastAction_bwm2
-## global lastAction_bwm3
-## lastAction_bwm1 = 0
-## lastAction_bwm2 = 0
-## lastAction_bwm3 = 0
-
 def touch(fname, times=None):
 def touch(fname, times=None):
     with open(fname, 'a'):
     with open(fname, 'a'):
         os.utime(fname, times)
         os.utime(fname, times)
@@ -250,8 +280,12 @@ def on_connect(client, userdata, flags, rc):
     if verbose:
     if verbose:
         log_write("MQTT connected with result code " + str(rc))
         log_write("MQTT connected with result code " + str(rc))
     if receive_from_mqtt_cul:
     if receive_from_mqtt_cul:
-        if mqtt_topic_cul_received != "":
-            client.subscribe(mqtt_topic_cul_received)
+        if mqtt_cul_topic_received != "":
+            client.subscribe(mqtt_cul_topic_received)
+            
+            if mqtt_cul_topic_send != "":
+                #client.publish
+                mqttc.publish(mqtt_cul_topic_send, culInitCmd, qos=0, retain=False)
             
             
     for in_topic in InTopicsToDevIds.keys():
     for in_topic in InTopicsToDevIds.keys():
         if in_topic != "":
         if in_topic != "":
@@ -265,20 +299,13 @@ def on_disconnect(client, userdata, rc):
         
         
 def on_message(client, userdata, msg):
 def on_message(client, userdata, msg):
     #print(msg.topic + ": " + str(msg.payload))
     #print(msg.topic + ": " + str(msg.payload))
-    
     payload = msg.payload.decode("utf-8")
     payload = msg.payload.decode("utf-8")
     
     
     if verbose:
     if verbose:
         log_write("MQTT received: " + msg.topic + " -> " + str(payload))
         log_write("MQTT received: " + msg.topic + " -> " + str(payload))
     
     
-    #log_write("")
-    #print("TOPIC: ")
-    #print(msg.topic)
-    #print("TOPIC REC: ")
-    #print(mqtt_topic_cul_received)
-    
     # MQTT message is output from CUL
     # MQTT message is output from CUL
-    if receive_from_mqtt_cul and msg.topic == mqtt_topic_cul_received:
+    if receive_from_mqtt_cul and msg.topic == mqtt_cul_topic_received:
         payload = payload.rstrip()
         payload = payload.rstrip()
         if verbose:
         if verbose:
             log_write("")
             log_write("")
@@ -288,33 +315,11 @@ def on_message(client, userdata, msg):
     else:
     else:
         for in_topic, dev in InTopicsToDevIds.items():
         for in_topic, dev in InTopicsToDevIds.items():
             if msg.topic == in_topic:
             if msg.topic == in_topic:
-                #log_write("")
-                log_write("MQTT received - '" + msg.topic + "' = '" + payload + "' => DEV: " + dev)
-                #print(devdata[dev])
+                if verbose: log_write("MQTT received - '" + msg.topic + "' = '" + payload + "' => DEV: " + dev)
                 
                 
                 if 'name' in devdata[dev].keys():
                 if 'name' in devdata[dev].keys():
                     log_write('devName: ' + devdata[dev]['name'])
                     log_write('devName: ' + devdata[dev]['name'])
                 
                 
-                if 'statTopic' in devdata[dev].keys():
-                    log_write('statTopic: ' + devdata[dev]['statTopic'])
-                    mqttc.publish(devdata[dev]['statTopic'], payload, qos=0, retain=False)
-                    
-                if 'add_statTopics_on' in devdata[dev].keys():
-                    log_write("add_statTopics_on:")
-                    for res in devdata[dev]['add_statTopics_on']:
-                        #print(res)
-                        if 'on_payload' in res and 'topic' in res and 'payload' in res:
-                            if payload == res['on_payload'] and payload != "" and res['topic'] != "" and res['payload'] != "":
-                                log_write("  on '" + payload + "': '" + res['payload'] + "' => '" + res['topic'] + "'")
-                                mqttc.publish(res[topic], res[payload], qos=0, retain=False)
-                                
-                #if 'TX' in devdata[dev].keys():
-                #    if payload in devdata[dev]['TX']:
-                #        txstring = devdata[dev]['TX'][payload]
-                #        print("TX code for '" + payload + "' is: " + txstring)
-                
-                TX_interface = TX_interface_prefer
-                
                 global lastSentDev, lastSentDevCmd, lastSentCmdTime
                 global lastSentDev, lastSentDevCmd, lastSentCmdTime
                 now = int(round(time.time() * 1000))
                 now = int(round(time.time() * 1000))
                 if debug:
                 if debug:
@@ -322,41 +327,41 @@ def on_message(client, userdata, msg):
                 if dev == lastSentDev and payload == lastSentDevCmd and (now - lastSentCmdTime) < 1000:
                 if dev == lastSentDev and payload == lastSentDevCmd and (now - lastSentCmdTime) < 1000:
                     if verbose:
                     if verbose:
                         log_write("MQTT: ignored command as we just sent this.")
                         log_write("MQTT: ignored command as we just sent this.")
-                    pass
                 else:
                 else:
                     cul_send(dev, payload)
                     cul_send(dev, payload)
-                    #if 'TX_interface' in devdata[dev].keys():
-                    #    print("TX_interface: " + devdata[dev]['TX_interface'])
-                    #    
-                    #if TX_interface == "MQTT" or TX_interface == "both":
-                    #    print("TX via MQTT")
-                    #    cul_send(dev, payload)
-                    #    
-                    #if TX_interface == "UART" or TX_interface == "both":
-                    #    print("TX via UART")
-                    #    cul_send(dev, payload)
+                    
+                if 'statTopic' in devdata[dev].keys():
+                    if verbose: log_write('statTopic: ' + devdata[dev]['statTopic'])
+                    mqttc.publish(devdata[dev]['statTopic'], payload, qos=0, retain=False)
+                    
+                if 'add_statTopics_on' in devdata[dev].keys():
+                    if verbose: log_write("add_statTopics_on:")
+                    for res in devdata[dev]['add_statTopics_on']:
+                        if 'on_payload' in res and 'topic' in res and 'payload' in res:
+                            if payload == res['on_payload'] and payload != "" and res['topic'] != "" and res['payload'] != "":
+                                if verbose: log_write("  on '" + payload + "': '" + res['payload'] + "' => '" + res['topic'] + "'")
+                                mqttc.publish(res[topic], res[payload], qos=0, retain=False)
 
 
                     
                     
 def publish_device_statusupdate(device, cmd):
 def publish_device_statusupdate(device, cmd):
     if device in devdata.keys():
     if device in devdata.keys():
         if 'statTopic' in devdata[device].keys():
         if 'statTopic' in devdata[device].keys():
-            statTopic = devdata[device]['statTopic']
-            log_write("MQTT publish: '" + cmd + "' -> '" + statTopic + "'")
+            statTopic = devdata[device].get('statTopic')
+            if verbose: log_write("MQTT publish: '" + cmd + "' -> '" + statTopic + "'")
             mqttc.publish(statTopic, cmd, qos=0, retain=False)
             mqttc.publish(statTopic, cmd, qos=0, retain=False)
         
         
         if 'add_statTopics_on' in devdata[device].keys():
         if 'add_statTopics_on' in devdata[device].keys():
-            log_write("  MQTT publish add_statTopics_on:")
-            for res in devdata[device]['add_statTopics_on']:
-                #print(res)
+            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 'on_payload' in res and 'topic' in res and 'payload' in res:
                     if cmd == res['on_payload']:
                     if cmd == res['on_payload']:
-                        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)
                         mqttc.publish(res['topic'], res['payload'], qos=0, retain=False)
                         
                         
         if 'add_statTopics' in devdata[device].keys():
         if 'add_statTopics' in devdata[device].keys():
-            log_write("  MQTT publish on add_statTopics:")
+            if verbose: log_write("  MQTT publish on add_statTopics:")
             for res in devdata[device]['add_statTopics']:
             for res in devdata[device]['add_statTopics']:
-                log_write("    '" + cmd + "' -> '" + res + "'")
+                if verbose: log_write("    '" + cmd + "' -> '" + res + "'")
                 mqttc.publish(res, cmd, qos=0, retain=False)
                 mqttc.publish(res, cmd, qos=0, retain=False)
         
         
 
 
@@ -364,17 +369,32 @@ def publish_device_statusupdate(device, cmd):
 def parseRXCode(rx_code, source_cul):
 def parseRXCode(rx_code, source_cul):
     receivedForDevice = None
     receivedForDevice = None
     receivedCmnd = None
     receivedCmnd = None
-    if rx_code in RXcodesToDevFunction:
-        receivedForDevice = RXcodesToDevFunction[rx_code][0]
-        receivedCmnd = RXcodesToDevFunction[rx_code][1]
-        #print("receivedForDevice = " + receivedForDevice + ", receivedCmnd = " + receivedCmnd)
-        #log_write("DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
-        log_write("")
-        log_write("CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
-    else:
-        if rx_code.startswith("i"):
+    if rx_code.startswith("i"):
+        # parse InterTechno RX code
+        if debug: log_write("INTERTECHNO PROTOCOL")
+        if rx_code in RXcodesToDevFunction_IT:
+            receivedForDevice = RXcodesToDevFunction_IT[rx_code][0]
+            receivedCmnd = RXcodesToDevFunction_IT[rx_code][1]
+            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)
+        else:
             receivedForDevice, receivedCmnd = decodeInterTechnoRX(rx_code)
             receivedForDevice, receivedCmnd = decodeInterTechnoRX(rx_code)
-            print(receivedForDevice, receivedCmnd)
+            if debug: log_write(receivedForDevice + ", " + receivedCmnd)
+    else:
+        # parse other/RAW RX code
+        if debug: log_write("OTHER/RAW PROTOCOL")
+        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 verbose: 
+                log_write("")
+                log_write("CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
+    
+    if debug: 
+        log_write("DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
     
     
     if receivedForDevice != None and receivedCmnd != None:
     if receivedForDevice != None and receivedCmnd != None:
         publish_device_statusupdate(receivedForDevice, receivedCmnd)
         publish_device_statusupdate(receivedForDevice, receivedCmnd)
@@ -431,17 +451,15 @@ def decodeInterTechnoRX(rx_code):
         
         
         if _housecode != None and _devaddr != None and _command != None:
         if _housecode != None and _devaddr != None and _command != None:
             _itname = "IT_" + _housecode + _devaddr
             _itname = "IT_" + _housecode + _devaddr
-            #print("valid IT code: '" + _itname + "' => '" + _command + "'")
+            if debug: log_write("valid IT code: '" + _itname + "' => '" + _command + "'")
             return _itname, _command
             return _itname, _command
             
             
         else:
         else:
+            if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
             return False
             return False
-            
-        #else:
-        #    print("unknown or invalid IT code '" + rx_code + "'")
-        
-    #else:
-    #    print("unknown or invalid IT code '" + rx_code + "'")
+
+    else:
+        if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
 
 
 def encodeInterTechnoRX(itname, cmd):
 def encodeInterTechnoRX(itname, cmd):
     # decode old fixed code from InterTechno remotes
     # decode old fixed code from InterTechno remotes
@@ -491,7 +509,7 @@ def encodeInterTechnoRX(itname, cmd):
         if cmd == "ON": _command = "15"
         if cmd == "ON": _command = "15"
         elif cmd == "OFF": _command = "14"
         elif cmd == "OFF": _command = "14"
         
         
-        #print("IT housecode=", _housecode, "- devaddr=", _devaddr, "- command=", _command)
+        if debug: print("IT housecode=", _housecode, "- devaddr=", _devaddr, "- command=", _command)
                 
                 
         if _housecode != None and _devaddr != None and _command != None:
         if _housecode != None and _devaddr != None and _command != None:
             _rxcode = "i" + _housecode + _devaddr + _command
             _rxcode = "i" + _housecode + _devaddr + _command
@@ -499,55 +517,86 @@ def encodeInterTechnoRX(itname, cmd):
             return _rxcode
             return _rxcode
             
             
         else:
         else:
+            if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
             return False
             return False
-            
-        #else:
-        #    print("unknown or invalid IT code '" + rx_code + "'")
         
         
-    #else:
-    #    print("unknown or invalid IT code '" + rx_code + "'")    
+    else:
+        if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
 
 
 def cul_received(payload, source_cul):
 def cul_received(payload, source_cul):
-    #global lastAction_bwm1, lastAction_bwm2, lastAction_bwm3
-    global lastReceivedTime, lastReceivedIndex, lastReceivedMaxAge
-    
-    #actionSupported = True
-    isSendReply = False
+    global lastReceivedTime, lastReceivedMaxAge
     
     
     if payload[:2] == 'is': # msg is reply from CUL to raw send command
     if payload[:2] == 'is': # msg is reply from CUL to raw send command
-        isSendReply = True
+        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 command - so look it up in the code table
-        inCmd = payload[:-2] #strip last 2 chars, these only represent signal strength and are not needed
+        
+        if culSendsRSSI:
+            inCmd = payload[:-2] #strip last 2 chars, depending on used CUL receive mode - if enabled this is only RSSI
+        else:
+            inCmd = payload
+            
         if verbose:
         if verbose:
-            log_write("inCmd: " + inCmd + ", 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)
         ignoreCommand = False
         ignoreCommand = False
-        lastReceivedLength = len(lastReceived) 
-        i = 0
-        while i < lastReceivedLength:
-            #print(str(i) + ": " + lastReceived[i])
-            if lastReceived[i] == inCmd:
-                lastTime = int(lastReceivedTime[i])
+        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 tdelta < lastReceivedMaxAge:
+                if verbose: log_write("ignoring command from CUL '" + source_cul + "', CMD: '" + inCmd + "' - already received " + str(tdelta) + " ms ago")
+                ignoreCommand = True
+        
+        if not ignoreCommand:
+            lastReceivedTime[inCmd] = int(round(time.time() * 1000))
+            parseRXCode(inCmd, source_cul)
+            
+    elif payload[:1] == 'p': # is RAW data
+        # example: "p11  288  864  800  320  288  832  33  1  4 1   288 10224     0 A4CEF09580"
+        # 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)
+                
+        ignoreCommand = False
+                
+        # handle/filter repetations of IT commands
+        isITrepetation = False
+        if ('i'+actualPayload) in lastReceivedTime.keys():
+            isITrepetation = True
+            if debug: log_write("IS IT REPETATION")
+            lastTime = int(lastReceivedTime['i'+actualPayload])
+            now = int(round(time.time() * 1000))
+            tdelta = (now - lastTime)
+            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")
+                ignoreCommand = True
+                
+        # filter fast repeated commands
+        if not isITrepetation:
+            if actualPayload in lastReceivedTime.keys():
+                lastTime = int(lastReceivedTime[actualPayload])
                 now = int(round(time.time() * 1000))
                 now = int(round(time.time() * 1000))
                 tdelta = (now - lastTime)
                 tdelta = (now - lastTime)
-                #print("TDELTA = " + str(tdelta))
+                if debug: log_write("TDELTA = " + str(tdelta))
                 if tdelta < lastReceivedMaxAge:
                 if tdelta < lastReceivedMaxAge:
-                    log_write("ignoring command from CUL '" + source_cul + "', CMD: '" + inCmd + "' - already received " + str(tdelta) + " ms ago")
+                    if verbose: log_write("ignoring command from CUL '" + source_cul + "', CMD: '" + actualPayload + "' - already received " + str(tdelta) + " ms ago")
                     ignoreCommand = True
                     ignoreCommand = True
-                    #break
-            i += 1
         
         
         if not ignoreCommand:
         if not ignoreCommand:
-            lastReceived[lastReceivedIndex] = inCmd
-            lastReceivedTime[lastReceivedIndex] = int(round(time.time() * 1000))
-            if lastReceivedIndex >= (lastReceivedLength - 1):
-                lastReceivedIndex = 0
+            if isITrepetation:
+                # treat as IT command
+                lastReceivedTime['i'+actualPayload] = int(round(time.time() * 1000))
+                parseRXCode('i'+actualPayload, source_cul)
             else:
             else:
-                lastReceivedIndex += 1
-                
-            parseRXCode(inCmd, source_cul)
+                lastReceivedTime[actualPayload] = int(round(time.time() * 1000))
+                parseRXCode(actualPayload, source_cul)
                             
                             
             #if repeat_received_commands:
             #if repeat_received_commands:
             #    lastSentLength = len(lastSent)
             #    lastSentLength = len(lastSent)
@@ -617,47 +666,41 @@ def cul_send(device, cmd):
     global lastSentTime
     global lastSentTime
     culCmd = ""
     culCmd = ""
     culSendCmdsKeyName = device + ' ' + cmd
     culSendCmdsKeyName = device + ' ' + cmd
-    #print culSendCmdsKeyName
-    
-    #statTopic = False
-    #if 'statTopic' in devdata[device].keys():
-    #    statTopic = devdata[device]['statTopic']
-    #    #print('statTopic: ' + devdata[device]['statTopic'])
-    #if statTopic:
-    #    mqttc.publish(statTopic, cmd, qos=0, retain=False)
-    #    print("    MQTT: published '" + cmd + "' on statTopic: '" + devdata[device]['statTopic'])
-    
-    publish_device_statusupdate(device, cmd)
-    
+    if debug: log_write("CUL send '" + cmd + "' to device '" + device + "'")
+            
     tx_code = False
     tx_code = False
     
     
     if 'TX' in devdata[device]:
     if 'TX' in devdata[device]:
-        #print("TX data available, cmd="+cmd)
-        #print(devdata[device]['TX'])
-        if cmd in devdata[device]['TX']:
+        if debug: print("TX data available, cmd="+cmd)
+        if debug: print(devdata[device]['TX'])
+        if cmd in devdata[device]['TX'].keys():
             tx_code = devdata[device]['TX'][cmd]
             tx_code = devdata[device]['TX'][cmd]
-            log_write("    TX code for '" + cmd + "': " + tx_code)
-            
+            if verbose: log_write("    TX code for '" + cmd + "': " + tx_code)
+    
     if not tx_code:
     if not tx_code:
-        print("    deviceID: ", device)
-        if 'RX' in devdata[device]:
-            print("    RX code configured, cmd=" + cmd)
-            print(devdata[device]['RX'])
-            if cmd in devdata[device]['RX']:
+        if verbose: log_write("    deviceID: " + device)
+        if 'RX' in devdata[device].keys():
+            if verbose: 
+                log_write("    RX code configured, cmd=" + cmd)
+                log_write(devdata[device]['RX'])
+            if cmd in devdata[device]['RX'].keys():
                 rx_code = devdata[device]['RX'][cmd]
                 rx_code = devdata[device]['RX'][cmd]
-                print("    RX code for '" + cmd + "': " + rx_code)
+                if debug: log_write("    RX code for '" + cmd + "': " + rx_code)
                 tx_code = IT_RXtoTXCode(rx_code)
                 tx_code = IT_RXtoTXCode(rx_code)
-                log_write("    TX code for '" + cmd + "': " + tx_code)
+                if verbose: log_write("    TX code for '" + cmd + "': " + tx_code)
+            else: 
+                log_write("    RX code for '" + cmd + "' NOT FOUND")
+                
         elif device.startswith("IT_"):
         elif device.startswith("IT_"):
             # InterTechno device with fixed code - encode RX code for IT device name and convert to TX code
             # InterTechno device with fixed code - encode RX code for IT device name and convert to TX code
             rx_code = encodeInterTechnoRX(device, cmd)
             rx_code = encodeInterTechnoRX(device, cmd)
             if rx_code:
             if rx_code:
-                print("    RX code for '" + cmd + "': " + rx_code)
+                if debug: log_write("    RX code for '" + cmd + "': " + rx_code)
                 tx_code = IT_RXtoTXCode(rx_code)
                 tx_code = IT_RXtoTXCode(rx_code)
-                log_write("    TX code for '" + cmd + "': " + tx_code)
+                if verbose: log_write("    TX code for '" + cmd + "': " + tx_code)
             
             
     if not tx_code:
     if not tx_code:
-        log_write("    no valid TX code for this device/command")
+        if verbose: log_write("    no valid TX code for this device/command")
     else:
     else:
         now = int(round(time.time() * 1000))
         now = int(round(time.time() * 1000))
         
         
@@ -668,14 +711,14 @@ def cul_send(device, cmd):
             lastTime = 0
             lastTime = 0
             
             
         lastTimeAge = now - lastTime
         lastTimeAge = now - lastTime
-        log_write('    lastTime: ' + str(lastTimeAge) + 'ms ago')
+        if verbose: log_write('    lastTime: ' + str(lastTimeAge) + 'ms ago')
         
         
         if lastTimeAge > lastSentMinInterval: # only send if last time + min interval is exceeded
         if lastTimeAge > lastSentMinInterval: # only send if last time + min interval is exceeded
             lastSentTime[culSendCmdsKeyName] = now # save what we send, so that we dont repeat our own sent messages if repeating is enabled
             lastSentTime[culSendCmdsKeyName] = now # save what we send, so that we dont repeat our own sent messages if repeating is enabled
             
             
             TX_interface = TX_interface_prefer
             TX_interface = TX_interface_prefer
             if 'TX_interface' in devdata[device].keys():
             if 'TX_interface' in devdata[device].keys():
-                log_write("    TX_interface: " + devdata[device]['TX_interface'])
+                if verbose: log_write("    TX_interface: " + devdata[device]['TX_interface'])
                 
                 
             if TX_interface == "UART" and not serialCULAvailable:
             if TX_interface == "UART" and not serialCULAvailable:
                 TX_interface = "MQTT"
                 TX_interface = "MQTT"
@@ -688,23 +731,24 @@ def cul_send(device, cmd):
             
             
             if send_on_mqtt_cul and (TX_interface == "MQTT" or TX_interface == "both"):
             if send_on_mqtt_cul and (TX_interface == "MQTT" or TX_interface == "both"):
                 log_write("    TX via MQTT: " + tx_code)
                 log_write("    TX via MQTT: " + tx_code)
-                mqttc.publish(mqtt_topic_cul_send, tx_code, qos=0, retain=False)
+                mqttc.publish(mqtt_cul_topic_send, tx_code, qos=0, retain=False)
                 
                 
             if serialCULAvailable and send_on_serial_cul and (TX_interface == "UART" or TX_interface == "both"):
             if serialCULAvailable and send_on_serial_cul and (TX_interface == "UART" or TX_interface == "both"):
                 log_write("    TX via UART: " + tx_code)
                 log_write("    TX via UART: " + tx_code)
                 culCmd = tx_code + '\r\n'
                 culCmd = tx_code + '\r\n'
-                ser.write(culCmd.encode('utf-8'))
+                ser.write(culCmd.encode('ascii'))
                 
                 
         else:
         else:
             log_write("WARNING: CUL send command repeated too quickly.")
             log_write("WARNING: CUL send command repeated too quickly.")
             
             
+    publish_device_statusupdate(device, cmd)
 
 
 
 
 # main
 # main
 if receive_from_serial_cul or send_on_serial_cul:
 if receive_from_serial_cul or send_on_serial_cul:
     if not os.path.exists(serialPort):
     if not os.path.exists(serialPort):
         log_write("ERROR opening connection to serial CUL... device '" + serialPort + "' does not exist.")
         log_write("ERROR opening connection to serial CUL... device '" + serialPort + "' does not exist.")
-        if log_enable: writeLog("CUL2MQTT v"+version+" starting")
+        if log_enable: log_write("CUL2MQTT v"+str(version)+" starting")
         
         
         if receive_from_mqtt_cul:
         if receive_from_mqtt_cul:
             if forceSerialCULConnected:
             if forceSerialCULConnected:
@@ -721,16 +765,16 @@ if receive_from_serial_cul or send_on_serial_cul:
         log_write("opening connection to serial CUL...")
         log_write("opening connection to serial CUL...")
         serLine = ""
         serLine = ""
         ser = serial.Serial(port=serialPort,baudrate=serialBaudrate,parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=serialTimeout)
         ser = serial.Serial(port=serialPort,baudrate=serialBaudrate,parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=serialTimeout)
-        sleep(culInitTimeout)
-        ser.write('V\r\n'.encode('utf-8'))  # get CUL version info
+        sleep(serialCulInitTimeout)
+        ser.write('V\r\n'.encode('ascii'))  # get CUL version info
         serLine = ser.readline()
         serLine = ser.readline()
-        serLine = serLine.decode('utf-8').rstrip('\r\n')
+        serLine = serLine.decode('ascii').rstrip('\r\n')
         if serLine.startswith("V ") and serLine.find("culfw") != -1:
         if serLine.startswith("V ") and serLine.find("culfw") != -1:
             log_write("connected. CUL version: " + serLine)
             log_write("connected. CUL version: " + serLine)
             serialCULAvailable = True
             serialCULAvailable = True
             sleep(0.1)
             sleep(0.1)
             log_write('Initializing CUL with command: ' + culInitCmd.rstrip('\r\n'))
             log_write('Initializing CUL with command: ' + culInitCmd.rstrip('\r\n'))
-            ser.write(culInitCmd.encode('utf-8'))  # initialize CUL in normal receive mode
+            ser.write(culInitCmd.encode('ascii'))  # initialize CUL in normal receive mode
             sleep(0.5)
             sleep(0.5)
         else:
         else:
             log_write("WARNING: could not connect serial CUL")
             log_write("WARNING: could not connect serial CUL")
@@ -746,8 +790,9 @@ mqttc.on_connect = on_connect
 mqttc.on_disconnect = on_disconnect
 mqttc.on_disconnect = on_disconnect
 mqttc.on_message = on_message
 mqttc.on_message = on_message
 
 
-if len(mqtt_user) > 0 and len(mqtt_password) > 0:
-    mqttc.username_pw_set(mqtt_user, mqtt_password)
+if mqtt_user is not None and mqtt_password is not None:
+    if len(mqtt_user) > 0 and len(mqtt_password) > 0:
+        mqttc.username_pw_set(mqtt_user, mqtt_password)
     
     
 mqttc.connect(mqtt_server, mqtt_port, 60)
 mqttc.connect(mqtt_server, mqtt_port, 60)
 mqttc.loop_start()
 mqttc.loop_start()
@@ -757,7 +802,7 @@ while True:
         serLine = ser.readline()
         serLine = ser.readline()
         if len(serLine) > 0:
         if len(serLine) > 0:
             now = int(round(time.time() * 1000))
             now = int(round(time.time() * 1000))
-            recvCmd = serLine.decode('utf-8')
+            recvCmd = serLine.decode('ascii')
             recvCmd = recvCmd.rstrip('\r\n')
             recvCmd = recvCmd.rstrip('\r\n')
             
             
             #if debug:
             #if debug:

+ 1 - 1
src/cul2mqtt.service

@@ -6,7 +6,7 @@ StartLimitInterval=0
 Type=simple
 Type=simple
 Restart=always
 Restart=always
 RestartSec=1
 RestartSec=1
-ExecStart=/home/pi/cul2mqtt.py
+ExecStart=/home/pi/cul2mqtt/cul2mqtt.py
 User=pi
 User=pi
 
 
 [Install]
 [Install]

+ 342 - 0
src/cul2mqtt_devices.yml

@@ -0,0 +1,342 @@
+"IT_C1":
+  name: "AZ Deckenlampe"
+  #"TX":
+  #  "ON": "is000000000FFF"
+  #  "OFF": "is000000000FF0"
+  #"RX":
+  #  "ON": "i000015"
+  #  "OFF": "i000014"
+  "statTopic": "cmnd/T5-AZ-Deckenlampe/TOGGLEDIM"
+  #"cmdTopic": ""
+
+"IT_C2":
+  name: "AZ Stehlampe"
+  #"TX":
+  #  "ON": "is0000F0000FFF"
+  #  "OFF": "is0000F0000FF0"
+  #"RX":
+  #  "ON": "i004015"
+  #  "OFF": "i004014"
+  "statTopic": "cmnd/T5-AZ-Stehlampe/TOGGLEDIM"
+  #"cmdTopic": ""
+  
+"IT_C3":
+  name: "AZ Regal Indirekt"
+  #"TX":
+  #  "ON": "is0000F0000FFF"
+  #  "OFF": "is0000F0000FF0"
+  #"RX":
+  #  "ON": "i004015"
+  #  "OFF": "i004014"
+  "statTopic": "cmnd/T5-AZ-RegalIndirekt/POWER"
+  #"cmdTopic": ""
+
+# IT B - Top 5 Schlafzimmer
+"IT_B1":
+  name: "SZ Licht"
+  #"TX": 
+  #  "ON": "isF00000000FFF"
+  #  "OFF": "isF00000000FF0"
+  #"RX":
+  #  "ON": "i400015"
+  #  "OFF": "i400014"
+  #"TX_interface": "MQTT"
+  "statTopic": "cmnd/T5-SZ-Licht/POWER1"
+  #"cmdTopic": "cmnd/T5-SZ-Ventilatorkl/POWER"
+
+"IT_B2":
+  name: "SZ Dekolicht"
+  #"TX":
+  #  "ON": "isF000F0000FFF"
+  #  "OFF": "isF000F0000FF0"
+  #"RX":
+  #  "ON": "i404015"
+  #  "OFF": "i404014"
+  #"TX_interface": "MQTT"
+  "statTopic": "stat/T5-SZ-Dekolicht/POWER"
+  "cmdTopic": "cmnd/T5-SZ-Dekolicht/POWER"
+
+# IT FB - Top 5 Wohnzimmer
+"IT_F1":
+  #"RX":
+  #  "ON": "i105015"
+  #  "OFF": "i105014"
+  "statTopic": "cmnd/T5-WZ-Deckenlampe/POWER"
+  #"cmdTopic": "cmnd/T5-WZ-Deckenlampe/POWER"
+  #"add_statTopics": [ "test/123/bla", "test/234/blablu"]
+
+"IT_F2":
+  name: "WZ-Hue"
+  #"TX":
+  #  "ON": "is0F0000000FFF"
+  #  "OFF": "is0F0000000FF0"
+  #"RX":
+  #  "ON": "i100015"
+  #  "OFF": "i100014"
+  #"TX_interface": "MQTT"
+  "statTopic": "cmnd/T5-WZ-Hue/POWER"
+  #"cmdTopic": "cmnd/T5-WZ-Abendlicht/POWER"
+
+"IT_F3":
+  name: "WZ-Tischlampe"
+  #"TX":
+  #  "ON": "is0F0000000FFF"
+  #  "OFF": "is0F0000000FF0"
+  #"RX":
+  #  "ON": "i100015"
+  #  "OFF": "i100014"
+  #"TX_interface": "MQTT"
+  "statTopic": "cmnd/T5-WZ-Tischlampe/POWER"
+  #"cmdTopic": "cmnd/T5-WZ-Abendlicht/POWER"
+
+"IT_F4":
+  name: "WZ-Abendlicht"
+  #"TX":
+  #  "ON": "is0F0000000FFF"
+  #  "OFF": "is0F0000000FF0"
+  #"RX":
+  #  "ON": "i100015"
+  #  "OFF": "i100014"
+  #"TX_interface": "MQTT"
+  "statTopic": "stat/T5-WZ-Abendlicht/POWER"
+  "cmdTopic": "cmnd/T5-WZ-Abendlicht/POWER"
+  
+"IT_F16":
+  #"RX":
+  #  "ON": "i105515"
+  #  "OFF": "i105514"
+  #"statTopic": "stat/T5-WZ-AlleLampen/POWER"
+  "statTopic": "cmnd/T5-WZ-AlleLampen/POWER"
+  #"cmdTopic": "cmnd/T5-WZ-AlleLampen/POWER"
+
+
+#"IT_C2":
+#  name: "WZ-Dekolicht"
+#  #"TX": 
+#  #  "ON": "is0F00F0000FFF"
+#  #  "OFF": "is0F00F0000FF0"
+#  "RX":
+#    "ON": "i104015"
+#    "OFF": "i104014"
+#  #"TX_interface": "MQTT"
+#  "statTopic": "stat/T5-WZ-Dekolicht/POWER"
+#  "cmdTopic": "cmnd/T5-WZ-Dekolicht/POWER"
+
+#"IT_C3":
+#  name: "WZ-Schwarzlicht"
+#  #"TX": 
+#  #  "ON": "is0F000F000FFF"
+#  #  "OFF": "is0F000F000FF0"
+#  "RX":
+#    "ON": "i101015"
+#    "OFF": "i101014"
+#  #"TX_interface": "MQTT"
+#  "statTopic": "stat/T5-WZ-Schwarzlicht/POWER"
+#  "cmdTopic": "cmnd/T5-WZ-Schwarzlicht/POWER"
+
+
+"FAN_WZ":
+  name: "Deckenventilator WZ"
+  #"TX":
+  #  "STOP": "is00F01DF0FD10"
+  #  "LOW": "is00F01DF0FDD0"
+  #  "MED": "is00F01DF0FD0D"
+  #  "HIGH": "is00F01DF0FDFD"
+  "RX":
+    "STOP": "i04E46C"
+    "LOW": "i04E468"
+    "MED": "i04E462"
+    "HIGH": "i04E466"
+  #"TX_interface": "MQTT"
+  "statTopic": "T5/WZ/Deckenventilator/level"
+  "cmdTopic": "T5/WZ/Deckenventilator/set"
+  "add_statTopics_on": [ {"on_payload": "STOP", "topic": "T5/WZ/Deckenventilator/setTimer", "payload": "OFF"},
+                       {"on_payload": "LOW", "topic": "T5/WZ/Deckenventilator/setTimer", "payload": "1H"}
+  ]
+
+"FAN_WZ_LAMP":
+  #"TX":
+  #  "TOGGLE": "is00F01DF0FDFF"
+  "RX":
+    "TOGGLE": "i04E465"
+  #"TX_interface": "MQTT"
+  "statTopic": "cmnd/T5-WZ-Deckenlampe/POWER"
+  #"cmdTopic": "T5/WZ/Deckenventilator/setLamp"
+  #"cmdTopic": ""
+  "cmnd_payload": "TOGGLE"
+  
+  
+"FAN_WZ_TIMER":
+  #"TX":
+  #  "1H": "is00F01DF0FDF0"
+  #  "2H": "is00F01DF0FD0F"
+  #  "4H": "is00F01DF0FD01"
+  #  "8H": "is00F01DF0FDDD"
+  "RX":
+    "1H": "i04E464"
+    "2H": "i04E461"
+    "4H": "i04E463"
+    "8H": "i04E46A"
+  #"TX_interface": "MQTT"
+  "statTopic": "T5/WZ/Deckenventilator/timer"
+  #"cmdTopic": "T5/WZ/Deckenventilator/set"
+  "cmdTopic": "T5/WZ/Deckenventilator/setTimer"
+  
+"FAN_SZ":
+  #"TX":
+  #  "STOP": "is0DD1FDFD0010"
+  #  "LOW": "is0DD1FDFD00D0"
+  #  "MED": "is0DD1FDFD000D"
+  #  "HIGH": "is0DD1FDFD00FD"
+  "RX":
+    "STOP": "i2B660C"
+    "LOW": "i2B6608"
+    "MED": "i2B6602"
+    "HIGH": "i2B6606"
+  #"TX_interface": "MQTT"
+  "statTopic": "T5/SZ/Deckenventilator/level"
+  "cmdTopic": "T5/SZ/Deckenventilator/set"
+  "add_statTopics_on": [ {"on_payload": "STOP", "topic": "T5/SZ/Deckenventilator/setTimer", "payload": "OFF"} ]
+  
+"FAN_SZ_LAMP":
+  #"TX":
+  #  "TOGGLE": "is0DD1FDFD00FF"
+  "RX":
+    "TOGGLE": "i2B6605"
+  #"TX_interface": "MQTT"
+  "statTopic": "T5/SZ/Deckenventilator/lamp"
+  #"cmdTopic": "T5/SZ/Deckenventilator/setLamp"
+  "add_statTopics_on": [ {"on_payload": "TOGGLE", "topic": "cmnd/T5-SZ-Licht/POWER1", "payload": "TOGGLE"} ]
+  #"add_statTopics": [ "test/123/bla", "test/234/blablu"]
+  
+"FAN_SZ_TIMER":
+  #"TX":
+  #  "1H": "is0DD1FDFD00F0"
+  #  "2H": "is0DD1FDFD000F"
+  #  "4H": "is0DD1FDFD0001"
+  #  "8H": "is0DD1FDFD00DD"
+  "RX":
+    "1H": "i2B6604"
+    "2H": "i2B6601"
+    "4H": "i2B6603"
+    "8H": "i2B660A"
+  #"TX_interface": "MQTT"
+  "statTopic": "T5/SZ/Deckenventilator/timer"
+  "cmdTopic": "T5/SZ/Deckenventilator/setTimer"
+
+"FAN_BA":
+  #"TX":
+  #  "STOP": "is0DD1FDFD0010"
+  #  "LOW": "is0DD1FDFD00D0"
+  #  "MED": "is0DD1FDFD000D"
+  #  "HIGH": "is0DD1FDFD00FD"
+  "RX":
+    "STOP": "i3C0C4C"
+    "LOW": "i3C0C48"
+    "MED": "i3C0C42"
+    "HIGH": "i3C0C46"
+  #"TX_interface": "MQTT"
+  "statTopic": "T5/Balkon/Deckenventilator/level"
+  "cmdTopic": "T5/Balkon/Deckenventilator/set"
+  "add_statTopics_on": [ {"on_payload": "STOP", "topic": "T5/Balkon/Deckenventilator/setTimer", "payload": "OFF"} ]
+  
+"FAN_BA_LAMP":
+  #"TX":
+  #  "TOGGLE": "is0DD1FDFD00FF"
+  "RX":
+    "TOGGLE": "i3C0C45"
+  #"TX_interface": "MQTT"
+  "statTopic": "cmnd/T5-Balkon-Licht/POWER1"
+  #"cmdTopic": "T5/SZ/Deckenventilator/setLamp"
+  #"add_statTopics_on": [ {"on_payload": "TOGGLE", "topic": "cmnd/T5-SZ-Licht/POWER1", "payload": "TOGGLE"} ]
+  #"add_statTopics": [ "test/123/bla", "test/234/blablu"]
+  
+"FAN_BA_TIMER":
+  #"TX":
+  #  "1H": "is0DD1FDFD00F0"
+  #  "2H": "is0DD1FDFD000F"
+  #  "4H": "is0DD1FDFD0001"
+  #  "8H": "is0DD1FDFD00DD"
+  "RX":
+    "1H": "i3C0C44"
+    "2H": "i3C0C41"
+    "4H": "i3C0C43"
+    "8H": "i3C0C4A"
+  #"TX_interface": "MQTT"
+  "statTopic": "T5/Balkon/Deckenventilator/timer"
+  "cmdTopic": "T5/Balkon/Deckenventilator/setTimer"
+  
+"BWM1":
+  name: "BWM T5 Balkon"
+  "RX":
+    "ON": "i6565A9A555555656"
+    "OFF": "i6565A9A555555556"
+  "statTopic": "T5/BWM/BA"
+  
+"BWM_T5_AZ":
+  name: "BWM T5 AZ"
+  "RX":
+    "ON": "i6656A69655555656"
+    "OFF": "i6656A69655555556"
+  "statTopic": "T5/BWM/AZ"
+
+#"BWM3":
+#  name: "BWM 3"
+#  "RX":
+#    "ON": "i65AAA69655555656"
+#    "OFF": "i65AAA69655555556"
+#  "statTopic": "T5/BWM/3"
+
+"MUMBI_1_A":
+  "RX": 
+    "ON": "8E28F06980"
+    "OFF": "8E28F16880"
+  "statTopic": "cmnd/T5-AZ-Deckenlampe/TOGGLEDIM"
+
+"MUMBI_1_B":
+  "RX": 
+    "ON": "8E28F26B80"
+    "OFF": "8E28F36A80"
+  "statTopic": "cmnd/T5-AZ-Stehlampe/TOGGLEDIM"
+
+"MUMBI_1_C":
+  "RX": 
+    "ON": "8E28F46D80"
+    "OFF": "8E28F56C80"
+  "statTopic": "cmnd/T5-AZ-RegalIndirekt/POWER"
+
+"MUMBI_1_D":
+  "RX": 
+    "ON": "8E28F86580"
+    "OFF": "8E28F96480"
+
+"MUMBI_1_ALL":
+  "RX": 
+    "ON": "8E28FB6680"
+    "OFF": "8E28F76E80"
+  "statTopic": "cmnd/T5-AZ-AllLights/POWER"
+
+"MUMBI_2_A":
+  "RX": 
+    "ON": "A4CEF09580"
+    "OFF": "A4CEF19480"
+
+"MUMBI_2_B":
+  "RX": 
+    "ON": "A4CEF29780"
+    "OFF": "A4CEF39680"
+
+"MUMBI_2_C":
+  "RX": 
+    "ON": "A4CEF49380"
+    "OFF": "A4CEF59280"
+
+"MUMBI_2_D":
+  "RX": 
+    "ON": "A4CEF89D80"
+    "OFF": "A4CEF99C80"
+
+"MUMBI_2_ALL":
+  "RX": 
+    "ON": "A4CEFB9E80"
+    "OFF": "A4CEF79180"