cul2mqtt.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  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. version = 0.2
  16. verbose = False
  17. debug = False
  18. deviceConfigFile = "/home/pi/cul2mqtt_devices.yaml"
  19. #Log levels: DEBUG, INFO, WARNING, ERROR or CRITICAL
  20. #loglevel = "DEBUG"
  21. log_enable = True
  22. log_path = '/home/pi/logs/cul2mqtt'
  23. mqtt_server = "10.1.1.11"
  24. mqtt_port = 1883
  25. mqtt_user = "admin"
  26. mqtt_password = "2Bierum4"
  27. TX_interface_prefer = "MQTT" # UART or MQTT
  28. repeat_received_commands = True # not yet implemented
  29. if len(sys.argv) >= 2:
  30. if sys.argv[1] == "-q":
  31. verbose = False
  32. elif sys.argv[1] == "-v":
  33. verbose = True
  34. # serial (USB) CUL device
  35. receive_from_serial_cul = True
  36. send_on_serial_cul = True
  37. serialPort = '/dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0'
  38. serialBaudrate = 38400
  39. serialTimeout = 1
  40. culInitCmd = 'X21\r\n' # CUL init command for normal operation
  41. culInitTimeout = 3
  42. forceSerialCULConnected = False
  43. serialCULAvailable = False
  44. # MQTT CUL
  45. receive_from_mqtt_cul = True
  46. send_on_mqtt_cul = True
  47. mqtt_topic_cul_received = "T5/CUL/received"
  48. mqtt_topic_cul_send = "T5/CUL/send"
  49. filterSelfSentIncomingTimeout = 100
  50. devdata = {}
  51. RXcodesToDevFunction = {}
  52. InTopicsToDevIds = {}
  53. try:
  54. from yaml import CLoader as Loader
  55. except ImportError:
  56. from yaml import Loader
  57. def no_duplicates_constructor(loader, node, deep=False):
  58. """Check for duplicate keys."""
  59. mapping = {}
  60. for key_node, value_node in node.value:
  61. key = loader.construct_object(key_node, deep=deep)
  62. value = loader.construct_object(value_node, deep=deep)
  63. if key in mapping:
  64. raise ConstructorError("while constructing a mapping", node.start_mark, "found duplicate key (%s)" % key, key_node.start_mark)
  65. mapping[key] = value
  66. return loader.construct_mapping(node, deep)
  67. yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, no_duplicates_constructor)
  68. log_last_date = None
  69. logfilehandle = False
  70. def log_start():
  71. global logfilehandle, log_last_date
  72. if log_enable:
  73. if not os.path.exists(log_path):
  74. os.makedirs(log_path)
  75. try:
  76. _log_current_date = strftime("%Y%m%d")
  77. #_logfilename = _log_current_date + "_" + logfile_name
  78. _logfilename = _log_current_date + ".log"
  79. logfilehandle = open(log_path + '/' + _logfilename, 'a')
  80. log_last_date = _log_current_date
  81. except:
  82. pass
  83. def log_rotate():
  84. global logfilehandle, log_last_date
  85. if log_enable:
  86. _log_current_date = strftime("%Y%m%d")
  87. if log_last_date != _log_current_date:
  88. try:
  89. logfilehandle.close()
  90. _logfilename = _log_current_date + ".log"
  91. logfilehandle = open(log_path + '/' + _logfilename, 'a')
  92. log_last_date = _log_current_date
  93. except:
  94. pass
  95. def log_write(_msg):
  96. global logfilehandle
  97. print(_msg)
  98. log_rotate()
  99. if log_enable:
  100. try:
  101. logfilehandle.write("[" + str(datetime.datetime.now()) + "] " + _msg + "\n")
  102. logfilehandle.flush()
  103. except:
  104. # guat dann hoit ned...
  105. pass
  106. log_start()
  107. log_write("CUL2MQTT v" + str(version))
  108. log_write("=====================================")
  109. try:
  110. with open(deviceConfigFile) as devfile:
  111. devdata = yaml.load(devfile, Loader=yaml.FullLoader)
  112. if debug:
  113. log_write("")
  114. log_write("")
  115. log_write("==== parsing config file ====")
  116. log_write("")
  117. log_write(devdata)
  118. log_write("")
  119. for deviceid in devdata:
  120. if debug:
  121. log_write("")
  122. log_write("")
  123. log_write("Device: " + deviceid)
  124. log_write(devdata[deviceid])
  125. if "RX" in devdata[deviceid].keys():
  126. if devdata[deviceid]["RX"] != "":
  127. if debug:
  128. log_write("RX codes:")
  129. for key, value in devdata[deviceid]["RX"].items():
  130. if debug:
  131. log_write(key, "->", value)
  132. if value in RXcodesToDevFunction.keys():
  133. log_write("")
  134. log_write("")
  135. log_write("ERROR: RX-string '" + str(value) + "' is already defined for another device! Must be unique.")
  136. raise
  137. else:
  138. RXcodesToDevFunction[value] = deviceid, key
  139. if "cmdTopic" in devdata[deviceid].keys():
  140. if devdata[deviceid]["cmdTopic"] != "":
  141. cmdTopic = devdata[deviceid]["cmdTopic"]
  142. if debug:
  143. log_write("cmdTopic: " + cmdTopic)
  144. if cmdTopic in InTopicsToDevIds.keys():
  145. log_write("")
  146. log_write("")
  147. log_write("ERROR: cmdTopic '" + str(cmdTopic) + "' is already defined for another device! Must be unique.")
  148. raise
  149. else:
  150. InTopicsToDevIds[cmdTopic] = deviceid
  151. if debug:
  152. log_write("")
  153. log_write("")
  154. log_write("")
  155. log_write("RXcodesToDevFunction:")
  156. log_write(RXcodesToDevFunction)
  157. log_write("")
  158. log_write("")
  159. log_write("InTopicsToDevIds:")
  160. log_write(InTopicsToDevIds)
  161. log_write("")
  162. log_write("")
  163. log_write("InTopicsToDevIds.keys():")
  164. log_write(InTopicsToDevIds.keys())
  165. log_write("")
  166. log_write("")
  167. log_write("==== parsing config file complete ====")
  168. log_write("")
  169. log_write("")
  170. log_write("")
  171. log_write("")
  172. except ConstructorError as err:
  173. log_write("ERROR on parsing configfile:")
  174. log_write(err)
  175. exit(1)
  176. except:
  177. log_write("ERROR opening configfile")
  178. exit(1)
  179. ## #mqtt_topic_in = "cul/in"
  180. ##
  181. ##
  182. lastReceived = ["","","","","","","","","",""]
  183. lastReceivedTime = [0,0,0,0,0,0,0,0,0,0]
  184. lastReceivedIndex = 0
  185. lastReceivedMaxAge = 500 # ignore repeated messages when they are younger than x ms
  186. lastSentTime = {}
  187. lastSentMinInterval = 1500 # ignore repeated messages when they are younger than x ms, should be > 1500
  188. lastSentCmd = ""
  189. lastSentCmdTime = 0
  190. lastSentDev = ""
  191. lastSentDevCmd = ""
  192. ##
  193. ##
  194. ##
  195. ## global lastAction_bwm1
  196. ## global lastAction_bwm2
  197. ## global lastAction_bwm3
  198. ## lastAction_bwm1 = 0
  199. ## lastAction_bwm2 = 0
  200. ## lastAction_bwm3 = 0
  201. def touch(fname, times=None):
  202. with open(fname, 'a'):
  203. os.utime(fname, times)
  204. def on_connect(client, userdata, flags, rc):
  205. if verbose:
  206. log_write("MQTT connected with result code " + str(rc))
  207. if receive_from_mqtt_cul:
  208. if mqtt_topic_cul_received != "":
  209. client.subscribe(mqtt_topic_cul_received)
  210. for in_topic in InTopicsToDevIds.keys():
  211. if in_topic != "":
  212. client.subscribe(in_topic)
  213. if verbose:
  214. log_write("MQTT subscribed: " + in_topic)
  215. def on_disconnect(client, userdata, rc):
  216. if rc != 0:
  217. log_write("Unexpected MQTT disconnection. Will auto-reconnect")
  218. def on_message(client, userdata, msg):
  219. #print(msg.topic + ": " + str(msg.payload))
  220. payload = msg.payload.decode("utf-8")
  221. if verbose:
  222. log_write("MQTT received: " + msg.topic + " -> " + str(payload))
  223. #log_write("")
  224. #print("TOPIC: ")
  225. #print(msg.topic)
  226. #print("TOPIC REC: ")
  227. #print(mqtt_topic_cul_received)
  228. # MQTT message is output from CUL
  229. if receive_from_mqtt_cul and msg.topic == mqtt_topic_cul_received:
  230. payload = payload.rstrip()
  231. if verbose:
  232. log_write("")
  233. log_write("MQTT-CUL RX: '" + payload + "'")
  234. cul_received(payload, "MQTT")
  235. else:
  236. for in_topic, dev in InTopicsToDevIds.items():
  237. if msg.topic == in_topic:
  238. #log_write("")
  239. log_write("MQTT received - '" + msg.topic + "' = '" + payload + "' => DEV: " + dev)
  240. #print(devdata[dev])
  241. if 'name' in devdata[dev].keys():
  242. log_write('devName: ' + devdata[dev]['name'])
  243. if 'statTopic' in devdata[dev].keys():
  244. log_write('statTopic: ' + devdata[dev]['statTopic'])
  245. mqttc.publish(devdata[dev]['statTopic'], payload, qos=0, retain=False)
  246. if 'add_statTopics_on' in devdata[dev].keys():
  247. log_write("add_statTopics_on:")
  248. for res in devdata[dev]['add_statTopics_on']:
  249. #print(res)
  250. if 'on_payload' in res and 'topic' in res and 'payload' in res:
  251. if payload == res['on_payload'] and payload != "" and res['topic'] != "" and res['payload'] != "":
  252. log_write(" on '" + payload + "': '" + res['payload'] + "' => '" + res['topic'] + "'")
  253. mqttc.publish(res[topic], res[payload], qos=0, retain=False)
  254. #if 'TX' in devdata[dev].keys():
  255. # if payload in devdata[dev]['TX']:
  256. # txstring = devdata[dev]['TX'][payload]
  257. # print("TX code for '" + payload + "' is: " + txstring)
  258. TX_interface = TX_interface_prefer
  259. global lastSentDev, lastSentDevCmd, lastSentCmdTime
  260. now = int(round(time.time() * 1000))
  261. if debug:
  262. log_write("dev="+dev+", lastSentDevCmd="+lastSentDevCmd)
  263. if dev == lastSentDev and payload == lastSentDevCmd and (now - lastSentCmdTime) < 1000:
  264. if verbose:
  265. log_write("MQTT: ignored command as we just sent this.")
  266. pass
  267. else:
  268. cul_send(dev, payload)
  269. #if 'TX_interface' in devdata[dev].keys():
  270. # print("TX_interface: " + devdata[dev]['TX_interface'])
  271. #
  272. #if TX_interface == "MQTT" or TX_interface == "both":
  273. # print("TX via MQTT")
  274. # cul_send(dev, payload)
  275. #
  276. #if TX_interface == "UART" or TX_interface == "both":
  277. # print("TX via UART")
  278. # cul_send(dev, payload)
  279. def publish_device_statusupdate(device, cmd):
  280. if device in devdata.keys():
  281. if 'statTopic' in devdata[device].keys():
  282. statTopic = devdata[device]['statTopic']
  283. log_write("MQTT publish: '" + cmd + "' -> '" + statTopic + "'")
  284. mqttc.publish(statTopic, cmd, qos=0, retain=False)
  285. if 'add_statTopics_on' in devdata[device].keys():
  286. log_write(" MQTT publish add_statTopics_on:")
  287. for res in devdata[device]['add_statTopics_on']:
  288. #print(res)
  289. if 'on_payload' in res and 'topic' in res and 'payload' in res:
  290. if cmd == res['on_payload']:
  291. log_write(" on '" + res['on_payload'] + "' -> publish '" + res['payload'] + "' on topic '" + res['topic'] + "'")
  292. mqttc.publish(res['topic'], res['payload'], qos=0, retain=False)
  293. if 'add_statTopics' in devdata[device].keys():
  294. log_write(" MQTT publish on add_statTopics:")
  295. for res in devdata[device]['add_statTopics']:
  296. log_write(" '" + cmd + "' -> '" + res + "'")
  297. mqttc.publish(res, cmd, qos=0, retain=False)
  298. def parseRXCode(rx_code, source_cul):
  299. receivedForDevice = None
  300. receivedCmnd = None
  301. if rx_code in RXcodesToDevFunction:
  302. receivedForDevice = RXcodesToDevFunction[rx_code][0]
  303. receivedCmnd = RXcodesToDevFunction[rx_code][1]
  304. #print("receivedForDevice = " + receivedForDevice + ", receivedCmnd = " + receivedCmnd)
  305. #log_write("DEV: " + receivedForDevice + ", CMD: " + receivedCmnd + ", RX: " + rx_code)
  306. log_write("")
  307. log_write("CUL '" + source_cul + "' received '" + rx_code + "' => DEV: " + receivedForDevice + ", CMD: " + receivedCmnd)
  308. else:
  309. if rx_code.startswith("i"):
  310. receivedForDevice, receivedCmnd = decodeInterTechnoRX(rx_code)
  311. print(receivedForDevice, receivedCmnd)
  312. if receivedForDevice != None and receivedCmnd != None:
  313. publish_device_statusupdate(receivedForDevice, receivedCmnd)
  314. def decodeInterTechnoRX(rx_code):
  315. # decode old fixed code from InterTechno remotes
  316. _housecode = None
  317. _devaddr = None
  318. _command = None
  319. _itname = None
  320. #print(rx_code[0:1])
  321. #print(rx_code[1:3])
  322. #print(rx_code[3:5])
  323. #print(rx_code[5:7])
  324. if rx_code[0:1] == "i":
  325. if rx_code[1:3] == "00": _housecode = "A"
  326. elif rx_code[1:3] == "40": _housecode = "B"
  327. elif rx_code[1:3] == "10": _housecode = "C"
  328. elif rx_code[1:3] == "50": _housecode = "D"
  329. elif rx_code[1:3] == "04": _housecode = "E"
  330. elif rx_code[1:3] == "44": _housecode = "F"
  331. elif rx_code[1:3] == "14": _housecode = "G"
  332. elif rx_code[1:3] == "54": _housecode = "H"
  333. elif rx_code[1:3] == "01": _housecode = "I"
  334. elif rx_code[1:3] == "41": _housecode = "J"
  335. elif rx_code[1:3] == "11": _housecode = "K"
  336. elif rx_code[1:3] == "51": _housecode = "L"
  337. elif rx_code[1:3] == "05": _housecode = "M"
  338. elif rx_code[1:3] == "45": _housecode = "N"
  339. elif rx_code[1:3] == "15": _housecode = "O"
  340. elif rx_code[1:3] == "55": _housecode = "P"
  341. if rx_code[3:5] == "00": _devaddr = "1"
  342. elif rx_code[3:5] == "40": _devaddr = "2"
  343. elif rx_code[3:5] == "10": _devaddr = "3"
  344. elif rx_code[3:5] == "50": _devaddr = "4"
  345. elif rx_code[3:5] == "04": _devaddr = "5"
  346. elif rx_code[3:5] == "44": _devaddr = "6"
  347. elif rx_code[3:5] == "14": _devaddr = "7"
  348. elif rx_code[3:5] == "54": _devaddr = "8"
  349. elif rx_code[3:5] == "01": _devaddr = "9"
  350. elif rx_code[3:5] == "41": _devaddr = "10"
  351. elif rx_code[3:5] == "11": _devaddr = "11"
  352. elif rx_code[3:5] == "51": _devaddr = "12"
  353. elif rx_code[3:5] == "05": _devaddr = "13"
  354. elif rx_code[3:5] == "45": _devaddr = "14"
  355. elif rx_code[3:5] == "15": _devaddr = "15"
  356. elif rx_code[3:5] == "55": _devaddr = "16"
  357. if rx_code[5:7] == "15": _command = "ON"
  358. elif rx_code[5:7] == "14": _command = "OFF"
  359. if _housecode != None and _devaddr != None and _command != None:
  360. _itname = "IT_" + _housecode + _devaddr
  361. #print("valid IT code: '" + _itname + "' => '" + _command + "'")
  362. return _itname, _command
  363. else:
  364. return False
  365. #else:
  366. # print("unknown or invalid IT code '" + rx_code + "'")
  367. #else:
  368. # print("unknown or invalid IT code '" + rx_code + "'")
  369. def encodeInterTechnoRX(itname, cmd):
  370. # decode old fixed code from InterTechno remotes
  371. _housecode = None
  372. _devaddr = None
  373. _command = None
  374. #print(itname[0:3])
  375. #print(itname[3:4])
  376. #print(itname[4:])
  377. if itname[0:3] == "IT_":
  378. if itname[3:4] == "A": _housecode = "00"
  379. elif itname[3:4] == "B": _housecode = "40"
  380. elif itname[3:4] == "C": _housecode = "10"
  381. elif itname[3:4] == "D": _housecode = "50"
  382. elif itname[3:4] == "E": _housecode = "04"
  383. elif itname[3:4] == "F": _housecode = "44"
  384. elif itname[3:4] == "G": _housecode = "14"
  385. elif itname[3:4] == "H": _housecode = "54"
  386. elif itname[3:4] == "I": _housecode = "01"
  387. elif itname[3:4] == "J": _housecode = "41"
  388. elif itname[3:4] == "K": _housecode = "11"
  389. elif itname[3:4] == "L": _housecode = "51"
  390. elif itname[3:4] == "M": _housecode = "05"
  391. elif itname[3:4] == "N": _housecode = "45"
  392. elif itname[3:4] == "O": _housecode = "15"
  393. elif itname[3:4] == "P": _housecode = "55"
  394. if itname[4:] == "1": _devaddr = "00"
  395. elif itname[4:] == "2": _devaddr = "40"
  396. elif itname[4:] == "3": _devaddr = "10"
  397. elif itname[4:] == "4": _devaddr = "50"
  398. elif itname[4:] == "5": _devaddr = "04"
  399. elif itname[4:] == "6": _devaddr = "44"
  400. elif itname[4:] == "7": _devaddr = "14"
  401. elif itname[4:] == "8": _devaddr = "54"
  402. elif itname[4:] == "9": _devaddr = "01"
  403. elif itname[4:] == "10": _devaddr = "41"
  404. elif itname[4:] == "11": _devaddr = "11"
  405. elif itname[4:] == "12": _devaddr = "51"
  406. elif itname[4:] == "13": _devaddr = "05"
  407. elif itname[4:] == "14": _devaddr = "45"
  408. elif itname[4:] == "15": _devaddr = "15"
  409. elif itname[4:] == "16": _devaddr = "55"
  410. if cmd == "ON": _command = "15"
  411. elif cmd == "OFF": _command = "14"
  412. #print("IT housecode=", _housecode, "- devaddr=", _devaddr, "- command=", _command)
  413. if _housecode != None and _devaddr != None and _command != None:
  414. _rxcode = "i" + _housecode + _devaddr + _command
  415. #print("encoded IT RX code: '" + itname + "' => '" + cmd + "' = '" + _rxcode)
  416. return _rxcode
  417. else:
  418. return False
  419. #else:
  420. # print("unknown or invalid IT code '" + rx_code + "'")
  421. #else:
  422. # print("unknown or invalid IT code '" + rx_code + "'")
  423. def cul_received(payload, source_cul):
  424. #global lastAction_bwm1, lastAction_bwm2, lastAction_bwm3
  425. global lastReceivedTime, lastReceivedIndex, lastReceivedMaxAge
  426. #actionSupported = True
  427. isSendReply = False
  428. if payload[:2] == 'is': # msg is reply from CUL to raw send command
  429. isSendReply = True
  430. elif payload[:1] == 'i': # is a IT compatible command - so look it up in the code table
  431. inCmd = payload[:-2] #strip last 2 chars, these only represent signal strength and are not needed
  432. if verbose:
  433. log_write("inCmd: " + inCmd + ", CUL: " + source_cul)
  434. ignoreCommand = False
  435. lastReceivedLength = len(lastReceived)
  436. i = 0
  437. while i < lastReceivedLength:
  438. #print(str(i) + ": " + lastReceived[i])
  439. if lastReceived[i] == inCmd:
  440. lastTime = int(lastReceivedTime[i])
  441. now = int(round(time.time() * 1000))
  442. tdelta = (now - lastTime)
  443. #print("TDELTA = " + str(tdelta))
  444. if tdelta < lastReceivedMaxAge:
  445. log_write("ignoring command from CUL '" + source_cul + "', CMD: '" + inCmd + "' - already received " + str(tdelta) + " ms ago")
  446. ignoreCommand = True
  447. #break
  448. i += 1
  449. if not ignoreCommand:
  450. lastReceived[lastReceivedIndex] = inCmd
  451. lastReceivedTime[lastReceivedIndex] = int(round(time.time() * 1000))
  452. if lastReceivedIndex >= (lastReceivedLength - 1):
  453. lastReceivedIndex = 0
  454. else:
  455. lastReceivedIndex += 1
  456. parseRXCode(inCmd, source_cul)
  457. #if repeat_received_commands:
  458. # lastSentLength = len(lastSent)
  459. # i = 0
  460. # dontRepeat = False
  461. # while i < lastSentLength:
  462. # #print(str(i) + ": " + lastReceived[i])
  463. # if lastSent[i] == decCmd:
  464. # lastTime = int(lastSentTime[i])
  465. # now = int(round(time.time() * 1000))
  466. # tdelta = (now - lastTime)
  467. # #print("TDELTA = " + str(tdelta))
  468. # if tdelta < lastSentMaxAge:
  469. # print("ignoring command as it originated from ourselfs " + inCmd + " " + str(tdelta) + " ms ago")
  470. # dontRepeat = True
  471. # #break
  472. # i += 1
  473. # #cmdToSend = culSendCmds[decCmd]
  474. # if not dontRepeat:
  475. # if device != "" and cmd != "":
  476. # print("REPEATING COMMAND: " + cmd + " TO DEVICE " + device)
  477. # cul_send(device, cmd)
  478. def IT_RXtoTXCode(itReceiveCode):
  479. if debug:
  480. statusstr = "IT_RXtoTXCode "
  481. statusstr += "RX: "
  482. statusstr += itReceiveCode
  483. #print("IT_RXtoTXCode ReceiveCode: " + itReceiveCode)
  484. itReceiveCode = itReceiveCode[1:] # remove first character "i"
  485. itReceiveCodeLengthBytes = int(len(itReceiveCode)/2)
  486. itReceiveCodeBytes = []
  487. itTransmitTristate = "is"
  488. for x in range(itReceiveCodeLengthBytes):
  489. itReceiveCodeBytes.append(bin(int(itReceiveCode[x*2:(x*2+2)],16))[2:].zfill(8))
  490. for x in range(len(itReceiveCodeBytes)):
  491. #print("IT REC byte " + str(x) + " = " + str(itReceiveCodeBytes[x]))
  492. for y in range(4):
  493. quarterbyte = str(itReceiveCodeBytes[x][y*2:y*2+2])
  494. if quarterbyte == "00":
  495. tmpTristate = "0";
  496. elif quarterbyte == "01":
  497. tmpTristate = "F";
  498. elif quarterbyte == "10":
  499. tmpTristate = "D";
  500. elif quarterbyte == "11":
  501. tmpTristate = "1";
  502. #print(quarterbyte + " -> " + tmpTristate)
  503. itTransmitTristate = itTransmitTristate + tmpTristate
  504. if debug:
  505. statusstr += " -> TX: "
  506. statusstr += itTransmitTristate
  507. #print("IT_RXtoTXCode TransmitCode: " + itTransmitTristate)
  508. log_write(statusstr)
  509. return(itTransmitTristate)
  510. def cul_send(device, cmd):
  511. global lastSentTime
  512. culCmd = ""
  513. culSendCmdsKeyName = device + ' ' + cmd
  514. #print culSendCmdsKeyName
  515. #statTopic = False
  516. #if 'statTopic' in devdata[device].keys():
  517. # statTopic = devdata[device]['statTopic']
  518. # #print('statTopic: ' + devdata[device]['statTopic'])
  519. #if statTopic:
  520. # mqttc.publish(statTopic, cmd, qos=0, retain=False)
  521. # print(" MQTT: published '" + cmd + "' on statTopic: '" + devdata[device]['statTopic'])
  522. publish_device_statusupdate(device, cmd)
  523. tx_code = False
  524. if 'TX' in devdata[device]:
  525. #print("TX data available, cmd="+cmd)
  526. #print(devdata[device]['TX'])
  527. if cmd in devdata[device]['TX']:
  528. tx_code = devdata[device]['TX'][cmd]
  529. log_write(" TX code for '" + cmd + "': " + tx_code)
  530. if not tx_code:
  531. print(" deviceID: ", device)
  532. if 'RX' in devdata[device]:
  533. print(" RX code configured, cmd=" + cmd)
  534. print(devdata[device]['RX'])
  535. if cmd in devdata[device]['RX']:
  536. rx_code = devdata[device]['RX'][cmd]
  537. print(" RX code for '" + cmd + "': " + rx_code)
  538. tx_code = IT_RXtoTXCode(rx_code)
  539. log_write(" TX code for '" + cmd + "': " + tx_code)
  540. elif device.startswith("IT_"):
  541. # InterTechno device with fixed code - encode RX code for IT device name and convert to TX code
  542. rx_code = encodeInterTechnoRX(device, cmd)
  543. if rx_code:
  544. print(" RX code for '" + cmd + "': " + rx_code)
  545. tx_code = IT_RXtoTXCode(rx_code)
  546. log_write(" TX code for '" + cmd + "': " + tx_code)
  547. if not tx_code:
  548. log_write(" no valid TX code for this device/command")
  549. else:
  550. now = int(round(time.time() * 1000))
  551. # look if this command has been sent in the past, and when
  552. if culSendCmdsKeyName in lastSentTime.keys():
  553. lastTime = lastSentTime[culSendCmdsKeyName]
  554. else:
  555. lastTime = 0
  556. lastTimeAge = now - lastTime
  557. log_write(' lastTime: ' + str(lastTimeAge) + 'ms ago')
  558. if lastTimeAge > lastSentMinInterval: # only send if last time + min interval is exceeded
  559. lastSentTime[culSendCmdsKeyName] = now # save what we send, so that we dont repeat our own sent messages if repeating is enabled
  560. TX_interface = TX_interface_prefer
  561. if 'TX_interface' in devdata[device].keys():
  562. log_write(" TX_interface: " + devdata[device]['TX_interface'])
  563. if TX_interface == "UART" and not serialCULAvailable:
  564. TX_interface = "MQTT"
  565. global lastSentCmd, lastSentCmdTime, lastSentDev, lastSentDevCmd
  566. lastSentCmd = tx_code
  567. lastSentCmdTime = now
  568. lastSentDev = device
  569. lastSentDevCmd = cmd
  570. if send_on_mqtt_cul and (TX_interface == "MQTT" or TX_interface == "both"):
  571. log_write(" TX via MQTT: " + tx_code)
  572. mqttc.publish(mqtt_topic_cul_send, tx_code, qos=0, retain=False)
  573. if serialCULAvailable and send_on_serial_cul and (TX_interface == "UART" or TX_interface == "both"):
  574. log_write(" TX via UART: " + tx_code)
  575. culCmd = tx_code + '\r\n'
  576. ser.write(culCmd.encode('utf-8'))
  577. else:
  578. log_write("WARNING: CUL send command repeated too quickly.")
  579. # main
  580. if receive_from_serial_cul or send_on_serial_cul:
  581. if not os.path.exists(serialPort):
  582. log_write("ERROR opening connection to serial CUL... device '" + serialPort + "' does not exist.")
  583. if log_enable: writeLog("CUL2MQTT v"+version+" starting")
  584. if receive_from_mqtt_cul:
  585. if forceSerialCULConnected:
  586. exit(2)
  587. else:
  588. log_write("resuming in MQTT-CUL only mode...")
  589. TX_interface_prefer = "MQTT"
  590. receive_from_serial_cul = False
  591. send_on_serial_cul = False
  592. serialCULAvailable = False
  593. log_write("")
  594. log_write("")
  595. else:
  596. log_write("opening connection to serial CUL...")
  597. serLine = ""
  598. ser = serial.Serial(port=serialPort,baudrate=serialBaudrate,parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=serialTimeout)
  599. sleep(culInitTimeout)
  600. ser.write('V\r\n'.encode('utf-8')) # get CUL version info
  601. serLine = ser.readline()
  602. serLine = serLine.decode('utf-8').rstrip('\r\n')
  603. if serLine.startswith("V ") and serLine.find("culfw") != -1:
  604. log_write("connected. CUL version: " + serLine)
  605. serialCULAvailable = True
  606. sleep(0.1)
  607. log_write('Initializing CUL with command: ' + culInitCmd.rstrip('\r\n'))
  608. ser.write(culInitCmd.encode('utf-8')) # initialize CUL in normal receive mode
  609. sleep(0.5)
  610. else:
  611. log_write("WARNING: could not connect serial CUL")
  612. receive_from_serial_cul = False
  613. send_on_serial_cul = False
  614. serialCULAvailable = False
  615. TX_interface_prefer = "MQTT"
  616. if forceSerialCULConnected:
  617. exit(2)
  618. mqttc = mqtt.Client()
  619. mqttc.on_connect = on_connect
  620. mqttc.on_disconnect = on_disconnect
  621. mqttc.on_message = on_message
  622. if len(mqtt_user) > 0 and len(mqtt_password) > 0:
  623. mqttc.username_pw_set(mqtt_user, mqtt_password)
  624. mqttc.connect(mqtt_server, mqtt_port, 60)
  625. mqttc.loop_start()
  626. while True:
  627. if receive_from_serial_cul:
  628. serLine = ser.readline()
  629. if len(serLine) > 0:
  630. now = int(round(time.time() * 1000))
  631. recvCmd = serLine.decode('utf-8')
  632. recvCmd = recvCmd.rstrip('\r\n')
  633. #if debug:
  634. # print("lastSentCmd: " + lastSentCmd + ", lastSentCmdTime=" + str(lastSentCmdTime))
  635. if recvCmd == lastSentCmd and (now - lastSentCmdTime) < filterSelfSentIncomingTimeout:
  636. pass
  637. else:
  638. if verbose:
  639. log_write("")
  640. log_write("Serial-CUL RX: '" + recvCmd + "'")
  641. cul_received(recvCmd, "UART")
  642. sleep(0.05)
  643. ## #print "test"
  644. ## #touch("/tmp/culagent_running")
  645. #except KeyboardInterrupt:
  646. # print("\n")