Add complete DWARFLAB protocol documentation
- Architecture overview, BLE and WebSocket protocols - All 19 modules with command IDs validated against smali source - BLE packet format with CRC-16/MODBUS pseudocode - Complete WsCmd index (~300 commands) - Connection flow with pseudocode examples - Methodology section explaining APK decompilation process
This commit is contained in:
141
03_websocket_protocol.md
Normal file
141
03_websocket_protocol.md
Normal file
@ -0,0 +1,141 @@
|
||||
# WebSocket Protocol
|
||||
|
||||
## Connection
|
||||
|
||||
After BLE authentication, the app connects via WebSocket for all telescope control.
|
||||
|
||||
```
|
||||
URL: ws://<device_ip>:9900/?client_id=<client_id>
|
||||
Port: 9900 (primary), 4409/4410 (secondary)
|
||||
Scheme: ws:// (unencrypted, NOT wss://)
|
||||
```
|
||||
|
||||
The device IP is obtained from `ResGetconfig.ip` (if already on WiFi) or `ResSta.ip` (after STA configuration).
|
||||
|
||||
The `client_id` is stored in MMKV under key `"data_default_client_id"`, or generated if not present.
|
||||
|
||||
Default device IP: `192.168.88.1`
|
||||
|
||||
## WsPacket Format
|
||||
|
||||
Every WebSocket message is a serialized `BaseProto.WsPacket` protobuf:
|
||||
|
||||
```protobuf
|
||||
message WsPacket {
|
||||
int32 majorVersion = 1; // Always 2
|
||||
int32 minorVersion = 2; // Always 3
|
||||
int32 deviceId = 3; // Target device ID (see DeviceType)
|
||||
int32 moduleId = 4; // Module ID (0-18, see module table)
|
||||
int32 cmd = 5; // Command ID (see WsCmd index)
|
||||
int32 type = 6; // Message type: 0=request, 1=response, 2=notification, 3=reply
|
||||
bytes data = 7; // Protobuf-encoded payload for the specific module
|
||||
string clientId = 8; // App client identifier
|
||||
}
|
||||
```
|
||||
|
||||
### Pseudocode: Sending a Command
|
||||
|
||||
```python
|
||||
import protobuf
|
||||
|
||||
def send_command(websocket, device_id, module_id, cmd, payload_bytes, client_id):
|
||||
"""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
|
||||
)
|
||||
websocket.send(packet.SerializeToString())
|
||||
|
||||
def send_track_start(websocket, device_id, client_id, x, y, w, h, cam_id):
|
||||
"""Example: Start tracking."""
|
||||
track_req = TrackProto.ReqStartTrack(x=x, y=y, w=w, h=h, camId=cam_id)
|
||||
send_command(websocket, device_id, module_id=7, cmd=14800,
|
||||
payload_bytes=track_req.SerializeToString(), client_id=client_id)
|
||||
```
|
||||
|
||||
## Message Types
|
||||
|
||||
| type | Name | Description |
|
||||
|------|------|-------------|
|
||||
| 0 | request | App → Device command |
|
||||
| 1 | response | Device → App response to a request |
|
||||
| 2 | notification | Device → App spontaneous notification |
|
||||
| 3 | reply | Device → App acknowledgement |
|
||||
|
||||
## Keepalive: DwarfPing / DwarfEcho
|
||||
|
||||
After WebSocket connection, the app periodically sends `DwarfPing` and the device responds with `DwarfEcho`.
|
||||
|
||||
```protobuf
|
||||
message DwarfPing {
|
||||
int32 vocaltype = 1; // VT_PING = 1
|
||||
int64 timestamp = 2; // Unix timestamp
|
||||
bytes magic = 3; // Session magic bytes
|
||||
repeated DwarfPing vocals = 4;
|
||||
repeated NifAP mutes = 5;
|
||||
}
|
||||
|
||||
message DwarfEcho {
|
||||
int32 vocaltype = 1; // VT_ECHO = 2
|
||||
int64 timestamp = 2; // Unix timestamp
|
||||
bytes magic = 3; // Session magic bytes
|
||||
int64 tsPing = 4; // Echo of the ping timestamp
|
||||
bytes macAddress = 5; // Device MAC address
|
||||
BleProto model = 6; // Device model info
|
||||
string sn = 7; // Serial number
|
||||
string name = 8; // Device name
|
||||
string psw = 9; // Password
|
||||
string fwVersion = 10; // Firmware version
|
||||
string wsScheme = 11; // WebSocket scheme ("ws")
|
||||
int32 session = 12; // Session ID
|
||||
BleProto ap = 13; // AP info
|
||||
BleProto sta = 14; // STA info
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: DwarfEcho contains complete device info (firmware, MAC, SSID, IP, etc.).
|
||||
|
||||
## Response Routing
|
||||
|
||||
The client uses `WsRequestHandle` to dispatch responses:
|
||||
|
||||
1. Parse `WsPacket` from received bytes
|
||||
2. Extract `cmd` (command ID)
|
||||
3. Look up registered handlers in a `HashMap<Integer, List>` for this cmd
|
||||
4. Execute callbacks with `WsPacket.getData()` (deserialized payload)
|
||||
|
||||
## Typical Exchange
|
||||
|
||||
```
|
||||
App Device
|
||||
│ │
|
||||
│ WsPacket(type=0, moduleId=1, │
|
||||
│ cmd=10000, data=ReqOpenCamera) │
|
||||
│ ─────────────────────────────────────► │
|
||||
│ │
|
||||
│ WsPacket(type=1, cmd=10000, │
|
||||
│ data=ComResponse{code=0}) │
|
||||
│ ◄───────────────────────────────────── │
|
||||
│ │
|
||||
│ WsPacket(type=0, moduleId=7, │
|
||||
│ cmd=14800, data=ReqStartTrack) │
|
||||
│ ─────────────────────────────────────► │
|
||||
│ │
|
||||
│ WsPacket(type=2, cmd=15200, │
|
||||
│ data=NotifyProto{...}) │
|
||||
│ ◄───────────────────────────────────── │
|
||||
│ (spontaneous notification) │
|
||||
```
|
||||
|
||||
## Protocol Version
|
||||
|
||||
| Field | Value | Description |
|
||||
|-------|-------|-------------|
|
||||
| `WS_MAJOR_VERSION_NUMBER` | 2 | Current major version |
|
||||
| `WS_MINOR_VERSION_NUMBER` | 3 | Current minor version |
|
||||
Reference in New Issue
Block a user