""" 设备端 SDK 自动发现 — 监听 UDP beacon(8898),无线主控免手填 IP。 与 sdk/app/services/discovery_service.py BEACON 协议对齐。 """ from __future__ import annotations import json import socket import time from typing import Optional BEACON_PORT = 8898 BEACON_MAGIC = "WORKPHONE_SDK" def _pick_best_ip(ips: list) -> str: """优先 192.168.110.x(工作机常用网段),否则取第一个私网 IP。""" for ip in ips: if ip.startswith("192.168.110."): return ip for ip in ips: if ip.startswith(("192.168.", "10.", "172.")): return ip return ips[0] def discover_sdk_ws_base(timeout: float = 20.0) -> Optional[str]: """ 监听局域网 UDP beacon,返回 WebSocket 基础地址。 例: ws://192.168.110.251:8899/ws/device """ deadline = time.time() + timeout sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: sock.bind(("", BEACON_PORT)) except OSError: # Termux 可能无权限绑 8898,改绑任意端口并只 recv sock.bind(("", 0)) sock.settimeout(1.0) while time.time() < deadline: try: data, _addr = sock.recvfrom(4096) payload = json.loads(data.decode("utf-8")) if payload.get("magic") != BEACON_MAGIC: continue port = int(payload.get("port") or 8899) ws_path = (payload.get("ws_path") or "/ws/device").rstrip("/") ips = payload.get("ips") or [] host = _pick_best_ip(ips) if ips else "127.0.0.1" return f"ws://{host}:{port}{ws_path}" except socket.timeout: continue except Exception: continue return None