From 0f9d6f6aab74e9cc3d6b05ab79f1bf6ce60fdd43 Mon Sep 17 00:00:00 2001 From: Kirill Snezhko <4477094+argrento@users.noreply.github.com> Date: Wed, 24 Feb 2021 02:08:48 +0300 Subject: [PATCH] Download firmware and font pack (experimental, unstable, dangerous), add ID and ACT columns. Fix #12 --- .gitignore | 2 ++ README.md | 35 ++++++++++++------ huami_token.py | 96 +++++++++++++++++++++++++++++++++++++++++++------- urls.py | 11 ++++++ 4 files changed, 121 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 15cd0bd..7ed7081 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ notebooks *.zip __pycache__ *.dump +*.fw +*.bin diff --git a/README.md b/README.md index 2550aae..c7f868c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ optional arguments: Account Password -b, --bt_keys Get bluetooth tokens of paired devices -g, --gps Download A-GPS files + -f, --firmware Request firmware updates. Works only with -b/--bt_keys + argument. Extremely dangerous -a, --all Do everything: get bluetooth tokens, download A-GPS files -n, --no_logout Do not logout, keep active session and display app @@ -60,11 +62,11 @@ Token: ['UaFHW53RJVYwqXaa7ncPQ'] Logging in... Logged in! User id: 1234567890 Getting linked wearables... -+----------------------------------------------------------------+ -| ACT | MAC | auth_key | -|-----+-------------------+--------------------------------------| -| 1 | AB:CD:EF:12:34:56 | 0xa3c10e34e5c14637eea6b9efc06106 | -+----------------------------------------------------------------+ ++----------------------------------------------------------------------+ +| ID | ACT | MAC | auth_key | +|-----+-----+-------------------+--------------------------------------| +| 0 | 1 | AB:CD:EF:12:34:56 | 0xa3c10e34e5c14637eea6b9efc06106 | ++----------------------------------------------------------------------+ Logged out. ``` @@ -99,12 +101,12 @@ Token: ['ALSG_CLOUDSRV_9B8D87D0EB77C71B45FF73B2266D922B'] Logging in... Logged in! User id: 3000654321 Getting linked wearables... -+----------------------------------------------------------------+ -| ACT | MAC | auth_key | -|-----+-------------------+--------------------------------------| -| 1 | 12:34:56:AB:CD:EF | 0x3c10e34e5c1463527579996fa83e6d | -| 0 | BA:DC:FE:21:43:65 | 0x00 | -+----------------------------------------------------------------+ ++----------------------------------------------------------------------+ +| ID | ACT | MAC | auth_key | +|-----+-----+-------------------+--------------------------------------| +| 0 | 1 | 12:34:56:AB:CD:EF | 0x3c10e34e5c1463527579996fa83e6d | +| 1 | 0 | BA:DC:FE:21:43:65 | 0x00 | ++----------------------------------------------------------------------+ Logged out. ``` @@ -115,6 +117,17 @@ active or not. In this example I have two devices: the first one is my Amazfit Bip S watch, the second one is my Xiaomi Mi Smart Scale. +## Experimental: updates download + +This is extremely dangerous: flashing the wrong version can brick your device! +I am not responsible for any of problems that might arise. + +Can be enabled with `-f/--firmware` argument. Will work only with `-b/--bt_keys` argument. +You should input the ID of a device, or `-1` to check for all. + +Script will try to find updates for the firmware and the font pack for the device from +the table above. + ## Dependencies diff --git a/huami_token.py b/huami_token.py index 9d6a526..3edab0c 100644 --- a/huami_token.py +++ b/huami_token.py @@ -12,6 +12,7 @@ import urllib import argparse import getpass import requests +import hashlib from rich.console import Console from rich.table import Table @@ -171,21 +172,60 @@ class HuamiAmazfit: key_str = device_info['auth_key'] auth_key = '0x' + (key_str if key_str != '' else '00') - if 'activeStatus' in _wearable: - active_status = str(_wearable['activeStatus']) - else: - active_status = '-1' - _wearables.append( { - 'active_status': active_status, + 'active_status': str(_wearable.get('activeStatus', '-1')), 'mac_address': mac_address, - 'auth_key': auth_key + 'auth_key': auth_key, + 'device_source': str(_wearable.get('deviceSource', 0)), + 'firmware_version': _wearable.get('firmwareVersion', 'v-1'), + 'hardware_version': device_info.get('hardwareVersion', 'v-1'), + 'production_source': device_info.get('productVersion', '0') } ) return _wearables + def get_firmware(self, _wearable: dict) -> None: + """Check and download updates for the furmware and fonts""" + fw_url = urls.URLS["fw_updates"] + params = urls.PAYLOADS["fw_updates"] + params['deviceSource'] = _wearable['device_source'] + params['firmwareVersion'] = _wearable['firmware_version'] + params['hardwareVersion'] = _wearable['hardware_version'] + params['productionSource'] = _wearable['production_source'] + headers = { + 'appplatform': 'android_phone', + 'appname': 'com.huami.midong', + 'lang': 'en_US' + } + response = requests.get(fw_url, params=params, headers=headers) + response.raise_for_status() + fw_response = response.json() + + msgs = [] + links = [] + hashes = [] + + if 'firmwareUrl' in fw_response: + msgs.append('firmware') + links.append(fw_response['firmwareUrl']) + hashes.append(fw_response['firmwareMd5']) + if 'fontUrl' in fw_response: + msgs.append('font') + links.append(fw_response['fontUrl']) + hashes.append(fw_response['fontMd5']) + + if not links: + print("No updates found!") + else: + for msg, link, hash_sum in zip(msgs, links, hashes): + file_name = link.split('/')[-1] + print(f"Downloading {file_name}...") + with requests.get(link, stream=True) as r: + with open(file_name, 'wb') as f: + shutil.copyfileobj(r.raw, f) + def get_gps_data(self) -> None: """Download GPS packs: almanac and AGPS""" agps_packs = ["AGPS_ALM", "AGPSZIP"] @@ -196,7 +236,7 @@ class HuamiAmazfit: headers['apptoken'] = self.app_token for idx, agps_pack_name in enumerate(agps_packs): - print("Downloading {}...".format(agps_pack_name)) + print(f"Downloading {agps_pack_name}...") response = requests.get(agps_link.format(pack_name=agps_pack_name), headers=headers) response.raise_for_status() agps_result = response.json()[0] @@ -252,11 +292,20 @@ if __name__ == "__main__": required=False, action='store_true', help="Download A-GPS files") + + parser.add_argument("-f", + "--firmware", + required=False, + action='store_true', + help='Request firmware updates. Works only with -b/--bt_keys argument. ' + 'Extremely dangerous!') + parser.add_argument("-a", "--all", required=False, action='store_true', - help="Do everything: get bluetooth tokens, download A-GPS files") + help="Do everything: get bluetooth tokens, download A-GPS files. But " + "do NOT download firmware updates") parser.add_argument("-n", "--no_logout", @@ -269,9 +318,13 @@ if __name__ == "__main__": console = Console() table = Table(show_header=True, header_style="bold", box=box.ASCII) + table.add_column("ID", width=3, justify='center') table.add_column("ACT", width=3, justify='center') table.add_column("MAC", style="dim", width=17, justify='center') - table.add_column("auth_key", width=50, justify='center') + table.add_column("auth_key", width=45, justify='center') + + if args.firmware and not args.bt_keys: + parser.error("Can not use -f/--firmware without -b/--bt_keys!") if args.password is None and args.method == "amazfit": args.password = getpass.getpass() @@ -282,12 +335,31 @@ if __name__ == "__main__": device.get_access_token() device.login() + wearables = [] if args.bt_keys or args.all: wearables = device.get_wearables() - for wearable in wearables: - table.add_row(wearable['active_status'], wearable['mac_address'], wearable['auth_key']) + for idx, wearable in enumerate(wearables): + table.add_row(str(idx), wearable['active_status'], + wearable['mac_address'], wearable['auth_key']) console.print(table) + if args.firmware: + print("Downloading the firmware is untested and can brick your device. " + "I am not responsible for any problems that might arise.") + answer = input("Do you want to proceed? [yes/no] ") + if answer.lower() in ['yes', 'y', 'ye']: + wearable_id = input("ID of the device to check for updates (-1 for all of them): ") + if wearable_id == "-1": + print("Be extremely careful with downloaded files!") + for idx, wearable in enumerate(wearables): + print(f"\nChecking for device {idx}...") + device.get_firmware(wearable) + elif int(wearable_id) in range(0, len(wearables)): + device.get_firmware(wearables[wearable_id]) + else: + print("Wrong input!") + + if args.gps or args.all: device.get_gps_data() diff --git a/urls.py b/urls.py index 79918b2..6e7a3ff 100644 --- a/urls.py +++ b/urls.py @@ -11,6 +11,7 @@ URLS = { 'agps': 'https://api-mifit-us2.huami.com/apps/com.huami.midong/fileTypes/{pack_name}/files', 'data_short': 'https://api-mifit-us2.huami.com/users/{user_id}/deviceTypes/4/data', 'logout': 'https://account-us2.huami.com/v1/client/logout', + 'fw_updates': 'https://api-mifit-us2.huami.com/devices/ALL/hasNewVersion' } PAYLOADS = { @@ -55,4 +56,14 @@ PAYLOADS = { 'logout': { 'login_token': None }, + 'fw_updates': { + 'productionSource': None, + 'deviceSource': None, + 'fontVersion': '0', + 'fontFlag': '0', + 'appVersion': '5.9.2-play_100355', + 'firmwareVersion': None, + 'hardwareVersion': None, + 'support8Bytes': 'true' + } }