1
1
#!/usr/bin/python3 -u
2
- #!/home/openhabian/Python3/Python-3.7.4/python -u
3
2
#-u to unbuffer output. Otherwise when calling with nohup or redirecting output things are printed very lately or would even mixup
4
3
5
4
print ("---------------------------------------------" )
6
- print ("MiTemperature2 / ATC Thermometer version 3.1 " )
5
+ print ("MiTemperature2 / ATC Thermometer version 4.0 " )
7
6
print ("---------------------------------------------" )
8
7
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
+
9
18
from bluepy import btle
10
19
import argparse
11
20
import os
22
31
import requests
23
32
import ssl
24
33
34
+
25
35
@dataclass
26
36
class Measurement :
27
37
temperature : float
@@ -309,7 +319,7 @@ def MQTTOnDisconnect(client, userdata,rc):
309
319
print ("MQTT disconnected, Client:" , client , "Userdata:" , userdata , "RC:" , rc )
310
320
311
321
# Main loop --------
312
- parser = argparse .ArgumentParser (allow_abbrev = False )
322
+ parser = argparse .ArgumentParser (allow_abbrev = False , epilog = readme )
313
323
parser .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' )
314
324
parser .add_argument ("--battery" ,"-b" , help = "Get estimated battery level, in ATC-Mode: Get battery level from device" , metavar = '' , type = int , nargs = '?' , const = 1 )
315
325
parser .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):
319
329
320
330
321
331
rounding = 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' )
323
333
rounding .add_argument ("--debounce" ,"-deb" , help = "Enable this option to get more stable temperature values, requires -r option" ,action = 'store_true' )
324
334
325
335
offsetgroup = parser .add_argument_group ("Offset calibration mode" )
@@ -368,12 +378,12 @@ def MQTTOnDisconnect(client, userdata,rc):
368
378
port = int (mqttConfig ["MQTT" ]["port" ])
369
379
370
380
# 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" ])
377
387
username = mqttConfig ["MQTT" ]["username" ]
378
388
password = mqttConfig ["MQTT" ]["password" ]
379
389
MQTTTopic = mqttConfig ["MQTT" ]["topic" ]
@@ -402,7 +412,6 @@ def MQTTOnDisconnect(client, userdata,rc):
402
412
if len (lwt ) > 0 :
403
413
print ("Using lastwill with topic:" ,lwt ,"and message:" ,lastwill )
404
414
client .will_set (lwt ,lastwill ,qos = 1 )
405
-
406
415
# MQTTS parameters
407
416
if tls :
408
417
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):
411
420
client .connect_async (broker ,port )
412
421
MQTTClient = client
413
422
423
+
414
424
if args .device :
415
425
if re .match ("[0-9a-fA-F]{2}([:]?)[0-9a-fA-F]{2}(\\ 1[0-9a-fA-F]{2}){4}$" ,args .device ):
416
426
adress = args .device
@@ -530,19 +540,21 @@ def MQTTOnDisconnect(client, userdata,rc):
530
540
print ("----------------------------" )
531
541
print ("In this mode all devices within reach are read out, unless a devicelistfile and --onlydevicelist is specified." )
532
542
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." )
534
544
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`)\" " )
535
545
print ("You have to redo this step if you upgrade your python version." )
536
546
print ("----------------------------" )
537
547
538
548
import sys
539
549
import bluetooth ._bluetooth as bluez
550
+ import cryptoFunctions
540
551
541
552
from bluetooth_utils import (toggle_device ,
542
553
enable_le_scan , parse_le_advertising_events ,
543
554
disable_le_scan , raw_packet_to_str )
544
555
545
- advCounter = dict ()
556
+ advCounter = dict ()
557
+ #encryptedPacketStore=dict()
546
558
sensors = dict ()
547
559
if args .devicelistfile :
548
560
#import configparser
@@ -557,6 +569,15 @@ def MQTTOnDisconnect(client, userdata,rc):
557
569
sensorsnew [key .upper ()] = sensors [key ]
558
570
sensors = sensorsnew
559
571
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
+
560
581
if args .onlydevicelist and not args .devicelistfile :
561
582
print ("Error: --onlydevicelist requires --devicelistfile <devicelistfile>" )
562
583
os ._exit (1 )
@@ -581,83 +602,152 @@ def le_advertise_packet_handler(mac, adv_type, data, rssi):
581
602
lastBLEPaketReceived = time .time ()
582
603
lastBLEPaketReceived = time .time ()
583
604
data_str = raw_packet_to_str (data )
584
- preeamble = "10161a18 "
605
+ preeamble = "161a18 "
585
606
paketStart = data_str .find (preeamble )
586
607
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 ]
589
611
ATCPaketMAC = atcData_str [0 :12 ].upper ()
590
612
macStr = mac .replace (":" ,"" ).upper ()
591
613
atcIdentifier = data_str [(offset - 4 ):offset ].upper ()
592
614
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
+
595
629
if macStr in advCounter :
596
630
lastAdvNumber = advCounter [macStr ]
597
631
else :
598
632
lastAdvNumber = None
599
633
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 ())
611
634
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 ())
612
703
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 :
619
717
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 :
646
726
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
650
740
651
- if args .callback or args .httpcallback :
652
- measurements .append (measurement )
741
+ if args .callback or args .httpcallback :
742
+ measurements .append (measurement )
653
743
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)
658
748
659
- #print("Length:", len(measurements))
660
- print ("" )
749
+ #print("Length:", len(measurements))
750
+ print ("" )
661
751
662
752
if args .watchdogtimer :
663
753
keepingLEScanRunningThread = threading .Thread (target = keepingLEScanRunning )
0 commit comments