ble_relay.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """
  2. AI-Light BLE Relay
  3. 从stdin读取JSON状态消息,通过BLE发送到AI-Light设备
  4. 用法:
  5. python ble_relay.py [选项]
  6. 选项:
  7. --device 蓝牙设备名称 (默认: AI-Light)
  8. --service-uuid 服务UUID (默认: b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001)
  9. --char-uuid 特征UUID (默认: b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001)
  10. """
  11. import sys
  12. import asyncio
  13. import json
  14. import argparse
  15. import logging
  16. from bleak import BleakClient, BleakScanner
  17. logging.basicConfig(
  18. level=logging.INFO,
  19. format="%(asctime)s [BLE] %(message)s",
  20. datefmt="%H:%M:%S"
  21. )
  22. logger = logging.getLogger(__name__)
  23. STATUS_MAP = {
  24. "idle": "idle",
  25. "busy": "busy",
  26. "retry": "thinking",
  27. "pending": "thinking",
  28. "reasoning": "thinking",
  29. "using_tool": "ai",
  30. "running": "ai",
  31. "completed": "success",
  32. "session_completed": "success",
  33. "permission": "alarm",
  34. "error": "error"
  35. }
  36. async def find_device(device_name: str):
  37. logger.info(f"Searching for {device_name}...")
  38. device = await BleakScanner.find_device_by_name(device_name, timeout=10.0)
  39. if device is None:
  40. logger.error(f"Device '{device_name}' not found")
  41. return None
  42. logger.info(f"Found: {device.name} ({device.address})")
  43. return device
  44. async def run_relay(device_name: str, service_uuid: str, char_uuid: str):
  45. device = await find_device(device_name)
  46. if not device:
  47. sys.exit(1)
  48. async with BleakClient(device) as client:
  49. logger.info(f"Connected to {device.name}")
  50. for line in sys.stdin:
  51. try:
  52. line = line.strip()
  53. if not line:
  54. continue
  55. data = json.loads(line)
  56. code = data.get("code", "idle")
  57. mode = STATUS_MAP.get(code, "idle")
  58. await client.write_gatt_char(char_uuid, mode.encode("utf-8"))
  59. logger.info(f"Sent: {mode} (from code: {code})")
  60. except json.JSONDecodeError:
  61. logger.warning(f"Invalid JSON: {line}")
  62. except Exception as e:
  63. logger.error(f"Error: {e}")
  64. # Try to reconnect
  65. logger.info("Attempting to reconnect...")
  66. device = await find_device(device_name)
  67. if not device:
  68. logger.error("Reconnection failed")
  69. break
  70. client = BleakClient(device)
  71. await client.__aenter__()
  72. logger.info("Reconnected")
  73. def main():
  74. parser = argparse.ArgumentParser(description="AI-Light BLE Relay")
  75. parser.add_argument("--device", default="AI-Light",
  76. help="BLE device name (default: AI-Light)")
  77. parser.add_argument("--service-uuid", default="b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001",
  78. help="BLE service UUID")
  79. parser.add_argument("--char-uuid", default="b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001",
  80. help="BLE characteristic UUID")
  81. args = parser.parse_args()
  82. logger.info(f"BLE Relay starting...")
  83. logger.info(f"Device: {args.device}")
  84. logger.info(f"Waiting for status messages on stdin...")
  85. try:
  86. asyncio.run(run_relay(args.device, args.service_uuid, args.char_uuid))
  87. except KeyboardInterrupt:
  88. logger.info("Stopped")
  89. except Exception as e:
  90. logger.error(f"Fatal error: {e}")
  91. sys.exit(1)
  92. if __name__ == "__main__":
  93. main()