# Connection Flow ## Complete Connection Sequence ``` Step 1: BLE Discovery Step 4: WebSocket Step 6: Commands ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ SCAN for │ │ Connect to │ │ Send WsPacket│ │ "DWARF*" │ │ ws://IP:9900 │ │ (moduleId+cmd)│ │ devices │ │ ?client_id= │ │ │ │ via BleManager│ │ │ │ Receive │ └──────┬───────┘ └──────┬───────┘ │ WsPacket │ │ │ │ (response/ │ Step 2: GATT Connect Step 5: Keepalive │ notification)│ ┌──────────────┐ ┌──────────────┐ └──────────────┘ │ Connect to │ │ DwarfPing → │ │ Service DAF2 │ │ ← DwarfEcho │ │ (UUID DAF2) │ │ (periodic) │ └──────┬───────┘ └──────────────┘ │ Step 3: Auth ┌──────────────┐ │ Write │ │ ReqGetconfig │ │ on DAF3 │ │ → Receive │ │ ResGetconfig │ │ on DAF4 │ └──────────────┘ ``` ## Step 1-3: BLE Phase ### Pseudocode: BLE Authentication ```python # 1. Scan for DWARF devices devices = ble_scan(filter_name="DWARF*") # 2. Connect to GATT service gatt = ble_connect(device, service_uuid="0000DAF2-0000-1000-8000-00805f9b34fb") # 3. Authenticate auth_request = BleProto.ReqGetconfig( cmd=1, # GetWiFiConfig blePsd="DWARF_12345678", # default password clientId="my-app-client-id" ) ble_packet = build_ble_packet(cmd=1, data=auth_request.SerializeToString()) ble_write(characteristic=DAF3, data=ble_packet) # 4. Receive response response_data = ble_notify(characteristic=DAF4) response_packet = parse_ble_packet(response_data) config = BleProto.ResGetconfig() config.ParseFromString(response_packet.dataPayload) device_ip = config.ip # e.g., "192.168.88.1" ``` ### Pseudocode: WiFi Configuration (STA mode) ```python # Configure device to connect to local WiFi sta_request = BleProto.ReqSta( cmd=3, # SetSTAMode autoStart=1, blePsd="DWARF_12345678", ssid="MyHomeWiFi", psd="home_password", clientId="my-app-client-id" ) ble_packet = build_ble_packet(cmd=3, data=sta_request.SerializeToString()) ble_write(characteristic=DAF5, data=ble_packet) # Receive STA response sta_data = ble_notify(characteristic=DAF6) sta_packet = parse_ble_packet(sta_data) sta_response = BleProto.ResSta() sta_response.ParseFromString(sta_packet.dataPayload) device_ip = sta_response.ip # IP for WebSocket ``` ## Step 4: WebSocket Phase ### Pseudocode: WebSocket Connection ```python import websocket import protobuf # Connect to device WebSocket ws = websocket.connect(f"ws://{device_ip}:9900/?client_id={client_id}") # Start keepalive thread def keepalive(): while connected: ping = BleProto.DwarfPing( vocaltype=1, # VT_PING timestamp=current_time_ms(), magic=session_magic_bytes ) packet = WsPacket( majorVersion=2, minorVersion=3, deviceId=device_id, moduleId=0, # NONE cmd=0, type=0, # request data=ping.SerializeToString(), clientId=client_id ) ws.send(packet.SerializeToString()) sleep(5) # 5-second interval ``` ### Pseudocode: Send Command ```python def send_command(ws, device_id, module_id, cmd, payload_bytes): """Send a command via WebSocket.""" packet = WsPacket( majorVersion=2, minorVersion=3, deviceId=device_id, moduleId=module_id, cmd=cmd, type=0, # request data=payload_bytes, clientId=client_id ) ws.send(packet.SerializeToString()) # Example: Start tracking track_req = TrackProto.ReqStartTrack(x=500, y=300, w=100, h=100, camId=0) send_command(ws, device_id=4, module_id=7, cmd=14800, payload_bytes=track_req.SerializeToString()) # Example: Open tele camera camera_req = CameraProto.ReqOpenCamera(binning=False, rtspEncodeType=0) send_command(ws, device_id=4, module_id=1, cmd=10000, payload_bytes=camera_req.SerializeToString()) ``` ## BLE Packet Binary Format ``` Byte Field Size Description ---- ----------------- ---- ----------- 0 magic 1 0xAA (constant) 1 protocolVersion 1 Protocol version (typically 1) 2 cmdInstruction 1 BLE command (0-7) 3 packageSequence 1 Current packet number (0-based) 4 totalPackages 1 Total packets for this message 5-6 extendedProtocol 2 Extended protocol (big-endian, usually 0) 7-8 validDataLength 2 Length of protobuf payload (big-endian) 9..N dataPayload N Protobuf-encoded BleProto message N+1..N+2 crc 2 CRC-16/MODBUS (poly=0x8005, init=0xFFFF, reflected) N+3 endMarker 1 0x0D (constant) Total = validDataLength + 12 bytes ``` ### CRC-16/MODBUS Parameters - Polynomial: 0x8005 (reflected: 0xA001) - Initial value: 0xFFFF - Reflect input: Yes - Reflect output: Yes - XOR out: 0x0000 - Computed over: header (9 bytes) + payload (N bytes), **excluding** CRC and end marker ## WsPacket Binary Format ``` Field Proto# Type Description -------------- ------ ---- ----------- majorVersion 1 int32 Always 2 minorVersion 2 int32 Always 3 deviceId 3 int32 Target device (0-6, see DeviceType) moduleId 4 int32 Module (0-18, see module table) cmd 5 int32 Command ID (see WsCmd index) type 6 int32 0=request, 1=response, 2=notification, 3=reply data 7 bytes Protobuf-encoded module-specific payload clientId 8 string App client identifier ``` Serialized using standard protobuf proto3 encoding. ## Device Models | Model | deviceId | Codename | Max Name | Special | |-------|----------|----------|----------|---------| | Unknown | 0 | — | — | — | | DWARF 2 | 1 | — | 21 | — | | DWARF 3 | 2 | — | 19 | — | | *(gap)* | 3 | — | — | *unused* | | DWARF mini | 4 | Bilbo | 15 | No Panorama Create, `indexBilbo.html` | | DWARF 4 | 5 | — | 19 | — | | DWARF DRAGON | 6 | — | — | — | ## Ports & Endpoints | Service | URL/Port | Note | |---------|----------|------| | WebSocket | `ws://:9900/?client_id=` | Primary control channel | | WebSocket (alt) | Ports 4409 (0x1139), 4410 (0x113A) | Secondary ports | | Firebase | `dwarflab-a7062.appspot.com` | Cloud backend | | S3 | `dwarf-bucket-02-1251529954` | OTA storage |