diff --git a/scripts/extract_gateway3_homekey.py b/scripts/extract_gateway3_homekey.py index fbcb2ff..ff5058b 100644 --- a/scripts/extract_gateway3_homekey.py +++ b/scripts/extract_gateway3_homekey.py @@ -1,71 +1,99 @@ -import requests -import json +"""Extract PowerView homekey from a G3 PowerView Gateway""" + import base64 +import json import struct +from typing import Any, Final -HUB = "http://192.168.0.184" +import requests -def create_request(sid, cid, sequenceId, data): - data = struct.pack(" bytes: + """Assemble a request frame for the PowerView protocol.""" + return struct.pack(" dict[str, Any]: if len(packet) < 4: - raise Exception('Packet size too small') - sid, cid, sequenceId, length = struct.unpack(" bytes: + """Create a GetShadeKey request frame.""" + return create_request(251, 18, sequence_id, b"") + + +def get_shade_key(hub: str, ble_name) -> bytes: + """Get the homekey for a shade.""" + try: + shades_exec_resp: requests.Response = requests.post( + hub + "/home/shades/exec?shades=" + ble_name, + json={"hex": create_get_shade_key_request(1).hex()}, + timeout=TIMEOUT, + ) + shades_exec_resp.raise_for_status() + except requests.exceptions.RequestException as ex: + print(f"Unable to send GetShadeKey {ex!s}") + raise + + result: dict = json.loads(shades_exec_resp.content) + if result.get("err") != 0 or len(result.get("responses", [])) != 1: + raise IOError("Error when attempting GetShadeKey") + response: Final[bytes] = bytes.fromhex(result["responses"][0]["hex"]) + dec_resp: Final[dict[str, Any]] = decode_response(response) + if dec_resp["errorCode"] != 0: + raise ValueError("BLE errorCode is not 0") + if len(dec_resp["data"]) != 16: + raise ValueError("Expected 16 byte homekey") + return dec_resp["data"] + + +def main(hub: str) -> None: + """Main function, extract homekeys from all shades.""" + try: + shades_resp: requests.Response = requests.get( + hub + "/home/shades", timeout=TIMEOUT + ) + shades_resp.raise_for_status() + except requests.exceptions.RequestException as ex: + print(f"Unable to get list of shades:\n\t{ex!s}") + return + shades = json.loads(shades_resp.content) print(f"Found {len(shades)} shades, interrogating") for shade in shades: - name = base64.b64decode(shade['name']).decode('utf-8') + name: str = base64.b64decode(shade["name"]).decode("utf-8") + key: bytes = get_shade_key(hub, shade["bleName"]) + print(f"Shade '{name}':") print(f"\tBLE name: '{shade['bleName']}'") - - key = get_shade_key(hub, shade['bleName']) print(f"\tHomeKey: {key.hex()}") -if __name__ == '__main__': +if __name__ == "__main__": import argparse import sys - parser = argparse.ArgumentParser(description="Extract PowerView homekey from a G3 PowerView Gateway") - parser.add_argument("hub", help="URL to HUB", default="http://powerview-g3.local") + + parser = argparse.ArgumentParser( + description="Extract PowerView homekey from a G3 PowerView Gateway" + ) + parser.add_argument("hub", nargs="?", help="URL to HUB", default=HUB) args = parser.parse_args() sys.exit(main(**vars(args)))