Complete reverse-engineering of the DWARFLAB Android app (v3.4.0) protocol
and implementation of a working CLI tool to control DWARF II telescopes.
Analysis (from APK decompilation with jadx):
- Extracted 17 protobuf definitions (382 messages) from embedded descriptors
- Mapped all 323 WebSocket command IDs across 16 modules
- Documented the full protocol: BLE discovery, WebSocket control (port 9900),
RTSP preview, WsPacket envelope (proto v2.3)
- Documented the Android UI structure (screens, navigation, shooting modes)
- Key discovery: telescope responds with type=3 (reply), not type=1 (response),
and several commands are fire-and-forget (RGB, camera open/close)
dwarfctl Go client:
- Protobuf bindings generated from extracted .proto files (397 messages)
- WebSocket transport layer with request-response matching and notification fan-out
- Typed API covering cameras, motors, astrophotography, focus, tracking, system, power
- Cobra CLI with 30+ subcommands and --debug traffic logging
- 57 unit tests (transport round-trip, command routing, proto encoding)
- Validated on real hardware: state, photo, motor slew (all directions/speeds),
focus, RGB, time/location sync all confirmed working
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
158 lines
3.3 KiB
Protocol Buffer
158 lines
3.3 KiB
Protocol Buffer
// source: shooting_schedule.proto
|
|
syntax = "proto3";
|
|
|
|
enum ShootingScheduleState {
|
|
SHOOTING_SCHEDULE_STATE_INITIALIZED = 0;
|
|
SHOOTING_SCHEDULE_STATE_PENDING_SHOOT = 1;
|
|
SHOOTING_SCHEDULE_STATE_SHOOTING = 2;
|
|
SHOOTING_SCHEDULE_STATE_COMPLETED = 3;
|
|
SHOOTING_SCHEDULE_STATE_EXPIRED = 4;
|
|
}
|
|
|
|
enum ShootingScheduleSyncState {
|
|
SHOOTING_SCHEDULE_SYNC_STATE_PENDING_SYNC = 0;
|
|
SHOOTING_SCHEDULE_SYNC_STATE_SYNCED = 1;
|
|
}
|
|
|
|
enum ShootingScheduleResult {
|
|
SHOOTING_SCHEDULE_RESULT_PENDING_START = 0;
|
|
SHOOTING_SCHEDULE_RESULT_ALL_COMPLETED = 1;
|
|
SHOOTING_SCHEDULE_RESULT_PARTIALLY_COMPLETED = 2;
|
|
SHOOTING_SCHEDULE_RESULT_ALL_FAILED = 3;
|
|
}
|
|
|
|
enum ShootingTaskState {
|
|
SHOOTING_TASK_STATUS_IDLE = 0;
|
|
SHOOTING_TASK_STATUS_SHOOTING = 1;
|
|
SHOOTING_TASK_STATUS_SUCCESS = 2;
|
|
SHOOTING_TASK_STATUS_FAILED = 3;
|
|
SHOOTING_TASK_STATUS_INTERRUPTED = 4;
|
|
}
|
|
|
|
enum ShootingScheduleMode {
|
|
SHOOTING_SCHEDULE_MODE_ASTRO_DEEP_SKY = 0;
|
|
}
|
|
|
|
message ShootingTaskMsg {
|
|
string schedule_id = 1;
|
|
string params = 2;
|
|
ShootingTaskState state = 3;
|
|
int32 code = 4;
|
|
int64 created_time = 5;
|
|
int64 updated_time = 6;
|
|
string schedule_task_id = 7;
|
|
int32 param_mode = 8;
|
|
int32 param_version = 9;
|
|
int32 create_from = 10;
|
|
}
|
|
|
|
message ShootingScheduleMsg {
|
|
string schedule_id = 1;
|
|
string schedule_name = 2;
|
|
int32 device_id = 3;
|
|
string mac_address = 4;
|
|
int64 start_time = 5;
|
|
int64 end_time = 6;
|
|
ShootingScheduleResult result = 7;
|
|
int64 created_time = 8;
|
|
int64 updated_time = 9;
|
|
ShootingScheduleState state = 10;
|
|
int32 lock = 11;
|
|
string password = 12;
|
|
repeated ShootingTaskMsg shooting_tasks = 13;
|
|
int32 param_mode = 14;
|
|
int32 param_version = 15;
|
|
string params = 16;
|
|
int64 schedule_time = 17;
|
|
ShootingScheduleSyncState sync_state = 18;
|
|
}
|
|
|
|
message ReqSyncShootingSchedule {
|
|
ShootingScheduleMsg shooting_schedule = 1;
|
|
}
|
|
|
|
message ResSyncShootingSchedule {
|
|
ShootingScheduleMsg shooting_schedule = 1;
|
|
repeated string time_conflict_schedule_ids = 2;
|
|
int32 code = 3;
|
|
bool can_replace = 4;
|
|
}
|
|
|
|
message ReqCancelShootingSchedule {
|
|
string id = 1;
|
|
string password = 2;
|
|
}
|
|
|
|
message ResCancelShootingSchedule {
|
|
string id = 1;
|
|
int32 code = 2;
|
|
}
|
|
|
|
message ReqGetAllShootingSchedule {
|
|
}
|
|
|
|
message ResGetAllShootingSchedule {
|
|
repeated ShootingScheduleMsg shooting_schedule = 1;
|
|
int32 code = 2;
|
|
}
|
|
|
|
message ReqGetShootingScheduleById {
|
|
string id = 1;
|
|
}
|
|
|
|
message ResGetShootingScheduleById {
|
|
ShootingScheduleMsg shooting_schedule = 1;
|
|
int32 code = 2;
|
|
}
|
|
|
|
message ReqGetShootingTaskById {
|
|
string id = 1;
|
|
}
|
|
|
|
message ResGetShootingTaskById {
|
|
ShootingTaskMsg shooting_task = 1;
|
|
int32 code = 2;
|
|
}
|
|
|
|
message ReqReplaceShootingSchedule {
|
|
ShootingScheduleMsg shooting_schedule = 1;
|
|
}
|
|
|
|
message ResReplaceShootingSchedule {
|
|
ShootingScheduleMsg shooting_schedule = 1;
|
|
repeated ShootingScheduleMsg replaced_shooting_schedule = 2;
|
|
int32 code = 3;
|
|
}
|
|
|
|
message ReqUnlockShootingSchedule {
|
|
string id = 1;
|
|
string password = 2;
|
|
}
|
|
|
|
message ResUnlockShootingSchedule {
|
|
string id = 1;
|
|
int32 code = 2;
|
|
}
|
|
|
|
message ReqLockShootingSchedule {
|
|
string id = 1;
|
|
string password = 2;
|
|
}
|
|
|
|
message ResLockShootingSchedule {
|
|
string id = 1;
|
|
string password = 2;
|
|
int32 code = 3;
|
|
}
|
|
|
|
message ReqDeleteShootingSchedule {
|
|
string id = 1;
|
|
string password = 2;
|
|
}
|
|
|
|
message ResDeleteShootingSchedule {
|
|
string id = 1;
|
|
int32 code = 2;
|
|
}
|
|
|