From c89bd2adf47e8e4601ec5b390f9046cc9af96596 Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Tue, 8 Dec 2020 23:41:46 +0100 Subject: [PATCH 1/7] when no Telink device detected error out --- flash.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/flash.py b/flash.py index 44454d7e..5fb3aea4 100755 --- a/flash.py +++ b/flash.py @@ -41,9 +41,14 @@ def connect(self, mac, doTest): self._services = {} for service in services: self._services[str(service.uuid)] = service - self._service = self._services['00010203-0405-0607-0809-0a0b0c0d1912'] - self._writeCharacteristic = self._service.getCharacteristics(forUUID='00010203-0405-0607-0809-0a0b0c0d2b12')[0] - self._detectMi() + if '00010203-0405-0607-0809-0a0b0c0d1912' in self._services: + self._service = self._services['00010203-0405-0607-0809-0a0b0c0d1912'] + self._writeCharacteristic = self._service.getCharacteristics(forUUID='00010203-0405-0607-0809-0a0b0c0d2b12')[0] + self._detectMi() + else: + print("No Telink device detected.") + self.disconnect() + sys.exit(-1) def _detectMi(self): self._miEnabled = "ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6" in self._services From b6ac65f6e0d50db22c5a1c660fb4ec9bddf66ff3 Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Wed, 9 Dec 2020 20:50:40 +0100 Subject: [PATCH 2/7] seperate flash from connection function --- flash.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/flash.py b/flash.py index 5fb3aea4..94849afe 100755 --- a/flash.py +++ b/flash.py @@ -6,8 +6,9 @@ import struct class Flash(object): - def __init__(self): + def __init__(self, doTest=False): super().__init__() + self._doTest = doTest self._firmware = None self._device = None self._miEnabled = False @@ -31,10 +32,9 @@ def disconnect(self): if not self._doTest: self._device.disconnect() - def connect(self, mac, doTest): - self._doTest = doTest - if doTest: - self._customAction() + def connect(self, mac): + if self._doTest: + return else: self._device = bluepy.btle.Peripheral(mac) services = self._device.getServices() @@ -60,13 +60,12 @@ def _detectMi(self): sys.exit(-1) elif self._customEnabled: print("Detected device with valid custom Firmware") - self._customAction() else: print("Detected device with not valid Firmware. Can't flash it.") self.disconnect() sys.exit(-1) - def _customAction(self): + def customAction(self): self._otaCharSend(bytes([0x00, 0xff])) self._otaCharSend(bytes([0x01, 0xff])) if self._doTest: @@ -126,7 +125,7 @@ def waitForNotifications(self, timeout): if len(sys.argv) < 3: - print("Usage: flash.py MAC_ADDRESS FIRMWARE_FILE [test]") + print("Usage: flash.py MAC_ADDRESS FIRMWARE_FILE [test]") sys.exit(-1) if len(sys.argv) == 4: @@ -134,7 +133,8 @@ def waitForNotifications(self, timeout): else: doTest = False -manager = Flash() +manager = Flash(doTest) manager.loadFirmware(sys.argv[2]) -manager.connect(sys.argv[1], doTest) +manager.connect(sys.argv[1]) +manager.customAction() manager.disconnect() From a464b4cb87687ec1fb0d5183b4547b117982c4ac Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Wed, 9 Dec 2020 21:13:59 +0100 Subject: [PATCH 3/7] use argparse for options --- flash.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/flash.py b/flash.py index 94849afe..03de2702 100755 --- a/flash.py +++ b/flash.py @@ -4,6 +4,7 @@ import sys import time import struct +import argparse class Flash(object): def __init__(self, doTest=False): @@ -124,17 +125,22 @@ def waitForNotifications(self, timeout): self._device.waitForNotifications(timeout) -if len(sys.argv) < 3: - print("Usage: flash.py MAC_ADDRESS FIRMWARE_FILE [test]") - sys.exit(-1) - -if len(sys.argv) == 4: - doTest = sys.argv[3] == 'test' -else: - doTest = False - -manager = Flash(doTest) -manager.loadFirmware(sys.argv[2]) -manager.connect(sys.argv[1]) -manager.customAction() -manager.disconnect() +def main(argv): + parser = argparse.ArgumentParser(description="Telink Flasher for Mi Thermostat") + parser.add_argument("mac", help="Mi Thermostat MAC address") + parser.add_argument("firmware_name", help="firmware file") + parser.add_argument("-t", "--test", dest="doTest", help="enable test mode", action="store_true") + args = parser.parse_args() + + manager = Flash(args.doTest) + manager.loadFirmware(args.firmware_name) + manager.connect(args.mac) + manager.customAction() + manager.disconnect() + +if __name__ == "__main__": + try: + main(sys.argv[1:]) + except Exception as exc: + sys.stderr.write("%s\n" % exc) + sys.exit(1) From 702ac6b9ffc3909a423a3e58267bf40f2f1f26d6 Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Wed, 9 Dec 2020 22:32:44 +0100 Subject: [PATCH 4/7] add function to send custom settings --- flash.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/flash.py b/flash.py index 03de2702..c94022d1 100755 --- a/flash.py +++ b/flash.py @@ -43,8 +43,8 @@ def connect(self, mac): for service in services: self._services[str(service.uuid)] = service if '00010203-0405-0607-0809-0a0b0c0d1912' in self._services: - self._service = self._services['00010203-0405-0607-0809-0a0b0c0d1912'] - self._writeCharacteristic = self._service.getCharacteristics(forUUID='00010203-0405-0607-0809-0a0b0c0d2b12')[0] + service = self._services['00010203-0405-0607-0809-0a0b0c0d1912'] + self._writeCharacteristic = service.getCharacteristics(forUUID='00010203-0405-0607-0809-0a0b0c0d2b12')[0] self._detectMi() else: print("No Telink device detected.") @@ -61,6 +61,8 @@ def _detectMi(self): sys.exit(-1) elif self._customEnabled: print("Detected device with valid custom Firmware") + service = self._services['00001f10-0000-1000-8000-00805f9b34fb'] + self._settingsCharacteristics = service.getCharacteristics(forUUID='00001f1f-0000-1000-8000-00805f9b34fb')[0] else: print("Detected device with not valid Firmware. Can't flash it.") self.disconnect() @@ -119,7 +121,22 @@ def _otaCharSend(self, data): self.disconnect() sys.exit(-1) - + def sendCustomSetting(self, data): + if self._doTest: + for d in data: + d = hex(d)[2:] + if len(d) == 1: + d = '0' + d + print(d, end = "") + print() + else: + try: + self._settingsCharacteristics.write(data) + except: + print(f"Error on sending setting 0x{data.hex()}") + self.disconnect() + sys.exit(-1) + print(f"Settings 0x{data.hex()} was send successful") def waitForNotifications(self, timeout): self._device.waitForNotifications(timeout) From 5b9583ac13c6dd46e0ddae0909766599b19cedfb Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Wed, 9 Dec 2020 22:38:31 +0100 Subject: [PATCH 5/7] rename flashing function --- flash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flash.py b/flash.py index c94022d1..d9465ec1 100755 --- a/flash.py +++ b/flash.py @@ -68,7 +68,7 @@ def _detectMi(self): self.disconnect() sys.exit(-1) - def customAction(self): + def startFlashing(self): self._otaCharSend(bytes([0x00, 0xff])) self._otaCharSend(bytes([0x01, 0xff])) if self._doTest: @@ -152,7 +152,7 @@ def main(argv): manager = Flash(args.doTest) manager.loadFirmware(args.firmware_name) manager.connect(args.mac) - manager.customAction() + manager.startFlashing() manager.disconnect() if __name__ == "__main__": From ea70621a8e79047329f07c689019ee50a33ea2c4 Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Thu, 10 Dec 2020 23:00:15 +0100 Subject: [PATCH 6/7] add connection retry loop --- flash.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/flash.py b/flash.py index d9465ec1..32d1ae3b 100755 --- a/flash.py +++ b/flash.py @@ -37,7 +37,24 @@ def connect(self, mac): if self._doTest: return else: - self._device = bluepy.btle.Peripheral(mac) + conTry = 0 + maxTry = 5 + self._device = bluepy.btle.Peripheral() + print(f"Trying to connect to {mac}") + while True: + conTry += 1 + try: + self._device.connect(mac) + break + except bluepy.btle.BTLEException as ex: + if conTry <= maxTry: + print(f"{ex}, retrying: {conTry}/{maxTry}") + else: + print(f"{ex}") + self.disconnect() + sys.exit(-1) + print(f"Connected to {mac}") + services = self._device.getServices() self._services = {} for service in services: From 152b4c0bb0d974a1d26fb6e13ac7ca9539609b4e Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Thu, 10 Dec 2020 23:00:36 +0100 Subject: [PATCH 7/7] add arguments to send custom settings --- flash.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/flash.py b/flash.py index 32d1ae3b..12962509 100755 --- a/flash.py +++ b/flash.py @@ -161,17 +161,34 @@ def waitForNotifications(self, timeout): def main(argv): parser = argparse.ArgumentParser(description="Telink Flasher for Mi Thermostat") - parser.add_argument("mac", help="Mi Thermostat MAC address") - parser.add_argument("firmware_name", help="firmware file") + group = parser.add_mutually_exclusive_group(required=True) parser.add_argument("-t", "--test", dest="doTest", help="enable test mode", action="store_true") + group.add_argument("-f", "--firmware_file", help="firmware file to flash") + group.add_argument("-s", "--setting", help="custom settings strings to send") + parser.add_argument("mac", help="Mi Thermostat MAC address") args = parser.parse_args() manager = Flash(args.doTest) - manager.loadFirmware(args.firmware_name) manager.connect(args.mac) - manager.startFlashing() + + if args.firmware_file: + manager.loadFirmware(args.firmware_file) + manager.startFlashing() + if args.setting: + try: + # convert "hex" string into an integer + setting = int(args.setting, 16) + # convert the integer into a bytearray + setting = setting.to_bytes(setting.bit_length() // 8, "big") + except: + print(f"Input '{args.setting}' is not a valid hex string") + manager.disconnect() + sys.exit(-1) + manager.sendCustomSetting(setting) + manager.disconnect() + if __name__ == "__main__": try: main(sys.argv[1:])