cul2mqtt.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. #!/usr/bin/python3 -u
  2. #
  3. # pip3 install pyyaml paho-mqtt pyserial
  4. import yaml
  5. from yaml.constructor import ConstructorError
  6. import serial
  7. import sys
  8. import time
  9. from time import sleep
  10. from time import localtime, strftime
  11. import datetime
  12. import paho.mqtt.client as mqtt
  13. import json
  14. import os
  15. import re
  16. import configparser
  17. version = 0.4
  18. # Change working dir to the same dir as this script
  19. os.chdir(sys.path[0])
  20. config = configparser.ConfigParser()
  21. config.read('cul2mqtt.ini')
  22. # global variables
  23. verbose = False
  24. debug = False
  25. quiet = True
  26. serialCULAvailable = False
  27. devdata = {}
  28. RXcodesToDevFunction_IT = {}
  29. RXcodesToDevFunction_RAW = {}
  30. InTopicsToDevIds = {}
  31. lastDataReceiveTime = time.time()
  32. # config vars
  33. deviceConfigFile = config['main'].get('devices_config_yml')
  34. log_enable = config['main'].getboolean('log_enable')
  35. logSuccessfulCommands = config['main'].getboolean('logSuccessfulCommands')
  36. log_path = config['main'].get('log_path')
  37. if not os.path.exists(log_path):
  38. os.makedirs(log_path)
  39. mqtt_server = config['mqtt'].get('server')
  40. mqtt_port = config['mqtt'].getint('port')
  41. mqtt_user = config['mqtt'].get('user')
  42. mqtt_password = config['mqtt'].get('password')
  43. # MQTT Control
  44. enableMQTTControl = config['main'].getboolean('enableMQTTControl')
  45. MQTT_Control_Topic_turnOnActions = config['main'].get('MQTT_Control_Topic_turnOnActions')
  46. MQTT_Control_StateTopic = config['main'].get('MQTT_Control_StateTopic')
  47. turnOnActions = True
  48. if enableMQTTControl:
  49. turnOnActions = False
  50. TX_interface_prefer = config['cul'].get('TX_interface_prefer') # UART or MQTT
  51. if TX_interface_prefer is None: # compatibility with old ini file structure
  52. TX_interface_prefer = config['main'].get('TX_interface_prefer') # UART or MQTT
  53. repeat_received_commands = False # not yet implemented
  54. dataReceiveTimeout = config['main'].getint('dataReceiveTimeout')
  55. lastReceivedStatusFile = config['main'].get('lastReceivedStatusFile')
  56. lastReceivedStatusFileUpdateTime = 0
  57. if len(sys.argv) >= 2:
  58. if sys.argv[1] == "-q":
  59. verbose = False
  60. debug = False
  61. quiet = True
  62. elif sys.argv[1] == "-v":
  63. verbose = True
  64. debug = False
  65. quiet = False
  66. elif sys.argv[1] == "-d":
  67. verbose = True
  68. debug = True
  69. quiet = False
  70. # serial (USB) CUL device
  71. receive_from_serial_cul = config['cul'].getboolean('receive_from_serial_cul')
  72. send_on_serial_cul = config['cul'].getboolean('send_on_serial_cul')
  73. serialPort = config['cul'].get('serialPort')
  74. serialBaudrate = config['cul'].getint('serialBaudrate')
  75. serialTimeout = config['cul'].getint('serialTimeout')
  76. # CUL init command for normal operation, i.E. X21, X05
  77. culInitCmd = config['cul'].get('culInitCmd') + '\r\n'
  78. culSendsRSSI = config['cul'].getboolean('culSendsRSSI') # set depending on culInitCmd chosen
  79. serialCulInitTimeout = config['cul'].getint('serialCulInitTimeout')
  80. forceSerialCULConnected = config['cul'].getboolean('forceSerialCULConnected')
  81. # MQTT CUL
  82. receive_from_mqtt_cul = config['cul'].getboolean('receive_from_mqtt_cul')
  83. send_on_mqtt_cul = config['cul'].getboolean('send_on_mqtt_cul')
  84. mqtt_cul_topic_received = config['cul'].get('mqtt_cul_topic_received')
  85. mqtt_cul_topic_send = config['cul'].get('mqtt_cul_topic_send')
  86. filterSelfSentIncomingTimeout = config['main'].get('filterSelfSentIncomingTimeout')
  87. try:
  88. from yaml import CLoader as Loader
  89. except ImportError:
  90. from yaml import Loader
  91. def no_duplicates_constructor(loader, node, deep=False):
  92. """Check for duplicate keys."""
  93. mapping = {}
  94. for key_node, value_node in node.value:
  95. key = loader.construct_object(key_node, deep=deep)
  96. value = loader.construct_object(value_node, deep=deep)
  97. if key in mapping:
  98. raise ConstructorError("while constructing a mapping", node.start_mark, "found duplicate key (%s)" % key, key_node.start_mark)
  99. mapping[key] = value
  100. return loader.construct_mapping(node, deep)
  101. yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, no_duplicates_constructor)
  102. log_last_date = None
  103. logfilehandle = False
  104. def log_start():
  105. global logfilehandle, log_last_date
  106. if log_enable:
  107. if not os.path.exists(log_path):
  108. os.makedirs(log_path)
  109. try:
  110. _log_current_date = strftime("%Y%m%d")
  111. _logfilename = _log_current_date + "_debug.log"
  112. logfilehandle = open(log_path + '/' + _logfilename, 'a')
  113. log_last_date = _log_current_date
  114. except:
  115. pass
  116. def log_rotate():
  117. global logfilehandle, log_last_date
  118. if log_enable:
  119. _log_current_date = strftime("%Y%m%d")
  120. if log_last_date != _log_current_date:
  121. try:
  122. logfilehandle.close()
  123. _logfilename = _log_current_date + "_debug.log"
  124. logfilehandle = open(log_path + '/' + _logfilename, 'a')
  125. log_last_date = _log_current_date
  126. except:
  127. pass
  128. def log_write(_msg):
  129. global logfilehandle
  130. if not quiet: print(_msg)
  131. log_rotate()
  132. if log_enable:
  133. try:
  134. logfilehandle.write("[" + str(datetime.datetime.now()) + "] " + _msg + "\n")
  135. logfilehandle.flush()
  136. except:
  137. # guat dann hoit ned...
  138. pass
  139. # Log successfully received (known) codes
  140. logSC_last_date = None
  141. logSCfilehandle = False
  142. def logSC_start():
  143. global logSCfilehandle, logSC_last_date
  144. if logSuccessfulCommands:
  145. if not os.path.exists(log_path):
  146. os.makedirs(log_path)
  147. try:
  148. _log_current_date = strftime("%Y%m%d")
  149. _logfilename = _log_current_date + "_received.log"
  150. logSCfilehandle = open(log_path + '/' + _logfilename, 'a')
  151. logSC_last_date = _log_current_date
  152. except:
  153. pass
  154. def logSC_rotate():
  155. global logSCfilehandle, logSC_last_date
  156. if logSuccessfulCommands:
  157. _log_current_date = strftime("%Y%m%d")
  158. if logSC_last_date != _log_current_date:
  159. try:
  160. logSCfilehandle.close()
  161. _logfilename = _log_current_date + "_received.log"
  162. logSCfilehandle = open(log_path + '/' + _logfilename, 'a')
  163. logSC_last_date = _log_current_date
  164. except:
  165. pass
  166. def logSC_write(_msg):
  167. global logSCfilehandle
  168. if not quiet: print(_msg)
  169. log_rotate()
  170. if logSuccessfulCommands:
  171. try:
  172. logSCfilehandle.write("[" + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + "] " + _msg + "\n")
  173. logSCfilehandle.flush()
  174. except:
  175. # guat dann hoit ned...
  176. pass
  177. ###
  178. log_start()
  179. logSC_start()
  180. log_write("CUL2MQTT v" + str(version))
  181. log_write("=====================================")
  182. try:
  183. with open(deviceConfigFile) as devfile:
  184. devdata = yaml.load(devfile, Loader=yaml.FullLoader)
  185. if debug:
  186. log_write("")
  187. log_write("")
  188. log_write("==== parsing config file ====")
  189. log_write("")
  190. log_write(devdata)
  191. log_write("")
  192. for deviceid in devdata:
  193. if debug:
  194. log_write("")
  195. log_write("")
  196. log_write("Device: " + deviceid)
  197. log_write(devdata[deviceid])
  198. if "RX" in devdata[deviceid].keys():
  199. if devdata[deviceid]["RX"] != "":
  200. if debug:
  201. log_write("RX codes:")
  202. for key, value in devdata[deviceid]["RX"].items():
  203. if debug:
  204. log_write(str(key) + "->" + str(value))
  205. if value.startswith("i"):
  206. # is Intertechno RX code
  207. if value in RXcodesToDevFunction_IT.keys():
  208. log_write("")
  209. log_write("")
  210. log_write("ERROR: RX-string '" + str(value) + "' is already defined for another device! Must be unique.")
  211. raise
  212. else:
  213. RXcodesToDevFunction_IT[value] = deviceid, key
  214. else:
  215. # is other RX code - lets call it RAW
  216. if value in RXcodesToDevFunction_RAW.keys():
  217. log_write("")
  218. log_write("")
  219. log_write("ERROR: RX-string '" + str(value) + "' is already defined for another device! Must be unique.")
  220. raise
  221. else:
  222. RXcodesToDevFunction_RAW[value] = deviceid, key
  223. if "cmdTopic" in devdata[deviceid].keys():
  224. if devdata[deviceid]["cmdTopic"] != "":
  225. cmdTopic = devdata[deviceid]["cmdTopic"]
  226. if debug:
  227. log_write("cmdTopic: " + cmdTopic)
  228. if cmdTopic in InTopicsToDevIds.keys():
  229. log_write("")
  230. log_write("")
  231. log_write("ERROR: cmdTopic '" + str(cmdTopic) + "' is already defined for another device! Must be unique.")
  232. raise
  233. else:
  234. InTopicsToDevIds[cmdTopic] = deviceid
  235. if debug:
  236. log_write("")
  237. log_write("")
  238. log_write("")
  239. log_write("RXcodesToDevFunction_IT:")
  240. log_write(RXcodesToDevFunction_IT)
  241. log_write("")
  242. log_write("")
  243. log_write("RXcodesToDevFunction_RAW:")
  244. log_write(RXcodesToDevFunction_RAW)
  245. log_write("")
  246. log_write("")
  247. log_write("InTopicsToDevIds:")
  248. log_write(InTopicsToDevIds)
  249. log_write("")
  250. log_write("")
  251. log_write("InTopicsToDevIds.keys():")
  252. log_write(InTopicsToDevIds.keys())
  253. log_write("")
  254. log_write("")
  255. log_write("devdata.keys():")
  256. log_write(devdata.keys())
  257. log_write("")
  258. log_write("")
  259. log_write("==== parsing config file complete ====")
  260. log_write("")
  261. log_write("")
  262. log_write("")
  263. log_write("")
  264. except ConstructorError as err:
  265. log_write("ERROR on parsing configfile:")
  266. log_write(err)
  267. exit(1)
  268. except:
  269. log_write("ERROR opening configfile")
  270. log_write("Unexpected error: " + str(sys.exc_info()[0]))
  271. exit(1)
  272. lastReceivedMaxAge = config['main'].getint('lastReceivedMaxAge') # ignore repeated messages when they are younger than x ms
  273. lastReceivedTime = dict()
  274. lastSentTime = {}
  275. lastSentMinInterval = config['main'].getint('lastSentMinInterval') # ignore repeated messages when they are younger than x ms, should be > 1500
  276. lastSentCmd = ""
  277. lastSentCmdTime = 0
  278. lastSentDev = ""
  279. lastSentDevCmd = ""
  280. def touch(fname, times=None):
  281. with open(fname, 'a'):
  282. os.utime(fname, times)
  283. def turnOnActions_enable():
  284. global turnOnActions
  285. turnOnActions = True
  286. log_write("")
  287. log_write("MQTT control: switched turnOnActions to ON")
  288. mqttc.publish(MQTT_Control_StateTopic + "/turnOnActions", "ON", qos=0, retain=True)
  289. try:
  290. turnOnActions_file = open("turnOnActions", "w")
  291. turnOnActions_file.write("ON")
  292. turnOnActions_file.close()
  293. except:
  294. log_write("ERROR: could not write turnOnActions state to file.")
  295. def turnOnActions_disable():
  296. global turnOnActions
  297. turnOnActions = False
  298. log_write("")
  299. log_write("MQTT control: switched turnOnActions to OFF")
  300. mqttc.publish(MQTT_Control_StateTopic + "/turnOnActions", "OFF", qos=0, retain=True)
  301. try:
  302. turnOnActions_file = open("turnOnActions", "w")
  303. turnOnActions_file.write("OFF")
  304. turnOnActions_file.close()
  305. except:
  306. log_write("ERROR: could not write turnOnActions state to file.")
  307. def turnOnActions_get():
  308. global turnOnActions
  309. try:
  310. turnOnActions_file = open("turnOnActions", "r")
  311. _content = turnOnActions_file.read()
  312. turnOnActions_file.close()
  313. log_write("MQTT control: restored last state of turnOnActions from file")
  314. if _content == "ON":
  315. turnOnActions = True
  316. log_write("MQTT control: switched turnOnActions to ON")
  317. elif _content == "OFF":
  318. turnOnActions = True
  319. log_write("MQTT control: switched turnOnActions to OFF")
  320. except:
  321. log_write("ERROR: could not read turnOnActions state from file.")
  322. def on_connect(client, userdata, flags, rc):
  323. if verbose:
  324. log_write("MQTT connected with result code " + str(rc))
  325. if receive_from_mqtt_cul:
  326. if mqtt_cul_topic_received != "":
  327. client.subscribe(mqtt_cul_topic_received)
  328. if mqtt_cul_topic_send != "":
  329. #client.publish
  330. mqttc.publish(mqtt_cul_topic_send, culInitCmd, qos=0, retain=False)
  331. for in_topic in InTopicsToDevIds.keys():
  332. if in_topic != "":
  333. client.subscribe(in_topic)
  334. if verbose:
  335. log_write("MQTT subscribed: " + in_topic)
  336. # MQTT control
  337. if enableMQTTControl:
  338. if MQTT_Control_Topic_turnOnActions != "":
  339. client.subscribe(MQTT_Control_Topic_turnOnActions)
  340. if verbose:
  341. log_write("MQTT subscribed: " + MQTT_Control_Topic_turnOnActions)
  342. def on_disconnect(client, userdata, rc):
  343. if rc != 0:
  344. log_write("Unexpected MQTT disconnection. Will auto-reconnect")
  345. def on_message(client, userdata, msg):
  346. global turnOnActions
  347. #print(msg.topic + ": " + str(msg.payload))
  348. payload = msg.payload.decode("utf-8")
  349. if verbose:
  350. log_write("")
  351. log_write("MQTT received: " + msg.topic + " -> " + str(payload))
  352. # MQTT message is for control
  353. if enableMQTTControl and msg.topic == MQTT_Control_Topic_turnOnActions:
  354. if payload == "ON" or payload == "on" or payload == "true" or payload == "1":
  355. turnOnActions_enable()
  356. elif payload == "OFF" or payload == "off" or payload == "false" or payload == "0":
  357. turnOnActions_disable()
  358. # MQTT message is output from CUL
  359. elif receive_from_mqtt_cul and msg.topic == mqtt_cul_topic_received:
  360. payload = payload.rstrip()
  361. # support output from Tasmota SerialBridge on tele/[DEVICE]/RESULT topic
  362. # example: {"SerialReceived":"p10 144 80 480 48 32 1360 28 1 3 4 160 6336 0 735C1470"}
  363. if payload.startswith('{"SerialReceived":'):
  364. payload_json = json.loads(payload)
  365. payload = payload_json.get('SerialReceived')
  366. if payload != None:
  367. cul_received(payload, "MQTT") # to lower case as MQTT CUL via Tasmota SerialBridge with Rule changes output to all uppercase
  368. if verbose:
  369. log_write("MQTT-CUL RX: '" + payload + "'")
  370. elif not payload.startswith('{'):
  371. # ignore everything different starting with {
  372. cul_received(payload.lower(), "MQTT") # to lower case as MQTT CUL via Tasmota SerialBridge with Rule changes output to all uppercase
  373. if verbose:
  374. log_write("MQTT-CUL RX: '" + payload.lower() + "'")
  375. else:
  376. for in_topic, dev in InTopicsToDevIds.items():
  377. if msg.topic == in_topic:
  378. if verbose: log_write("MQTT received - '" + msg.topic + "' = '" + payload + "' => DEV: " + dev)
  379. if 'name' in devdata[dev].keys():
  380. log_write('devName: ' + devdata[dev]['name'])
  381. global lastSentDev, lastSentDevCmd, lastSentCmdTime
  382. now = int(round(time.time() * 1000))
  383. if debug:
  384. log_write("dev="+dev+", lastSentDevCmd="+lastSentDevCmd)
  385. if dev == lastSentDev and payload == lastSentDevCmd and (now - lastSentCmdTime) < 1000:
  386. if verbose:
  387. log_write("MQTT: ignored command as we just sent this.")
  388. else:
  389. cul_send(dev, payload)
  390. if 'statTopic' in devdata[dev].keys():
  391. if verbose: log_write('statTopic: ' + devdata[dev]['statTopic'])
  392. mqttc.publish(devdata[dev]['statTopic'], payload, qos=0, retain=False)
  393. if 'add_statTopics_on' in devdata[dev].keys():
  394. if verbose: log_write("add_statTopics_on:")
  395. for res in devdata[dev]['add_statTopics_on']:
  396. if 'on_payload' in res and 'topic' in res and 'payload' in res:
  397. if payload == res['on_payload'] and payload != "" and res['topic'] != "" and res['payload'] != "":
  398. if verbose: log_write(" on '" + payload + "': '" + res['payload'] + "' => '" + res['topic'] + "'")
  399. mqttc.publish(res['topic'], res['payload'], qos=0, retain=False)
  400. def publish_device_statusupdate(device, cmd, value):
  401. global turnOnActions
  402. if turnOnActions and device in devdata.keys():
  403. if 'statTopic' in devdata[device].keys():
  404. statTopic = devdata[device].get('statTopic')
  405. if value is not None and cmd == "%VALUE%":
  406. if verbose: log_write(" MQTT publish: '" + value + "' -> '" + statTopic + "'")
  407. mqttc.publish(statTopic, value, qos=0, retain=False)
  408. else:
  409. if verbose: log_write(" MQTT publish: '" + cmd + "' -> '" + statTopic + "'")
  410. mqttc.publish(statTopic, cmd, qos=0, retain=False)
  411. if logSuccessfulCommands:
  412. logSC_write("received from device: " + device + ", cmd: " + cmd + ", value: " + str(value) + ", topic: " + statTopic)
  413. if 'add_statTopics_on' in devdata[device].keys():
  414. if verbose: log_write(" MQTT publish add_statTopics_on:")
  415. for res in devdata[device].get('add_statTopics_on'):
  416. if 'on_payload' in res and 'topic' in res and 'payload' in res:
  417. if cmd == res['on_payload']:
  418. if verbose: log_write(" on '" + res['on_payload'] + "' -> publish '" + res['payload'] + "' on topic '" + res['topic'] + "'")
  419. mqttc.publish(res['topic'], res['payload'], qos=0, retain=False)
  420. if 'add_statTopics' in devdata[device].keys():
  421. if verbose: log_write(" MQTT publish on add_statTopics:")
  422. for res in devdata[device]['add_statTopics']:
  423. if verbose: log_write(" '" + cmd + "' -> '" + res + "'")
  424. mqttc.publish(res, cmd, qos=0, retain=False)
  425. def parseRXCode(rx_code, source_cul):
  426. receivedForDevice = None
  427. receivedCmnd = None
  428. receivedValue = None
  429. skipPublishing = False
  430. if rx_code.startswith("i"):
  431. # parse Intertechno RX code
  432. if debug: log_write(" PROTOCOL: Intertechno")
  433. sucessfullyParsedITCode = False
  434. # look if this code is in the device_config
  435. if rx_code in RXcodesToDevFunction_IT:
  436. if debug: log_write(" code found in device config")
  437. receivedForDevice = RXcodesToDevFunction_IT[rx_code][0]
  438. receivedCmnd = RXcodesToDevFunction_IT[rx_code][1]
  439. sucessfullyParsedITCode = True
  440. if debug: log_write(" DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
  441. if verbose:
  442. #log_write("")
  443. log_write(" CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
  444. # if this had no result -> try again with last 8 bit stripped, as in many cases this is only RSSI and not part of the code
  445. # in this case treat this last 8 bit as sensor value
  446. # --> sensor with 16 bit address and 8 bit data (i.E. Arduino sending using RCSwitch library)
  447. rx_code_stripped = rx_code[0:-2]
  448. if not sucessfullyParsedITCode and rx_code_stripped in RXcodesToDevFunction_IT:
  449. if debug: log_write(" code found in device config with last 8 bit stripped")
  450. receivedForDevice = RXcodesToDevFunction_IT[rx_code_stripped][0]
  451. receivedCmnd = RXcodesToDevFunction_IT[rx_code_stripped][1]
  452. receivedValue = rx_code[-2:]
  453. # convert value to decimal if enabled for this device
  454. if "convertValueToDecimal" in devdata[receivedForDevice]:
  455. if devdata[receivedForDevice]["convertValueToDecimal"] == True:
  456. if debug: log_write(" converting value to decimal")
  457. receivedValue = str(int(receivedValue, base=16))
  458. sucessfullyParsedITCode = True
  459. if debug: log_write(" DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", VALUE: " + receivedValue + ", RX: " + rx_code_stripped)
  460. if verbose:
  461. #log_write("")
  462. log_write(" CUL '" + source_cul + "' received '" + rx_code_stripped + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", VALUE: " + receivedValue)
  463. # if this also had no result -> try again with last 12 bit stripped
  464. # --> sensor with 12 bit address and 12 bit data (i.E. Arduino sending using RCSwitch library)
  465. rx_code_stripped = rx_code[0:-3]
  466. if not sucessfullyParsedITCode and rx_code_stripped in RXcodesToDevFunction_IT:
  467. if debug: log_write(" code found in device config with last 12 bit stripped")
  468. receivedForDevice = RXcodesToDevFunction_IT[rx_code_stripped][0]
  469. receivedCmnd = RXcodesToDevFunction_IT[rx_code_stripped][1]
  470. receivedValue = rx_code[-3:]
  471. tmpReceivedValue = int(receivedValue, base=16)
  472. if debug: log_write(" RAW receivedValue=" + hex(tmpReceivedValue))
  473. # sensor type DS18B20
  474. # -> convert raw data to temperature °C
  475. if "isSensorType" in devdata[receivedForDevice]:
  476. if devdata[receivedForDevice]["isSensorType"] == "DS18B20":
  477. if debug:
  478. log_write(" is sensor type DS18B20")
  479. log_write(" converting RAW data...")
  480. if tmpReceivedValue == 0xfff or tmpReceivedValue == 0xffe:
  481. _battState = ""
  482. # this is a low battery warning
  483. # -> send warning on MQTT if configured and skip value transmitting
  484. if "battTopic" in devdata[receivedForDevice]:
  485. if devdata[receivedForDevice]["battTopic"] is not None:
  486. if tmpReceivedValue == 0xfff:
  487. _battState = "OK"
  488. elif tmpReceivedValue == 0xffe:
  489. _battState = "LOW"
  490. mqttc.publish(devdata[receivedForDevice]["battTopic"], _battState, qos=0, retain=False)
  491. skipPublishing = True # no data will be published this time
  492. else:
  493. # left shift 2 bits as sender right shifts it
  494. tmpReceivedValue = tmpReceivedValue << 2
  495. if debug: log_write(" receivedValue=" + str(tmpReceivedValue))
  496. # if MSB (bit 13) == 1 -> negative value
  497. if tmpReceivedValue >= 8192:
  498. tmpReceivedValue = tmpReceivedValue - 16384
  499. tmpReceivedValue = round(0.0078125 * tmpReceivedValue, 1)
  500. if debug: log_write(" converted value=" + str(tmpReceivedValue))
  501. if tmpReceivedValue >= -50 and tmpReceivedValue <= 125:
  502. receivedValue = str(tmpReceivedValue)
  503. else:
  504. log_write(" WARN: value out of range - skipping")
  505. skipPublishing = True # no data will be published this time
  506. # not a "special" sensor but convertValueToDecimal is enabled
  507. # -> convert value to decimal if enabled for this device
  508. elif "convertValueToDecimal" in devdata[receivedForDevice]:
  509. if devdata[receivedForDevice]["convertValueToDecimal"] == True:
  510. if debug: log_write(" converting value to decimal")
  511. receivedValue = str(int(receivedValue, base=16))
  512. sucessfullyParsedITCode = True
  513. if debug: log_write(" DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", VALUE: " + receivedValue + ", RX: " + rx_code_stripped)
  514. if verbose:
  515. #log_write("")
  516. log_write(" CUL '" + source_cul + "' received '" + rx_code_stripped + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", VALUE: " + receivedValue)
  517. # if this also did not work, try to parse it as "default/cheap" Intertechno code...
  518. if not sucessfullyParsedITCode:
  519. if debug: log_write(" treat code as 'default/cheap' Intertechno code...")
  520. try:
  521. receivedForDevice, receivedCmnd = decodeInterTechnoRX(rx_code_stripped)
  522. except:
  523. log_write(" Error parsing code as 'default/cheap' Intertechno code.")
  524. if debug:
  525. log_write(str(receivedForDevice) + ", " + str(receivedCmnd))
  526. else:
  527. # parse other/RAW RX code
  528. if debug: log_write(" PROTOCOL: OTHER/RAW")
  529. if rx_code in RXcodesToDevFunction_RAW:
  530. receivedForDevice = RXcodesToDevFunction_RAW[rx_code][0]
  531. receivedCmnd = RXcodesToDevFunction_RAW[rx_code][1]
  532. if debug: log_write(" DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
  533. if verbose:
  534. #log_write("")
  535. log_write(" CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
  536. #if debug:
  537. # log_write(" DEV: " + str(receivedForDevice) + ", CMD: " + str(receivedCmnd) + ", RX: " + rx_code)
  538. if receivedForDevice != None and receivedCmnd != None and receivedForDevice != False and receivedCmnd != False and not skipPublishing:
  539. publish_device_statusupdate(receivedForDevice, receivedCmnd, receivedValue)
  540. def decodeInterTechnoRX(rx_code):
  541. # decode old fixed code from Intertechno remotes
  542. _housecode = None
  543. _devaddr = None
  544. _command = None
  545. _itname = None
  546. #print(rx_code[0:1])
  547. #print(rx_code[1:3])
  548. #print(rx_code[3:5])
  549. #print(rx_code[5:7])
  550. if rx_code[0:1] == "i":
  551. if rx_code[1:3] == "00": _housecode = "A"
  552. elif rx_code[1:3] == "40": _housecode = "B"
  553. elif rx_code[1:3] == "10": _housecode = "C"
  554. elif rx_code[1:3] == "50": _housecode = "D"
  555. elif rx_code[1:3] == "04": _housecode = "E"
  556. elif rx_code[1:3] == "44": _housecode = "F"
  557. elif rx_code[1:3] == "14": _housecode = "G"
  558. elif rx_code[1:3] == "54": _housecode = "H"
  559. elif rx_code[1:3] == "01": _housecode = "I"
  560. elif rx_code[1:3] == "41": _housecode = "J"
  561. elif rx_code[1:3] == "11": _housecode = "K"
  562. elif rx_code[1:3] == "51": _housecode = "L"
  563. elif rx_code[1:3] == "05": _housecode = "M"
  564. elif rx_code[1:3] == "45": _housecode = "N"
  565. elif rx_code[1:3] == "15": _housecode = "O"
  566. elif rx_code[1:3] == "55": _housecode = "P"
  567. if rx_code[3:5] == "00": _devaddr = "1"
  568. elif rx_code[3:5] == "40": _devaddr = "2"
  569. elif rx_code[3:5] == "10": _devaddr = "3"
  570. elif rx_code[3:5] == "50": _devaddr = "4"
  571. elif rx_code[3:5] == "04": _devaddr = "5"
  572. elif rx_code[3:5] == "44": _devaddr = "6"
  573. elif rx_code[3:5] == "14": _devaddr = "7"
  574. elif rx_code[3:5] == "54": _devaddr = "8"
  575. elif rx_code[3:5] == "01": _devaddr = "9"
  576. elif rx_code[3:5] == "41": _devaddr = "10"
  577. elif rx_code[3:5] == "11": _devaddr = "11"
  578. elif rx_code[3:5] == "51": _devaddr = "12"
  579. elif rx_code[3:5] == "05": _devaddr = "13"
  580. elif rx_code[3:5] == "45": _devaddr = "14"
  581. elif rx_code[3:5] == "15": _devaddr = "15"
  582. elif rx_code[3:5] == "55": _devaddr = "16"
  583. if rx_code[5:7] == "15": _command = "ON"
  584. elif rx_code[5:7] == "14": _command = "OFF"
  585. if _housecode != None and _devaddr != None and _command != None:
  586. _itname = "IT_" + _housecode + _devaddr
  587. if debug: log_write("valid IT code: '" + _itname + "' => '" + _command + "'")
  588. return _itname, _command
  589. else:
  590. if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
  591. return False, False
  592. else:
  593. if debug: log_write("unknown or invalid IT code '" + rx_code + "'")
  594. def encodeInterTechnoRX(itname, cmd):
  595. # decode old fixed code from InterTechno remotes
  596. _housecode = None
  597. _devaddr = None
  598. _command = None
  599. #print(itname[0:3])
  600. #print(itname[3:4])
  601. #print(itname[4:])
  602. if itname[0:3] == "IT_":
  603. if itname[3:4] == "A": _housecode = "00"
  604. elif itname[3:4] == "B": _housecode = "40"
  605. elif itname[3:4] == "C": _housecode = "10"
  606. elif itname[3:4] == "D": _housecode = "50"
  607. elif itname[3:4] == "E": _housecode = "04"
  608. elif itname[3:4] == "F": _housecode = "44"
  609. elif itname[3:4] == "G": _housecode = "14"
  610. elif itname[3:4] == "H": _housecode = "54"
  611. elif itname[3:4] == "I": _housecode = "01"
  612. elif itname[3:4] == "J": _housecode = "41"
  613. elif itname[3:4] == "K": _housecode = "11"
  614. elif itname[3:4] == "L": _housecode = "51"
  615. elif itname[3:4] == "M": _housecode = "05"
  616. elif itname[3:4] == "N": _housecode = "45"
  617. elif itname[3:4] == "O": _housecode = "15"
  618. elif itname[3:4] == "P": _housecode = "55"
  619. if itname[4:] == "1": _devaddr = "00"
  620. elif itname[4:] == "2": _devaddr = "40"
  621. elif itname[4:] == "3": _devaddr = "10"
  622. elif itname[4:] == "4": _devaddr = "50"
  623. elif itname[4:] == "5": _devaddr = "04"
  624. elif itname[4:] == "6": _devaddr = "44"
  625. elif itname[4:] == "7": _devaddr = "14"
  626. elif itname[4:] == "8": _devaddr = "54"
  627. elif itname[4:] == "9": _devaddr = "01"
  628. elif itname[4:] == "10": _devaddr = "41"
  629. elif itname[4:] == "11": _devaddr = "11"
  630. elif itname[4:] == "12": _devaddr = "51"
  631. elif itname[4:] == "13": _devaddr = "05"
  632. elif itname[4:] == "14": _devaddr = "45"
  633. elif itname[4:] == "15": _devaddr = "15"
  634. elif itname[4:] == "16": _devaddr = "55"
  635. if cmd == "ON": _command = "15"
  636. elif cmd == "OFF": _command = "14"
  637. if debug: print("IT housecode=", _housecode, "- devaddr=", _devaddr, "- command=", _command)
  638. if _housecode != None and _devaddr != None and _command != None:
  639. _rxcode = "i" + _housecode + _devaddr + _command
  640. #print("encoded IT RX code: '" + itname + "' => '" + cmd + "' = '" + _rxcode)
  641. return _rxcode
  642. else:
  643. if debug: log_write(" unknown or invalid IT code '" + rx_code + "'")
  644. return False
  645. else:
  646. if debug: log_write(" unknown or invalid IT code '" + rx_code + "'")
  647. def cul_received(payload, source_cul):
  648. global lastReceivedTime, lastReceivedMaxAge, lastDataReceiveTime, lastReceivedStatusFile, lastReceivedStatusFileUpdateTime
  649. lastDataReceiveTime = time.time()
  650. if debug:
  651. print("DEBUG: lastDataReceiveTime=" + str(lastDataReceiveTime))
  652. # touch lastReceivedStatusFile if configured
  653. if lastReceivedStatusFile is not None:
  654. if (time.time() - lastReceivedStatusFileUpdateTime) >= 60:
  655. # try updating lastReceivedStatusFile if last time was >60s ago
  656. lastReceivedStatusFileUpdateTime = time.time()
  657. try:
  658. touch(lastReceivedStatusFile)
  659. if debug:
  660. print("DEBUG: lastReceivedStatusFile updated")
  661. except:
  662. if debug:
  663. print("ERROR: could not update lastReceivedStatusFile")
  664. else:
  665. if debug:
  666. print("DEBUG: lastReceivedStatusFile not defined")
  667. # send lastReceived status to MQTT (for monitoring)
  668. mqttc.publish(MQTT_Control_StateTopic + "/lastReceived", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  669. if payload[:2] == 'is': # msg is reply from CUL to raw send command
  670. pass
  671. elif payload[:1] == 'i': # is a IT compatible code - so look it up in the code table
  672. if culSendsRSSI:
  673. inCmd = payload[:-2] # strip last 2 chars, depending on used CUL receive mode - if enabled this is only RSSI
  674. else:
  675. inCmd = payload
  676. if verbose:
  677. log_write(" inCmd: " + inCmd + ", receiving CUL: " + source_cul)
  678. # filter fast repeated codes (strip first char on Intertechno codes as the repetation will come in as RAW without "i" prefix)
  679. ignoreCommand = False
  680. if inCmd in lastReceivedTime.keys():
  681. lastTime = int(lastReceivedTime[inCmd])
  682. now = int(round(time.time() * 1000))
  683. tdelta = (now - lastTime)
  684. if debug: log_write(" TDELTA = " + str(tdelta))
  685. if debug: log_write(" lastTime = " + str(lastTime))
  686. if tdelta < lastReceivedMaxAge:
  687. if verbose: log_write(" ignoring code from CUL '" + source_cul + "', CMD: '" + inCmd + "' - already received " + str(tdelta) + " ms ago")
  688. ignoreCommand = True
  689. if not ignoreCommand:
  690. lastReceivedTime[inCmd] = int(round(time.time() * 1000))
  691. parseRXCode(inCmd, source_cul)
  692. elif payload[:1] == 'p': # is RAW data
  693. # example: "p11 288 864 800 320 288 832 33 1 4 1 288 10224 0 A4CEF09580"
  694. # split string and extract last row as we dont need the rest
  695. splitPayload = payload.split(' ')
  696. actualPayload = splitPayload[len(splitPayload)-1]
  697. if debug: log_write(" actualPayload: " + actualPayload)
  698. ignoreCommand = False
  699. # handle/filter repetations of Intertechno codes
  700. isITrepetation = False
  701. if ('i'+actualPayload) in lastReceivedTime.keys():
  702. isITrepetation = True
  703. if debug: log_write(" skipping repeated Intertechno code")
  704. lastTime = int(lastReceivedTime['i'+actualPayload])
  705. now = int(round(time.time() * 1000))
  706. tdelta = (now - lastTime)
  707. if debug: log_write(" TDELTA = " + str(tdelta))
  708. if tdelta < lastReceivedMaxAge:
  709. if verbose: log_write(" ignoring code from CUL '" + source_cul + "', CMD: '" + 'i'+actualPayload + "' - already received " + str(tdelta) + " ms ago")
  710. ignoreCommand = True
  711. # filter fast repeated codes
  712. if not isITrepetation:
  713. if actualPayload in lastReceivedTime.keys():
  714. lastTime = int(lastReceivedTime[actualPayload])
  715. now = int(round(time.time() * 1000))
  716. tdelta = (now - lastTime)
  717. if debug: log_write(" TDELTA = " + str(tdelta))
  718. if tdelta < lastReceivedMaxAge:
  719. if verbose: log_write(" ignoring code from CUL '" + source_cul + "', CMD: '" + actualPayload + "' - already received " + str(tdelta) + " ms ago")
  720. ignoreCommand = True
  721. if not ignoreCommand:
  722. if isITrepetation:
  723. # treat as Intertechno code
  724. lastReceivedTime['i'+actualPayload] = int(round(time.time() * 1000))
  725. parseRXCode('i'+actualPayload, source_cul)
  726. else:
  727. lastReceivedTime[actualPayload] = int(round(time.time() * 1000))
  728. parseRXCode(actualPayload, source_cul)
  729. #if repeat_received_commands:
  730. # lastSentLength = len(lastSent)
  731. # i = 0
  732. # dontRepeat = False
  733. # while i < lastSentLength:
  734. # #print(str(i) + ": " + lastReceived[i])
  735. # if lastSent[i] == decCmd:
  736. # lastTime = int(lastSentTime[i])
  737. # now = int(round(time.time() * 1000))
  738. # tdelta = (now - lastTime)
  739. # #print("TDELTA = " + str(tdelta))
  740. # if tdelta < lastSentMaxAge:
  741. # print("ignoring command as it originated from ourselfs " + inCmd + " " + str(tdelta) + " ms ago")
  742. # dontRepeat = True
  743. # #break
  744. # i += 1
  745. # #cmdToSend = culSendCmds[decCmd]
  746. # if not dontRepeat:
  747. # if device != "" and cmd != "":
  748. # print("REPEATING COMMAND: " + cmd + " TO DEVICE " + device)
  749. # cul_send(device, cmd)
  750. def IT_RXtoTXCode(itReceiveCode):
  751. if debug:
  752. statusstr = "IT_RXtoTXCode "
  753. statusstr += "RX: "
  754. statusstr += itReceiveCode
  755. #print("IT_RXtoTXCode ReceiveCode: " + itReceiveCode)
  756. itReceiveCode = itReceiveCode[1:] # remove first character "i"
  757. itReceiveCodeLengthBytes = int(len(itReceiveCode)/2)
  758. itReceiveCodeBytes = []
  759. itTransmitTristate = "is"
  760. for x in range(itReceiveCodeLengthBytes):
  761. itReceiveCodeBytes.append(bin(int(itReceiveCode[x*2:(x*2+2)],16))[2:].zfill(8))
  762. for x in range(len(itReceiveCodeBytes)):
  763. #print("IT REC byte " + str(x) + " = " + str(itReceiveCodeBytes[x]))
  764. for y in range(4):
  765. quarterbyte = str(itReceiveCodeBytes[x][y*2:y*2+2])
  766. if quarterbyte == "00":
  767. tmpTristate = "0";
  768. elif quarterbyte == "01":
  769. tmpTristate = "F";
  770. elif quarterbyte == "10":
  771. tmpTristate = "D";
  772. elif quarterbyte == "11":
  773. tmpTristate = "1";
  774. #print(quarterbyte + " -> " + tmpTristate)
  775. itTransmitTristate = itTransmitTristate + tmpTristate
  776. if debug:
  777. statusstr += " -> TX: "
  778. statusstr += itTransmitTristate
  779. #print("IT_RXtoTXCode TransmitCode: " + itTransmitTristate)
  780. log_write(statusstr)
  781. return(itTransmitTristate)
  782. def cul_send(device, cmd):
  783. global lastSentTime
  784. culCmd = ""
  785. culSendCmdsKeyName = device + ' ' + cmd
  786. if debug: log_write("CUL send '" + cmd + "' to device '" + device + "'")
  787. tx_code = False
  788. if 'TX' in devdata[device]:
  789. if debug: print("TX data available, cmd="+cmd)
  790. if debug: print(devdata[device]['TX'])
  791. if cmd in devdata[device]['TX'].keys():
  792. tx_code = devdata[device]['TX'][cmd]
  793. if verbose: log_write(" TX code for '" + cmd + "': " + tx_code)
  794. if not tx_code:
  795. if verbose: log_write(" deviceID: " + device)
  796. if 'RX' in devdata[device].keys():
  797. if verbose:
  798. log_write(" RX code configured, cmd=" + cmd)
  799. log_write(devdata[device]['RX'])
  800. if cmd in devdata[device]['RX'].keys():
  801. rx_code = devdata[device]['RX'][cmd]
  802. if debug: log_write(" RX code for '" + cmd + "': " + rx_code)
  803. tx_code = IT_RXtoTXCode(rx_code)
  804. if verbose: log_write(" TX code for '" + cmd + "': " + tx_code)
  805. else:
  806. log_write(" RX code for '" + cmd + "' NOT FOUND")
  807. elif device.startswith("IT_"):
  808. # InterTechno device with fixed code - encode RX code for IT device name and convert to TX code
  809. rx_code = encodeInterTechnoRX(device, cmd)
  810. if rx_code:
  811. if debug: log_write(" RX code for '" + cmd + "': " + rx_code)
  812. tx_code = IT_RXtoTXCode(rx_code)
  813. if verbose: log_write(" TX code for '" + cmd + "': " + tx_code)
  814. if not tx_code:
  815. if verbose: log_write(" no valid TX code for this device/command")
  816. else:
  817. now = int(round(time.time() * 1000))
  818. # look if this command has been sent in the past, and when
  819. if culSendCmdsKeyName in lastSentTime.keys():
  820. lastTime = lastSentTime[culSendCmdsKeyName]
  821. else:
  822. lastTime = 0
  823. lastTimeAge = now - lastTime
  824. if verbose: log_write(' lastTime: ' + str(lastTimeAge) + 'ms ago')
  825. if lastTimeAge > lastSentMinInterval: # only send if last time + min interval is exceeded
  826. lastSentTime[culSendCmdsKeyName] = now # save what we send, so that we dont repeat our own sent messages if repeating is enabled
  827. TX_interface = TX_interface_prefer
  828. if 'TX_interface' in devdata[device].keys():
  829. if verbose: log_write(" TX_interface: " + devdata[device]['TX_interface'])
  830. if TX_interface == "UART" and not serialCULAvailable:
  831. TX_interface = "MQTT"
  832. global lastSentCmd, lastSentCmdTime, lastSentDev, lastSentDevCmd
  833. lastSentCmd = tx_code
  834. lastSentCmdTime = now
  835. lastSentDev = device
  836. lastSentDevCmd = cmd
  837. if send_on_mqtt_cul and (TX_interface == "MQTT" or TX_interface == "both"):
  838. log_write(" TX via MQTT: " + tx_code)
  839. mqttc.publish(mqtt_cul_topic_send, tx_code, qos=0, retain=False)
  840. if serialCULAvailable and send_on_serial_cul and (TX_interface == "UART" or TX_interface == "both"):
  841. log_write(" TX via UART: " + tx_code)
  842. culCmd = tx_code + '\r\n'
  843. ser.write(culCmd.encode('ascii'))
  844. else:
  845. log_write("WARNING: CUL send command repeated too quickly.")
  846. publish_device_statusupdate(device, cmd, None)
  847. # main
  848. if receive_from_serial_cul or send_on_serial_cul:
  849. if not os.path.exists(serialPort):
  850. log_write("ERROR opening connection to serial CUL... device '" + serialPort + "' does not exist.")
  851. if log_enable: log_write("CUL2MQTT v"+str(version)+" starting")
  852. if receive_from_mqtt_cul:
  853. if forceSerialCULConnected:
  854. exit(2)
  855. else:
  856. log_write("resuming in MQTT-CUL only mode...")
  857. TX_interface_prefer = "MQTT"
  858. receive_from_serial_cul = False
  859. send_on_serial_cul = False
  860. serialCULAvailable = False
  861. log_write("")
  862. log_write("")
  863. else:
  864. log_write("opening connection to UART-CUL...")
  865. serLine = ""
  866. ser = serial.Serial(port=serialPort,baudrate=serialBaudrate,parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=serialTimeout)
  867. sleep(serialCulInitTimeout)
  868. ser.write('V\r\n'.encode('ascii')) # get CUL version info
  869. serLine = ser.readline()
  870. serLine = serLine.decode('ascii').rstrip('\r\n')
  871. if serLine.startswith("V ") and serLine.find("culfw") != -1:
  872. log_write("connected. CUL version: " + serLine)
  873. serialCULAvailable = True
  874. sleep(0.1)
  875. log_write('Initializing CUL with command: ' + culInitCmd.rstrip('\r\n'))
  876. ser.write(culInitCmd.encode('ascii')) # initialize CUL in normal receive mode
  877. sleep(0.5)
  878. else:
  879. log_write("WARNING: could not connect UART-CUL")
  880. receive_from_serial_cul = False
  881. send_on_serial_cul = False
  882. serialCULAvailable = False
  883. TX_interface_prefer = "MQTT"
  884. if forceSerialCULConnected:
  885. exit(2)
  886. mqttc = mqtt.Client()
  887. mqttc.on_connect = on_connect
  888. mqttc.on_disconnect = on_disconnect
  889. mqttc.on_message = on_message
  890. if enableMQTTControl:
  891. turnOnActions_get()
  892. log_write("")
  893. log_write("MQTT-control is enabled.")
  894. log_write("to enable any actions you must send 'ON' to MQTT-topic: '" + MQTT_Control_Topic_turnOnActions + "'")
  895. log_write("")
  896. log_write("")
  897. if mqtt_user is not None and mqtt_password is not None:
  898. if len(mqtt_user) > 0 and len(mqtt_password) > 0:
  899. mqttc.username_pw_set(mqtt_user, mqtt_password)
  900. mqttc.connect(mqtt_server, mqtt_port, 60)
  901. mqttc.loop_start()
  902. try:
  903. while True:
  904. if receive_from_serial_cul:
  905. serLine = ser.readline()
  906. if len(serLine) > 0:
  907. now = int(round(time.time() * 1000))
  908. recvCmd = serLine.decode('ascii')
  909. recvCmd = recvCmd.rstrip('\r\n')
  910. #if debug:
  911. # print("lastSentCmd: " + lastSentCmd + ", lastSentCmdTime=" + str(lastSentCmdTime))
  912. if recvCmd == lastSentCmd and (now - lastSentCmdTime) < filterSelfSentIncomingTimeout:
  913. pass
  914. else:
  915. if verbose:
  916. log_write("")
  917. log_write("UART-CUL RX: '" + recvCmd + "'")
  918. cul_received(recvCmd, "UART")
  919. #touch("/tmp/culagent_running")
  920. # check if receive timeout is exceeded
  921. # - if so there is possibly something wrong
  922. # -> quit program (will be restarted by systemd)
  923. if dataReceiveTimeout is not None:
  924. if (time.time() - lastDataReceiveTime) >= dataReceiveTimeout:
  925. print("WARNING: received no data from CUL for >" + str(dataReceiveTimeout) + "s -> something has gone wrong -> quitting program.")
  926. quit()
  927. ##else:
  928. ## print("dataReceiveTimeout not configured")
  929. sleep(0.05)
  930. except KeyboardInterrupt:
  931. print("\n")