Reverse-engineer DWARF II telescope API and build open-source Go client
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
This commit is contained in:
368
analysis/protos/Astro.proto
Normal file
368
analysis/protos/Astro.proto
Normal file
@ -0,0 +1,368 @@
|
||||
// source: astro.proto
|
||||
syntax = "proto3";
|
||||
|
||||
message ReqStartCalibration {
|
||||
double lon = 1;
|
||||
double lat = 2;
|
||||
}
|
||||
|
||||
message ReqStopCalibration {
|
||||
}
|
||||
|
||||
message ReqGotoDSO {
|
||||
double ra = 1;
|
||||
double dec = 2;
|
||||
string target_name = 3;
|
||||
bool goto_only = 4;
|
||||
}
|
||||
|
||||
message ReqGotoSolarSystem {
|
||||
int32 index = 1;
|
||||
double lon = 2;
|
||||
double lat = 3;
|
||||
string target_name = 4;
|
||||
bool force_start = 5;
|
||||
}
|
||||
|
||||
message ResGotoSolarSystem {
|
||||
int32 code = 1;
|
||||
ReqGotoSolarSystem req = 2;
|
||||
}
|
||||
|
||||
message ReqStopGoto {
|
||||
}
|
||||
|
||||
message ReqCaptureRawLiveStacking {
|
||||
int32 ir_index = 1;
|
||||
bool force_start = 2;
|
||||
}
|
||||
|
||||
message ReqStopCaptureRawLiveStacking {
|
||||
}
|
||||
|
||||
message ReqFastStopCaptureRawLiveStacking {
|
||||
}
|
||||
|
||||
message ReqCheckDarkFrame {
|
||||
}
|
||||
|
||||
message ResCheckDarkFrame {
|
||||
int32 progress = 1;
|
||||
int32 code = 2;
|
||||
}
|
||||
|
||||
message ReqCaptureDarkFrame {
|
||||
int32 reshoot = 1;
|
||||
}
|
||||
|
||||
message ReqStopCaptureDarkFrame {
|
||||
}
|
||||
|
||||
message ReqCaptureDarkFrameWithParam {
|
||||
int32 exp_index = 1;
|
||||
int32 gain_index = 2;
|
||||
int32 bin_index = 3;
|
||||
int32 cap_size = 4;
|
||||
}
|
||||
|
||||
message ReqStopCaptureDarkFrameWithParam {
|
||||
}
|
||||
|
||||
message ReqGetDarkFrameList {
|
||||
}
|
||||
|
||||
message ResGetDarkFrameInfo {
|
||||
// oneof _temperature
|
||||
int32 exp_index = 1;
|
||||
int32 gain_index = 2;
|
||||
int32 bin_index = 3;
|
||||
string exp_name = 4;
|
||||
string gain_name = 5;
|
||||
string bin_name = 6;
|
||||
int32 temperature = 7;
|
||||
}
|
||||
|
||||
message ResGetDarkFrameInfoList {
|
||||
int32 code = 1;
|
||||
repeated ResGetDarkFrameInfo results = 2;
|
||||
}
|
||||
|
||||
message ReqDelDarkFrame {
|
||||
int32 exp_index = 1;
|
||||
int32 gain_index = 2;
|
||||
int32 bin_index = 3;
|
||||
int32 temp_value = 4;
|
||||
}
|
||||
|
||||
message ReqDelDarkFrameList {
|
||||
repeated ReqDelDarkFrame dark_list = 1;
|
||||
}
|
||||
|
||||
message ResDelDarkFrameList {
|
||||
int32 code = 1;
|
||||
}
|
||||
|
||||
message ReqGoLive {
|
||||
}
|
||||
|
||||
message ReqTrackSpecialTarget {
|
||||
int32 index = 1;
|
||||
double lon = 2;
|
||||
double lat = 3;
|
||||
}
|
||||
|
||||
message ReqStopTrackSpecialTarget {
|
||||
}
|
||||
|
||||
message ReqOneClickGotoDSO {
|
||||
double ra = 1;
|
||||
double dec = 2;
|
||||
string target_name = 3;
|
||||
double lon = 4;
|
||||
double lat = 5;
|
||||
int32 shooting_mode = 6;
|
||||
bool goto_only = 7;
|
||||
}
|
||||
|
||||
message ResOneClickGoto {
|
||||
int32 step = 1;
|
||||
int32 code = 2;
|
||||
bool all_end = 3;
|
||||
}
|
||||
|
||||
message ReqOneClickGotoSolarSystem {
|
||||
int32 index = 1;
|
||||
double lon = 2;
|
||||
double lat = 3;
|
||||
string target_name = 4;
|
||||
int32 shooting_mode = 5;
|
||||
bool force_start = 6;
|
||||
}
|
||||
|
||||
message ResOneClickGotoSolarSystem {
|
||||
int32 step = 1;
|
||||
int32 code = 2;
|
||||
bool all_end = 3;
|
||||
ReqOneClickGotoSolarSystem req = 4;
|
||||
}
|
||||
|
||||
message ReqStopOneClickGoto {
|
||||
}
|
||||
|
||||
message ReqCaptureWideRawLiveStacking {
|
||||
bool force_start = 1;
|
||||
}
|
||||
|
||||
message ReqStopCaptureWideRawLiveStacking {
|
||||
}
|
||||
|
||||
message ReqFastStopCaptureWideRawLiveStacking {
|
||||
}
|
||||
|
||||
message ReqStartEqSolving {
|
||||
double lon = 1;
|
||||
double lat = 2;
|
||||
}
|
||||
|
||||
message ResStartEqSolving {
|
||||
double azi_err = 1;
|
||||
double alt_err = 2;
|
||||
int32 code = 3;
|
||||
}
|
||||
|
||||
message ReqStopEqSolving {
|
||||
}
|
||||
|
||||
message ReqStartAiEnhance {
|
||||
}
|
||||
|
||||
message ReqStopAiEnhance {
|
||||
}
|
||||
|
||||
message ReqStartMosaic {
|
||||
int32 horizontal_scale = 1;
|
||||
int32 vertical_scale = 2;
|
||||
int32 rotation = 3;
|
||||
int32 ir_index = 4;
|
||||
bool force_start = 5;
|
||||
}
|
||||
|
||||
message ReqStartMakeFitsThumb {
|
||||
string src_dir = 1;
|
||||
}
|
||||
|
||||
message ReqStopMakeFitsThumb {
|
||||
}
|
||||
|
||||
message ResMakeFitsThumb {
|
||||
int32 code = 1;
|
||||
string src_dir = 2;
|
||||
}
|
||||
|
||||
message MakeFitsThumbTaskParam {
|
||||
string src_dir = 1;
|
||||
}
|
||||
|
||||
message ReqIsImageStackable {
|
||||
repeated string src_dirs = 1;
|
||||
}
|
||||
|
||||
message ResIsImageStackable {
|
||||
int32 code = 1;
|
||||
repeated ResGetDarkFrameInfo need_dark_frame_info = 2;
|
||||
}
|
||||
|
||||
message ReqStartRepostprocess {
|
||||
repeated string src_dirs = 1;
|
||||
}
|
||||
|
||||
message ReqStopRepostprocess {
|
||||
}
|
||||
|
||||
message ResRepostprocess {
|
||||
int32 code = 1;
|
||||
string result_dir = 2;
|
||||
}
|
||||
|
||||
message RepostprocessTaskParam {
|
||||
string result_dir = 1;
|
||||
}
|
||||
|
||||
message ReqGetAstroShootingTime {
|
||||
int32 exp_index = 1;
|
||||
int32 horizontal_scale = 2;
|
||||
int32 vertical_scale = 3;
|
||||
int32 rotation = 4;
|
||||
int32 cam_id = 5;
|
||||
int32 shooting_mode = 6;
|
||||
}
|
||||
|
||||
message ResGetAstroShootingTime {
|
||||
enum AstroMode {
|
||||
NORMAL = 0;
|
||||
MOSAIC = 1;
|
||||
}
|
||||
int32 code = 1;
|
||||
int32 shooting_time = 2;
|
||||
int32 cam_id = 3;
|
||||
ResGetAstroShootingTime.AstroMode astro_mode = 4;
|
||||
int32 shooting_mode = 5;
|
||||
}
|
||||
|
||||
message ReqGetCaliFrameList {
|
||||
int32 camera_type = 1;
|
||||
int32 cali_frame_type = 2;
|
||||
}
|
||||
|
||||
message CaliFrameInfo {
|
||||
// oneof _filter_type
|
||||
// oneof _info_id
|
||||
// oneof _temp_value
|
||||
string exp_name = 1;
|
||||
int32 gain = 2;
|
||||
int32 resolution = 3;
|
||||
int32 camera_type = 4;
|
||||
int32 progress = 5;
|
||||
int32 cali_frame_type = 6;
|
||||
int32 filter_type = 7;
|
||||
int32 info_id = 8;
|
||||
int32 temp_value = 9;
|
||||
}
|
||||
|
||||
message ResGetCaliFrameList {
|
||||
int32 code = 1;
|
||||
repeated CaliFrameInfo list = 2;
|
||||
int32 camera_type = 3;
|
||||
int32 cali_frame_type = 4;
|
||||
}
|
||||
|
||||
message ReqDelCaliFrameList {
|
||||
repeated int32 info_ids = 1;
|
||||
}
|
||||
|
||||
message ReqCaptureCaliFrame {
|
||||
// oneof _filter_type
|
||||
int32 exp_index = 1;
|
||||
int32 gain = 2;
|
||||
int32 resolution = 3;
|
||||
int32 cap_size = 4;
|
||||
int32 camera_type = 5;
|
||||
int32 cali_frame_type = 6;
|
||||
int32 filter_type = 7;
|
||||
int32 scene_type = 8;
|
||||
}
|
||||
|
||||
message ReqStopCaptureCaliFrame {
|
||||
int32 camera_type = 1;
|
||||
}
|
||||
|
||||
message CaptureCaliFrameTaskParam {
|
||||
int32 camera_type = 1;
|
||||
int32 cali_frame_type = 2;
|
||||
}
|
||||
|
||||
message ReqGetQuickSetList {
|
||||
int32 camera_type = 1;
|
||||
}
|
||||
|
||||
message QuikSetInfo {
|
||||
string exp_name = 1;
|
||||
int32 exp_index = 2;
|
||||
int32 gain = 3;
|
||||
int32 resolution = 4;
|
||||
int32 camera_type = 5;
|
||||
string info_id = 6;
|
||||
}
|
||||
|
||||
message ResGetQuikSetList {
|
||||
int32 code = 1;
|
||||
repeated QuikSetInfo quick_set_list = 2;
|
||||
int32 camera_type = 3;
|
||||
}
|
||||
|
||||
message ReqSetQuickSet {
|
||||
string info_id = 1;
|
||||
}
|
||||
|
||||
message ResSetQuickSet {
|
||||
int32 code = 1;
|
||||
string info_id = 2;
|
||||
}
|
||||
|
||||
message ReqOneClickShooting {
|
||||
}
|
||||
|
||||
message ResAstroShooting {
|
||||
// oneof _exp_name
|
||||
// oneof _gain
|
||||
// oneof _resolution
|
||||
// oneof _filter_type
|
||||
// oneof _temp_threshold
|
||||
int32 code = 1;
|
||||
string exp_name = 2;
|
||||
int32 gain = 3;
|
||||
int32 resolution = 4;
|
||||
int32 filter_type = 5;
|
||||
int32 temp_threshold = 6;
|
||||
}
|
||||
|
||||
message ReqStartSkyTargetFinder {
|
||||
double lon = 1;
|
||||
double lat = 2;
|
||||
bool force_restart = 3;
|
||||
int32 scene_type = 4;
|
||||
}
|
||||
|
||||
message ResStartSkyTargetFinder {
|
||||
int32 code = 1;
|
||||
double zenith_azi = 2;
|
||||
double zenith_alt = 3;
|
||||
double center_azi = 4;
|
||||
double center_alt = 5;
|
||||
double center_roll = 6;
|
||||
int32 scene_type = 7;
|
||||
}
|
||||
|
||||
message ReqStopSkyTargetFinder {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user