Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 41 additions & 18 deletions unlock_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 hex> <- 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)
Expand All @@ -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()

Expand Down