11#!/usr/bin/python3 -u
2- #!/home/openhabian/Python3/Python-3.7.4/python -u
32#-u to unbuffer output. Otherwise when calling with nohup or redirecting output things are printed very lately or would even mixup
43
54print ("---------------------------------------------" )
6- print ("MiTemperature2 / ATC Thermometer version 3.1 " )
5+ print ("MiTemperature2 / ATC Thermometer version 4.0 " )
76print ("---------------------------------------------" )
87
8+ readme = """
9+
10+ Please read README.md in this folder. Latest version is available at https://github.com/JsBergbau/MiTemperature2#readme
11+ This file explains very detailed about the usage and covers everything you need to know as user.
12+
13+ """
14+
15+ print (readme )
16+
17+
918from bluepy import btle
1019import argparse
1120import os
2231import requests
2332import ssl
2433
34+
2535@dataclass
2636class Measurement :
2737 temperature : float
@@ -309,7 +319,7 @@ def MQTTOnDisconnect(client, userdata,rc):
309319 print ("MQTT disconnected, Client:" , client , "Userdata:" , userdata , "RC:" , rc )
310320
311321# Main loop --------
312- parser = argparse .ArgumentParser (allow_abbrev = False )
322+ parser = argparse .ArgumentParser (allow_abbrev = False , epilog = readme )
313323parser .add_argument ("--device" ,"-d" , help = "Set the device MAC-Address in format AA:BB:CC:DD:EE:FF" ,metavar = 'AA:BB:CC:DD:EE:FF' )
314324parser .add_argument ("--battery" ,"-b" , help = "Get estimated battery level, in ATC-Mode: Get battery level from device" , metavar = '' , type = int , nargs = '?' , const = 1 )
315325parser .add_argument ("--count" ,"-c" , help = "Read/Receive N measurements and then exit script" , metavar = 'N' , type = int )
@@ -319,7 +329,7 @@ def MQTTOnDisconnect(client, userdata,rc):
319329
320330
321331rounding = parser .add_argument_group ("Rounding and debouncing" )
322- rounding .add_argument ("--round" ,"-r" , help = "Round temperature to one decimal place" ,action = 'store_true' )
332+ rounding .add_argument ("--round" ,"-r" , help = "Round temperature to one decimal place (and in ATC mode humidity to whole numbers) " ,action = 'store_true' )
323333rounding .add_argument ("--debounce" ,"-deb" , help = "Enable this option to get more stable temperature values, requires -r option" ,action = 'store_true' )
324334
325335offsetgroup = parser .add_argument_group ("Offset calibration mode" )
@@ -368,12 +378,12 @@ def MQTTOnDisconnect(client, userdata,rc):
368378 port = int (mqttConfig ["MQTT" ]["port" ])
369379
370380 # MQTTS parameters
371- tls = int (mqttConfig ["MQTT" ]["tls" ])
372- cacerts = mqttConfig [ "MQTT" ][ "cacerts" ] if mqttConfig [ "MQTT" ][ "cacerts" ] else None
373- certificate = mqttConfig ["MQTT" ]["certificate " ] if mqttConfig ["MQTT" ]["certificate " ] else None
374- certificate_key = mqttConfig ["MQTT" ]["certificate_key " ] if mqttConfig ["MQTT" ]["certificate_key " ] else None
375- insecure = int ( mqttConfig ["MQTT" ]["insecure" ])
376-
381+ tls = int (mqttConfig ["MQTT" ]["tls" ]) if "tls" in mqttConfig [ "MQTT" ] else 0
382+ if tls != 0 :
383+ cacerts = mqttConfig ["MQTT" ]["cacerts " ] if mqttConfig ["MQTT" ]["cacerts " ] else None
384+ certificate = mqttConfig ["MQTT" ]["certificate " ] if mqttConfig ["MQTT" ]["certificate " ] else None
385+ certificate_key = mqttConfig ["MQTT" ]["certificate_key" ] if mqttConfig [ "MQTT" ][ "certificate_key" ] else None
386+ insecure = int ( mqttConfig [ "MQTT" ][ "insecure" ])
377387 username = mqttConfig ["MQTT" ]["username" ]
378388 password = mqttConfig ["MQTT" ]["password" ]
379389 MQTTTopic = mqttConfig ["MQTT" ]["topic" ]
@@ -402,7 +412,6 @@ def MQTTOnDisconnect(client, userdata,rc):
402412 if len (lwt ) > 0 :
403413 print ("Using lastwill with topic:" ,lwt ,"and message:" ,lastwill )
404414 client .will_set (lwt ,lastwill ,qos = 1 )
405-
406415 # MQTTS parameters
407416 if tls :
408417 client .tls_set (cacerts , certificate , certificate_key , cert_reqs = ssl .CERT_REQUIRED , tls_version = ssl .PROTOCOL_TLS , ciphers = None )
@@ -411,6 +420,7 @@ def MQTTOnDisconnect(client, userdata,rc):
411420 client .connect_async (broker ,port )
412421 MQTTClient = client
413422
423+
414424if args .device :
415425 if re .match ("[0-9a-fA-F]{2}([:]?)[0-9a-fA-F]{2}(\\ 1[0-9a-fA-F]{2}){4}$" ,args .device ):
416426 adress = args .device
@@ -530,19 +540,21 @@ def MQTTOnDisconnect(client, userdata,rc):
530540 print ("----------------------------" )
531541 print ("In this mode all devices within reach are read out, unless a devicelistfile and --onlydevicelist is specified." )
532542 print ("Also --name Argument is ignored, if you require names, please use --devicelistfile." )
533- print ("In this mode rounding and debouncing are not available, since ATC firmware sends out only one decimal place." )
543+ print ("In this mode debouncing is not available. Rounding option will round humidity and temperature to one decimal place." )
534544 print ("ATC mode usually requires root rights. If you want to use it with normal user rights, \n please execute \" sudo setcap cap_net_raw,cap_net_admin+eip $(eval readlink -f `which python3`)\" " )
535545 print ("You have to redo this step if you upgrade your python version." )
536546 print ("----------------------------" )
537547
538548 import sys
539549 import bluetooth ._bluetooth as bluez
550+ import cryptoFunctions
540551
541552 from bluetooth_utils import (toggle_device ,
542553 enable_le_scan , parse_le_advertising_events ,
543554 disable_le_scan , raw_packet_to_str )
544555
545- advCounter = dict ()
556+ advCounter = dict ()
557+ #encryptedPacketStore=dict()
546558 sensors = dict ()
547559 if args .devicelistfile :
548560 #import configparser
@@ -557,6 +569,15 @@ def MQTTOnDisconnect(client, userdata,rc):
557569 sensorsnew [key .upper ()] = sensors [key ]
558570 sensors = sensorsnew
559571
572+ #loop through sensors to generate key
573+ sensorsnew = sensors
574+ for sensor in sensors :
575+ if "decryption" in sensors [sensor ]:
576+ if sensors [sensor ]["decryption" ][0 ] == "k" :
577+ sensorsnew [sensor ]["key" ] = sensors [sensor ]["decryption" ][1 :]
578+ #print(sensorsnew[sensor]["key"])
579+ sensors = sensorsnew
580+
560581 if args .onlydevicelist and not args .devicelistfile :
561582 print ("Error: --onlydevicelist requires --devicelistfile <devicelistfile>" )
562583 os ._exit (1 )
@@ -581,83 +602,152 @@ def le_advertise_packet_handler(mac, adv_type, data, rssi):
581602 lastBLEPaketReceived = time .time ()
582603 lastBLEPaketReceived = time .time ()
583604 data_str = raw_packet_to_str (data )
584- preeamble = "10161a18 "
605+ preeamble = "161a18 "
585606 paketStart = data_str .find (preeamble )
586607 offset = paketStart + len (preeamble )
587- #print("reveived BLE packet")+
588- atcData_str = data_str [offset :offset + 26 ]
608+ atcData_str = data_str [offset :offset + 26 ] #if shorter will just be shorter then 13 Bytes
609+ atcData_str = data_str [offset :] #if shorter will just be shorter then 13 Bytes
610+ customFormat_str = data_str [offset :offset + 29 ]
589611 ATCPaketMAC = atcData_str [0 :12 ].upper ()
590612 macStr = mac .replace (":" ,"" ).upper ()
591613 atcIdentifier = data_str [(offset - 4 ):offset ].upper ()
592614
593- if (atcIdentifier == "1A18" and ATCPaketMAC == macStr ) and not args .onlydevicelist or (atcIdentifier == "1A18" and mac in sensors ) and len (atcData_str ) == 26 : #only Data from ATC devices, double checked
594- advNumber = atcData_str [- 2 :]
615+ # if (atcIdentifier == "1A18" ) and mac == "A4:C1:38:92:E3:BD" : #debug
616+ # print("BLE packet: %s %02x %s %d" % (mac, adv_type, data_str, rssi))
617+ # print("raw:",data_str)
618+
619+
620+ batteryVoltage = None
621+ if (atcIdentifier == "1A18" ) and not args .onlydevicelist or (atcIdentifier == "1A18" and mac in sensors ) and (len (atcData_str ) == 26 or len (atcData_str ) == 16 or len (atcData_str ) == 22 ): #only Data from ATC devices
622+ global measurements
623+ measurement = Measurement (0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 )
624+ if len (atcData_str ) == 30 : #custom format, next-to-last ist adv number
625+ advNumber = atcData_str [- 4 :- 2 ]
626+ else :
627+ advNumber = atcData_str [- 2 :] #last data in paket is adv number
628+
595629 if macStr in advCounter :
596630 lastAdvNumber = advCounter [macStr ]
597631 else :
598632 lastAdvNumber = None
599633 if lastAdvNumber == None or lastAdvNumber != advNumber :
600- advCounter [macStr ] = advNumber
601- print ("BLE packet: %s %02x %s %d" % (mac , adv_type , data_str , rssi ))
602- #print("AdvNumber: ", advNumber)
603- #temp = data_str[22:26].encode('utf-8')
604- #temperature = int.from_bytes(bytearray.fromhex(data_str[22:26]),byteorder='big') / 10.
605- global measurements
606- measurement = Measurement (0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 )
607- if args .influxdb == 1 :
608- measurement .timestamp = int ((time .time () // 10 ) * 10 )
609- else :
610- measurement .timestamp = int (time .time ())
611634
635+ if len (atcData_str ) == 26 : #ATC1441 Format
636+ #print("atc14441") #debug
637+ advCounter [macStr ] = advNumber
638+ print ("BLE packet: %s %02x %s %d" % (mac , adv_type , data_str , rssi ))
639+ #print("AdvNumber: ", advNumber)
640+ #temp = data_str[22:26].encode('utf-8')
641+ #temperature = int.from_bytes(bytearray.fromhex(data_str[22:26]),byteorder='big') / 10.
642+ #temperature = int(data_str[22:26],16) / 10.
643+ temperature = int .from_bytes (bytearray .fromhex (atcData_str [12 :16 ]),byteorder = 'big' ,signed = True ) / 10.
644+ # print("Temperature: ", temperature)
645+ humidity = int (atcData_str [16 :18 ], 16 )
646+ # print("Humidity: ", humidity)
647+ batteryVoltage = int (atcData_str [20 :24 ], 16 ) / 1000
648+ # print ("Battery voltage:", batteryVoltage,"V")
649+ # print ("RSSI:", rssi, "dBm")
650+
651+ #if args.battery:
652+ batteryPercent = int (atcData_str [18 :20 ], 16 )
653+ #print ("Battery:", batteryPercent,"%")
654+
655+ elif len (atcData_str ) == 30 : #custom format
656+ #print("custom:", atcData_str)
657+ print ("BLE packet: %s %02x %s %d" % (mac , adv_type , data_str , rssi ))
658+ temperature = int .from_bytes (bytearray .fromhex (atcData_str [12 :16 ]),byteorder = 'little' ,signed = True ) / 100.
659+ humidity = int .from_bytes (bytearray .fromhex (atcData_str [16 :20 ]),byteorder = 'little' ,signed = False ) / 100.
660+ batteryVoltage = int .from_bytes (bytearray .fromhex (atcData_str [20 :24 ]),byteorder = 'little' ,signed = False ) / 1000.
661+ batteryPercent = int .from_bytes (bytearray .fromhex (atcData_str [24 :26 ]),byteorder = 'little' ,signed = False )
662+
663+
664+
665+ elif len (atcData_str ) == 22 or len (atcData_str ) == 16 : #encrypted: length 22/11 Bytes on custom format, 16/8 Bytes on ATC1441 Format
666+ #print("enc") # debug
667+ #if macStr in encryptedPacketStore:
668+ if macStr in advCounter :
669+ lastData = advCounter [macStr ]
670+ else :
671+ lastData = None
672+
673+ if lastData == None or lastData != atcData_str :
674+ print ("Encrypted BLE packet: %s %02x %s %d, length: %d" % (mac , adv_type , data_str , rssi , len (atcData_str )/ 2 ))
675+ if mac in sensors and "key" in sensors [mac ]:
676+ bindkey = bytes .fromhex (sensors [mac ]["key" ])
677+ macReversed = ""
678+ for x in range (- 1 ,- len (macStr ),- 2 ):
679+ macReversed += macStr [x - 1 ] + macStr [x ]
680+ macReversed = bytes .fromhex (macReversed .lower ())
681+ #print("New encrypted format, MAC:" , macStr, "Reversed: ", macReversed)
682+ lengthHex = data_str [offset - 8 :offset - 6 ]
683+ #lengthHex="0b"
684+ ret = cryptoFunctions .decrypt_aes_ccm (bindkey ,macReversed ,bytes .fromhex (lengthHex + "161a18" + atcData_str ))
685+ if ret == None : #Error decrypting
686+ print ("\n " )
687+ return
688+ #temperature, humidity, batteryPercent = cryptoFunctions.decrypt_aes_ccm(bindkey,macReversed,bytes.fromhex(lengthHex + "161a18" + atcData_str))
689+ temperature , humidity , batteryPercent = ret
690+ else :
691+ print ("Warning: No key provided for sensor:" , mac ,"\n " )
692+ return
693+ else : #no fitting paket
694+ return
695+
696+ else : #Packet is just repeated
697+ return
698+
699+ if args .influxdb == 1 :
700+ measurement .timestamp = int ((time .time () // 10 ) * 10 )
701+ else :
702+ measurement .timestamp = int (time .time ())
612703
613- #temperature = int(data_str[22:26],16) / 10.
614- temperature = int .from_bytes (bytearray .fromhex (atcData_str [12 :16 ]),byteorder = 'big' ,signed = True ) / 10.
615- print ("Temperature: " , temperature )
616- humidity = int (atcData_str [16 :18 ], 16 )
617- print ("Humidity: " , humidity )
618- batteryVoltage = int (atcData_str [20 :24 ], 16 ) / 1000
704+ if args .round :
705+ temperature = round (temperature ,1 )
706+ humidity = round (humidity ,1 )
707+
708+ measurement .battery = batteryPercent
709+ measurement .humidity = humidity
710+ measurement .temperature = temperature
711+ measurement .voltage = batteryVoltage if batteryVoltage != None else 0
712+ measurement .rssi = rssi
713+
714+ print ("Temperature: " , temperature )
715+ print ("Humidity: " , humidity )
716+ if batteryVoltage != None :
619717 print ("Battery voltage:" , batteryVoltage ,"V" )
620- print ("RSSI:" , rssi , "dBm" )
621-
622- #if args.battery:
623- batteryPercent = int (atcData_str [18 :20 ], 16 )
624- print ("Battery:" , batteryPercent ,"%" )
625- measurement .battery = batteryPercent
626- measurement .humidity = humidity
627- measurement .temperature = temperature
628- measurement .voltage = batteryVoltage
629- measurement .rssi = rssi
630-
631- currentMQTTTopic = MQTTTopic
632- if mac in sensors :
633- try :
634- measurement .sensorname = sensors [mac ]["sensorname" ]
635- except :
636- measurement .sensorname = mac
637- if "offset1" in sensors [mac ] and "offset2" in sensors [mac ] and "calpoint1" in sensors [mac ] and "calpoint2" in sensors [mac ]:
638- measurement .humidity = calibrateHumidity2Points (humidity ,int (sensors [mac ]["offset1" ]),int (sensors [mac ]["offset2" ]),int (sensors [mac ]["calpoint1" ]),int (sensors [mac ]["calpoint2" ]))
639- print ("Humidity calibrated (2 points calibration): " , measurement .humidity )
640- elif "humidityOffset" in sensors [mac ]:
641- measurement .humidity = humidity + int (sensors [mac ]["humidityOffset" ])
642- print ("Humidity calibrated (offset calibration): " , measurement .humidity )
643- if "topic" in sensors [mac ]:
644- currentMQTTTopic = sensors [mac ]["topic" ]
645- else :
718+ print ("RSSI:" , rssi , "dBm" )
719+ print ("Battery:" , batteryPercent ,"%" )
720+
721+ currentMQTTTopic = MQTTTopic
722+ if mac in sensors :
723+ try :
724+ measurement .sensorname = sensors [mac ]["sensorname" ]
725+ except :
646726 measurement .sensorname = mac
647-
648- if measurement .calibratedHumidity == 0 :
649- measurement .calibratedHumidity = measurement .humidity
727+ if "offset1" in sensors [mac ] and "offset2" in sensors [mac ] and "calpoint1" in sensors [mac ] and "calpoint2" in sensors [mac ]:
728+ measurement .humidity = calibrateHumidity2Points (humidity ,int (sensors [mac ]["offset1" ]),int (sensors [mac ]["offset2" ]),int (sensors [mac ]["calpoint1" ]),int (sensors [mac ]["calpoint2" ]))
729+ print ("Humidity calibrated (2 points calibration): " , measurement .humidity )
730+ elif "humidityOffset" in sensors [mac ]:
731+ measurement .humidity = humidity + int (sensors [mac ]["humidityOffset" ])
732+ print ("Humidity calibrated (offset calibration): " , measurement .humidity )
733+ if "topic" in sensors [mac ]:
734+ currentMQTTTopic = sensors [mac ]["topic" ]
735+ else :
736+ measurement .sensorname = mac
737+
738+ if measurement .calibratedHumidity == 0 :
739+ measurement .calibratedHumidity = measurement .humidity
650740
651- if args .callback or args .httpcallback :
652- measurements .append (measurement )
741+ if args .callback or args .httpcallback :
742+ measurements .append (measurement )
653743
654- if args .mqttconfigfile :
655- jsonString = buildJSONString (measurement )
656- myMQTTPublish (currentMQTTTopic ,jsonString )
657- #MQTTClient.publish(currentMQTTTopic,jsonString,1)
744+ if args .mqttconfigfile :
745+ jsonString = buildJSONString (measurement )
746+ myMQTTPublish (currentMQTTTopic ,jsonString )
747+ #MQTTClient.publish(currentMQTTTopic,jsonString,1)
658748
659- #print("Length:", len(measurements))
660- print ("" )
749+ #print("Length:", len(measurements))
750+ print ("" )
661751
662752 if args .watchdogtimer :
663753 keepingLEScanRunningThread = threading .Thread (target = keepingLEScanRunning )
0 commit comments