Advertisement
Guest User

Untitled

a guest
Sep 26th, 2022
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.01 KB | Source Code | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. import datetime, time, sys, os, serial, signal
  4. import paho.mqtt.client as paho
  5.  
  6. serial_port = "/dev/ttyUSB1"
  7. serial_bps = 9600
  8.  
  9. MQTT_ADDRESS = '192.168***'
  10. MQTT_PORT = 1883
  11. MQTT_USER = '***'
  12. MQTT_PASSWORD = '***'
  13. MQTT_TOPIC = 'itr/*****'
  14. MQTT_CLIENT_ID = 'serial2mqtt'
  15. mqtt = None
  16. last_value = {}
  17. precision = 3
  18. verbose = 0
  19.  
  20. def debug(*args, end='\n'):
  21. if verbose:
  22. for i in list(args):
  23. print(i, end='')
  24. print(end, end='')
  25.  
  26. def search_key(code):
  27. for key in obis_keys:
  28. if key[0] == code:
  29. return key
  30.  
  31. # OBIS-Kennzahl, Textbeschreibung, mqtt-topic, encoding
  32. obis_keys = [
  33. [ b'\x01\x00\x60\x01\x00\xFF', 'Seriennummer', 'serial', 'serial'],
  34. [ b'\x01\x00\x60\x32\x01\x01', 'Hersteller-Identifikation', 'manufacturer', 'bytes'],
  35. # [ b'\x01\x00\x00\x00\x09\xFF', 'Server-Id / Geraeteeinzelidentifikation', 'server-id'],
  36. [ b'\x01\x00\x01\x08\x00\xFF', 'Zaehlwerk pos. Wirkenergie (Bezug), tariflos', 'import/total'],
  37. [ b'\x01\x00\x01\x08\x01\xFF', 'Zaehlwerk pos. Wirkenergie (Bezug), Tarif 1', 'import/tarif_1/total'],
  38. # [ b'\x01\x00\x01\x08\x02\xFF', 'Zaehlwerk pos. Wirkenergie (Bezug), Tarif 2', 'import/tarif_2/total'],
  39. [ b'\x01\x00\x02\x08\x00\xFF', 'Zaehlwerk neg. Wirkenergie (Einspeisung), tariflos', 'export/total'],
  40. # [ b'\x01\x00\x02\x08\x01\xFF', 'Zaehlwerk neg. Wirkenergie (Einspeisung), Tarif 1', 'export/tarif_1/total'],
  41. # [ b'\x01\x00\x02\x08\x02\xFF', 'Zaehlwerk neg. Wirkenergie (Einspeisung), Tarif 2', 'export/tarif_2/total'],
  42. [ b'\x01\x00\x01\x07\x00\xFF', 'Aktuelle Wirkleistung gesamt', 'import/power'],
  43. # [ b'\x01\x00\x24\x07\x00\xFF', 'Aktuelle Wirkleistung L1', 'l1/power'],
  44. # [ b'\x01\x00\x38\x07\x00\xFF', 'Aktuelle Wirkleistung L2', 'l2/power'],
  45. # [ b'\x01\x00\x4c\x07\x00\xFF', 'Aktuelle Wirkleistung L3', 'l3/power'],
  46. # [ b'\x81\x81\xC7\x82\x05\xFF', 'Public Key', 'public_key']
  47. ]
  48.  
  49. # https://github.com/volkszaehler/libsml/blob/master/examples/unit.h
  50. obis_unit = {
  51. 0:'',
  52. 27:'W',
  53. 28:'VA',
  54. 30:'Wh',
  55. 33:'A',
  56. 35:'V',
  57. 44:'Hz'
  58. }
  59.  
  60. crc16_x25_table = [
  61. 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF,
  62. 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7,
  63. 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E,
  64. 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876,
  65. 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD,
  66. 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5,
  67. 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C,
  68. 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974,
  69. 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
  70. 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3,
  71. 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A,
  72. 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72,
  73. 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9,
  74. 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1,
  75. 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738,
  76. 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70,
  77. 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7,
  78. 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
  79. 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036,
  80. 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E,
  81. 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5,
  82. 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD,
  83. 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134,
  84. 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C,
  85. 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3,
  86. 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB,
  87. 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
  88. 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A,
  89. 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1,
  90. 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9,
  91. 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330,
  92. 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78]
  93.  
  94. def crc16_x25(Buffer):
  95. crcsum = 0xffff
  96. global crc16_x25_table
  97. for byte in Buffer:
  98. crcsum = crc16_x25_table[(byte ^ crcsum) & 0xff] ^ (crcsum >> 8 & 0xff)
  99. crcsum ^= 0xffff
  100. return crcsum
  101.  
  102. def parse_SML_file(sml):
  103. obis_list = []
  104. active_list = None
  105.  
  106. # Telegramm prüfen
  107. start = b'\x1b\x1b\x1b\x1b\x01\x01\x01\x01'
  108. end = b'\x1b\x1b\x1b\x1b\x1a'
  109. debug(sml.hex())
  110.  
  111. try:
  112. message = sml[0:-2] #letzen beiden Bytes wegschneiden
  113. crc_rx = int.from_bytes([sml[-1], sml[-2]], 'big') #CRC Bytes umgedreht in eine Variable speichern
  114. #print("crc: " + str(crc_rx))
  115. except IndexError:
  116. debug("Error: SML CRC")
  117. return
  118.  
  119. #print("message: " + message[0:8].hex() + "\tstart: " + start.hex())
  120.  
  121. if message[0:8] != start: #die ersten 8 Bytes prüfen
  122. debug("Error: SML start")
  123. return
  124.  
  125. #print("message: " + message[-5:].hex() + "\tend: " + end.hex())
  126.  
  127. if message[-6:-1] != end: #die letzten 5 Bytes prüfen
  128. debug("Error: SML end")
  129. return
  130.  
  131. crc_calc = crc16_x25(message) #eigene Prüfsumme bilden
  132. if crc_rx != crc_calc: #Prüfsummen vergleichen
  133. debug("Error: CRC")
  134. return
  135.  
  136. level = [0]
  137. index = [0]
  138. x = 0
  139.  
  140. sml = sml[8:] # Start abschneiden
  141. # debug(sml.hex())
  142.  
  143. while x < len(sml):
  144. while level:
  145. if level[-1] == 0:
  146. level.pop()
  147. index.pop()
  148. active_list = False
  149. else:
  150. break
  151. if not level and sml[x] == 0x1B:
  152. break
  153.  
  154. if level:
  155. if level[-1]:
  156. level[-1] -= 1
  157. index[-1] += 1
  158.  
  159. debug(level, end=' ')
  160. if index:
  161. debug(index[-1], end='')
  162. debug("\n\t\t", end='')
  163.  
  164. for i in range(0, len(level)):
  165. debug(' ', end='')
  166.  
  167. debug("%02X" % sml[x], end='')
  168.  
  169. prefix = sml[x].to_bytes(1, byteorder='big')
  170.  
  171. al = (sml[x] & 0x0F) - 1
  172. buf = 0
  173.  
  174. if (sml[x] & 0x80) == 0x80:
  175. # Extended length
  176. x += 1
  177. prefix += sml[x].to_bytes(1, byteorder='big')
  178. debug("+%02X" % sml[x], end='')
  179. if sml[x] & 0x70:
  180. debug(" Unsupported!")
  181. al = ((sml[x-1] & 0x0F) << 4) | (sml[x] & 0x0F) - 2
  182.  
  183. debug(": ", end='')
  184.  
  185. if sml[x] & 0x70 == 0x70:
  186. # List of
  187. level.append(sml[x] & 0x0F)
  188. index.append(0)
  189. elif sml[x] & 0x7F == 0x00:
  190. # EndOfSmlMsg
  191. debug ("EndOfSmlMSg")
  192. elif sml[x] & 0x70 == 0x00:
  193. # Octet String
  194. buf = sml[x+1:x+1+al]
  195. x += al
  196. for i in buf:
  197. debug("%02X " % i, end='')
  198.  
  199. if index:
  200. if index[-1] == 1 and al == 6:
  201. key = search_key(buf)
  202. if key is not None:
  203. debug("\t# OBIS " + key[1])
  204. obis_list.append([key])
  205. active_list = True
  206.  
  207. elif sml[x] & 0x70 == 0x50:
  208. # IntergerX
  209. buf = sml[x+1:x+1+al]
  210. x += al
  211. for i in buf:
  212. debug("%02X " % i, end='')
  213. elif sml[x] & 0x70 == 0x60:
  214. # UnsignedX
  215. buf = sml[x+1:x+1+al]
  216. x += al
  217. for i in buf:
  218. debug("%02X "% i, end='')
  219. else:
  220. debug(" Unsupported!")
  221.  
  222. if active_list:
  223. obis_list[-1].append(prefix)
  224. obis_list[-1].append(buf)
  225.  
  226. debug("") # "\n"
  227. x +=1
  228.  
  229. return obis_list
  230.  
  231. def parse_SML_entry(data):
  232. key = data[0]
  233. debug("Entries:\t%d" % data[1][0])
  234.  
  235. debug("OBIS-Code:\t", end='')
  236. obis_code = ''
  237. sep = '-:..'
  238. for i in range(0, 5):
  239. obis_code += '%d' % data[2][i]
  240. if i != 4:
  241. obis_code += sep[i]
  242.  
  243. debug(obis_code)
  244. debug("Text:\t" + key[1])
  245. debug("status:\t%02X" % data[3][0] + "\t" + data[4].hex())
  246. debug("valTime:%02X" % data[5][0] + "\t" + data[6].hex())
  247. debug("unit:\t%02X" % data[7][0] + "\t" + data[8].hex())
  248. debug("scaler:\t%02X" % data[9][0] + "\t" + data[10].hex())
  249. debug("value:\t%02X" % data[11][0] + "\t" + data[12].hex())
  250. debug("valueSignature:\t%02X" % data[13][0] + "\t" + data[14].hex())
  251.  
  252. sml_unit = [data[7][0], data[8]]
  253. sml_scaler = [data[9][0], data[10]]
  254. sml_value = [data[11][0], data[12]]
  255. num = data[12]
  256.  
  257. signed = sml_scaler[0] & 0x70 == 0x50
  258. scaler = int.from_bytes(sml_scaler[1], byteorder="big", signed=signed)
  259.  
  260. # get unit
  261. if len(sml_unit[1]) > 0:
  262. unit = obis_unit.get(sml_unit[1][0], '')
  263. else:
  264. unit = ''
  265.  
  266. # decode byte-string
  267. if key[3:4] == ['bytes']:
  268. value = num.decode()
  269. elif key[3:4] == ['serial']:
  270. value = str(num[1:2][0]) + num[2:5].decode() + num[5:6].hex() + str(int(num[6:].hex(), 16))
  271. else:
  272. signed = sml_value[0] & 0x70 == 0x50
  273. value = int.from_bytes(sml_value[1], byteorder="big", signed=signed) * (10 ** scaler)
  274.  
  275. # Wh -> kWh
  276. if unit == 'Wh':
  277. value = float(round(value / 1000, precision))
  278. unit = 'k' + unit
  279.  
  280. debug("Wert: " + str(value) + " " + unit)
  281. debug("")
  282.  
  283. # # Update only if new value
  284. # if last_value.get(obis_code) != value:
  285. # last_value[obis_code] = value
  286. # mqtt_publish(key[2], value)
  287.  
  288. mqtt_error = {
  289. 0: 'Connection successful',
  290. 1: 'Incorrect protocol version',
  291. 2: 'Invalid client identifier',
  292. 3: 'Server unavailable',
  293. 4: 'Bad username or password',
  294. 5: 'Not authorised'
  295. }
  296.  
  297. def mqtt_on_connect(client, userdata, flags, rc):
  298. if rc == 0:
  299. debug('Connected to mqtt broker')
  300. mqtt_publish('status', 'online')
  301. else:
  302. print('mqtt connection failed: ' + mqtt_error[rc])
  303.  
  304. #def mqtt_on_publish(client, userdata, result):
  305. # pass
  306.  
  307. def mqtt_publish(topic, value):
  308. ret = mqtt.publish(MQTT_TOPIC + '/' + topic, value, retain=True)
  309. if ret.rc != 0:
  310. print('Error: mqtt publish failed')
  311.  
  312. def exit_handler(sig, frame):
  313. if verbose:
  314. print("Signal:" + str(sig))
  315. ser.close()
  316. mqtt_publish('status', 'disconnect')
  317. mqtt.disconnect()
  318. # send out last publish
  319. mqtt.loop_forever()
  320. sys.exit()
  321.  
  322. if __name__ == '__main__':
  323. # Kommandozeile prüfen
  324. verbose = True
  325. sml = ''
  326.  
  327. if len(sys.argv) > 1:
  328. for i in sys.argv[1:]:
  329. sml += i
  330.  
  331. sml = bytearray.fromhex(sml)
  332.  
  333. result = parse_SML_file(sml)
  334. if result:
  335. for i in result:
  336. if verbose:
  337. print(i)
  338. print()
  339. parse_SML_entry(i)
  340.  
  341. sys.exit()
  342.  
  343.  
  344. signal.signal(signal.SIGTERM, exit_handler)
  345. signal.signal(signal.SIGINT, exit_handler)
  346.  
  347. if verbose:
  348. print('Serial to MQTT bridge')
  349.  
  350. mqtt = paho.Client(MQTT_CLIENT_ID)
  351. mqtt.username_pw_set(MQTT_USER, MQTT_PASSWORD)
  352. mqtt.will_set(MQTT_TOPIC + '/' + 'status', 'offline', qos=0, retain=True)
  353. # mqtt.on_publish = mqtt_on_publish
  354. mqtt.on_connect = mqtt_on_connect
  355. #mqtt.on_message = on_message
  356. mqtt.connect(MQTT_ADDRESS, MQTT_PORT)
  357. mqtt.loop_start()
  358.  
  359. ser = serial.Serial(
  360. port=serial_port,
  361. baudrate=serial_bps,
  362. parity=serial.PARITY_NONE,
  363. stopbits=serial.STOPBITS_ONE,
  364. bytesize=serial.EIGHTBITS,
  365. timeout=0.2,
  366. xonxoff=False,
  367. rtscts=False,
  368. dsrdtr=False)
  369. ser.flushInput()
  370. ser.flushOutput()
  371.  
  372. while True:
  373. sml = b''
  374. while True:
  375. try:
  376. received = ser.read(50)
  377. except serial.serialutil.SerialException:
  378. debug("Error reading serial port")
  379. sys.exit()
  380. if not received:
  381. #time.sleep(5)
  382. break
  383. sml += received
  384.  
  385. if sml:
  386. result = parse_SML_file(sml)
  387. if result:
  388. for i in result:
  389. if verbose:
  390. print(i)
  391. print()
  392. parse_SML_entry(i)
  393.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement