diff --git a/unlock_daemon.py b/unlock_daemon.py index f259f2f..31a6a7c 100755 --- a/unlock_daemon.py +++ b/unlock_daemon.py @@ -368,8 +368,19 @@ def main(): print_startup_info() log.info("Waiting for Apple Watch...") - mac = rssi = None - + # btmon (BlueZ 5.x) does not print raw manufacturer hex; it decodes Apple + # data across separate lines and never emits the "4c 00 10 05" byte string: + # Company: Apple, Inc. (76) + # Type: Unknown (16) <- 16 == 0x10 == Nearby Info + # Data: <- payload[1] carries the auto-unlock flags + # RSSI is the last field of each advertising report, so a detection is held + # as pending when its Data line is parsed and fired when the report's RSSI + # line arrives, giving the correct RSSI for that report. + mac = None + apple = False + nearby = False + pending_flags = None + try: while True: ready, _, _ = select.select([sys.stdin], [], [], 1.0) @@ -378,38 +389,50 @@ def main(): if not line: break - # Extract MAC + # New advertising report: capture address, reset per-report state m = re.search(r'Address:\s*([0-9A-Fa-f:]{17})', line) if m: mac = m.group(1) - - # Extract RSSI + apple = False + nearby = False + pending_flags = None + + # Track Apple Nearby Info (Type 16) block and capture its payload + if 'Company: Apple, Inc. (76)' in line: + apple = True + nearby = False + elif apple and re.search(r'Type:.*\(16\)', line): + nearby = True + else: + d = re.search(r'Data:\s*([0-9a-fA-F]+)', line) + if d and nearby: + payload = bytes.fromhex(d.group(1)) + nearby = False + if len(payload) >= 2: + pending_flags = payload[1] + + # RSSI ends the report: fire the detection with the correct RSSI r = re.search(r'RSSI:\s*(-?\d+)', line) if r: rssi = int(r.group(1)) - - # Parse Nearby Info - hx = re.search(r'4c 00 10 (0[56]) ([0-9a-f]{2}) ([0-9a-f]{2})', line.lower()) - if hx and mac and rssi is not None: - status_flags = int(hx.group(3), 16) - - if is_my_watch(mac): + if pending_flags is not None and mac and is_my_watch(mac): + status_flags = pending_flags + has_au_on = bool(status_flags & FLAG_AUTO_UNLOCK_ON) has_locked = bool(status_flags & FLAG_WATCH_LOCKED) - + if has_au_on and not has_locked: status = "on-wrist/unlocked" elif has_locked: status = "on-wrist/locked" else: status = "off-wrist" - + log.debug(f"[DETECT] Watch {mac} | RSSI:{rssi} | {status} | flags:0x{status_flags:02X}") - + handle_watch_detection(rssi, status_flags) - - mac = rssi = None - + pending_flags = None + # Periodic presence check check_presence_timeout()