The motor reset command (cmd 14003, inferred from a gap in the WsCmd enum)
caused the telescope to immediately reboot during live testing. The Android
app never uses ReqMotorReset — it only uses joystick (14006-14009), run
(14000) and stop (14002). Motor homing is handled internally by the firmware
during astro calibration or panorama operations.
Changes:
- MotorReset() now returns an error instead of sending the command
- CLI "motor reset" shows the disabled message
- Documented the incident and lessons in TEST_RESULTS.md
- Confirmed motor position query (cmd 14001) works but returns NEED_RESET
- Confirmed slew movements (all directions/speeds) work safely after reboot
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
dwarfctl — Open-source DWARF telescope control CLI
A Go command-line tool for controlling DWARF II (and upcoming DWARF III) smart telescopes over Wi-Fi via their WebSocket API.
Built from reverse-engineered protocol specs (see ../analysis/).
Quick start
make build
./dwarfctl --ip 192.168.1.100 state
Prerequisites
The telescope must be connected to your Wi-Fi (or your phone joined to the
telescope's AP hotspot). Once you can ping the telescope's IP, dwarfctl can
reach it.
Find the IP from:
- The official DWARFLAB app's Settings → My Device
- Your router's DHCP table (hostname like
DWARFxxxx) - The BLE handshake (see
../analysis/API_REFERENCE.md)
Usage examples
# Device state
dwarfctl --ip 192.168.1.100 state
# Camera
dwarfctl camera open --cam tele
dwarfctl camera photo --cam tele
dwarfctl camera photo --cam wide --raw
dwarfctl camera burst start --cam tele
dwarfctl camera exp --cam tele 156 # 1/4s exposure
dwarfctl camera gain --cam tele 60
dwarfctl camera params --cam tele
# Motor / slew
dwarfctl motor slew 90 0.5 # angle 90°, half speed
dwarfctl motor stop 0 # stop RA motor
dwarfctl motor goto 0 45.0 5.0 # RA motor to 45° at speed 5
# Astrophotography
dwarfctl astro calibrate start
dwarfctl astro goto-dso 10.6847 41.2687 "M31 Andromeda"
dwarfctl astro goto-solar 3 # Earth = index 3
dwarfctl astro stack start
dwarfctl astro eq-solve start # polar alignment
# Focus
dwarfctl focus auto
dwarfctl focus step 1 # 1=out, 0=in
dwarfctl focus astro-af start
# Tracking
dwarfctl track start 640 360 100 100 0 # bbox at center of tele cam
dwarfctl track stop
# System
dwarfctl system sync-time
dwarfctl system set-location 48.8566 2.3522 35
# Power
dwarfctl power rgb-on
dwarfctl power rgb-off
dwarfctl power reboot
dwarfctl power off
# Monitor notifications (live status events)
dwarfctl monitor
Architecture
dwarfctl/
├── proto/dwarf.proto # unified proto3 definitions (397 messages)
├── proto/dwarf.pb.go # generated Go bindings (24948 lines)
├── internal/
│ ├── transport/client.go # WebSocket client + WsPacket envelope
│ └── api/
│ ├── commands.go # 323 command IDs + module routing
│ └── client.go # typed Telescope API (camera/motor/astro/...)
└── cmd/dwarfctl/main.go # cobra CLI
Protocol summary
| Layer | Transport | Port |
|---|---|---|
| Control | WebSocket (binary protobuf) | 9900 |
| Preview | RTSP (not implemented here) | 554 |
Every command is a serialized WsPacket{major=2, minor=3, device_id, module_id, cmd, type, data, client_id}. The module_id is derived from cmd
by range. See ../analysis/API_REFERENCE.md for details.
Regenerating protos
If the proto definitions change:
# From the APK analysis directory:
python3 ../analysis/extract_protos.py ../extracted/jadx/.../proto ../analysis/protos
# Merge into unified proto:
cd dwarfctl/proto
python3 merge.py . dwarf.proto
protoc --go_out=. --go_opt=paths=source_relative dwarf.proto
Roadmap
- BLE discovery + handshake (DwarfPing/DwarfEcho)
- RTSP preview viewer
- Interactive REPL mode
- Schedule plan management
- OTA firmware update
- Full Notify event parsing (typed)