Add interactive slew mode and fix joystick coordinate mapping
- New `dwarfctl interactive` command: persistent WS connection with
real-time slew commands on a single session (no reconnect per command).
Decodes DeviceAttitude notifications (cmd 15295) when available.
- Fixed coordinate mapping: 90°=UP (toward sky), 270°=DOWN (toward ground),
confirmed by live testing on DWARF II in alt-az config.
- Monitor now decodes attitude notifications inline.
- Note: IMU attitude (15295) is NOT pushed during free slew — only during
calibration/EQ solving. Position feedback requires astro calibration.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@ -105,9 +105,9 @@ Tests en condition réelle, télescope posé verticalement sur trépied (pas en
|
|||||||
| Angle | Axe | Direction observée |
|
| Angle | Axe | Direction observée |
|
||||||
|-------|-----|-------------------|
|
|-------|-----|-------------------|
|
||||||
| 0° | Azimut (horizontal) | Rotation horaire vue de dessus |
|
| 0° | Azimut (horizontal) | Rotation horaire vue de dessus |
|
||||||
| 90° | Altitude | **Descente** (vers le sol) |
|
| 90° | Altitude | **Montée** (vers le ciel) |
|
||||||
| 180° | Azimut | Rotation anti-horaire (opposé de 0°) |
|
| 180° | Azimut | Rotation anti-horaire (opposé de 0°) |
|
||||||
| 270° | Altitude | **Montée** (vers le ciel) |
|
| 270° | Altitude | **Descente** (vers le sol) |
|
||||||
|
|
||||||
**IMPORTANT** : ces directions sont validées pour une config alt-az simple (télescope vertical sur trépied). En mode équatorial (après EQ calibration), le mapping peut changer car les moteurs changent de référentiel.
|
**IMPORTANT** : ces directions sont validées pour une config alt-az simple (télescope vertical sur trépied). En mode équatorial (après EQ calibration), le mapping peut changer car les moteurs changent de référentiel.
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@ -9,7 +10,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/antitbone/dwarfctl/internal/api"
|
"github.com/antitbone/dwarfctl/internal/api"
|
||||||
|
pb "github.com/antitbone/dwarfctl/proto"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -39,6 +42,7 @@ func main() {
|
|||||||
cmdSystem(),
|
cmdSystem(),
|
||||||
cmdPower(),
|
cmdPower(),
|
||||||
cmdMonitor(),
|
cmdMonitor(),
|
||||||
|
cmdInteractive(),
|
||||||
cmdPano(),
|
cmdPano(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -667,7 +671,8 @@ func cmdPano() *cobra.Command {
|
|||||||
|
|
||||||
// --- monitor ---
|
// --- monitor ---
|
||||||
|
|
||||||
func cmdMonitor() *cobra.Command { return &cobra.Command{
|
func cmdMonitor() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
Use: "monitor",
|
Use: "monitor",
|
||||||
Short: "Listen for telescope notifications (Ctrl-C to stop)",
|
Short: "Listen for telescope notifications (Ctrl-C to stop)",
|
||||||
Run: func(_ *cobra.Command, _ []string) {
|
Run: func(_ *cobra.Command, _ []string) {
|
||||||
@ -676,8 +681,71 @@ func cmdMonitor() *cobra.Command { return &cobra.Command{
|
|||||||
ch := scope.Notifications()
|
ch := scope.Notifications()
|
||||||
fmt.Println("Monitoring notifications (Ctrl-C to stop)...")
|
fmt.Println("Monitoring notifications (Ctrl-C to stop)...")
|
||||||
for pkt := range ch {
|
for pkt := range ch {
|
||||||
fmt.Printf("[NOTIFY] cmd=%d module=%d type=%d data_len=%d\n",
|
cmdID := pkt.GetCmd()
|
||||||
pkt.GetCmd(), pkt.GetModuleId(), pkt.GetType(), len(pkt.GetData()))
|
extra := ""
|
||||||
|
if cmdID == 15295 && len(pkt.GetData()) > 0 {
|
||||||
|
att := &pb.DeviceAttitude{}
|
||||||
|
if proto.Unmarshal(pkt.GetData(), att) == nil {
|
||||||
|
extra = fmt.Sprintf(" pitch=%.1f yaw=%.1f roll=%.1f", att.GetPitch(), att.GetYaw(), att.GetRoll())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("[NOTIFY] cmd=%d module=%d type=%d data=%d%s\n",
|
||||||
|
cmdID, pkt.GetModuleId(), pkt.GetType(), len(pkt.GetData()), extra)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdInteractive() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "interactive",
|
||||||
|
Short: "Interactive slew with real-time attitude (Ctrl-C to exit)",
|
||||||
|
Run: func(_ *cobra.Command, _ []string) {
|
||||||
|
scope, cleanup := dial()
|
||||||
|
defer cleanup()
|
||||||
|
ch := scope.Notifications()
|
||||||
|
go func() {
|
||||||
|
for pkt := range ch {
|
||||||
|
if pkt.GetCmd() == 15295 && len(pkt.GetData()) > 0 {
|
||||||
|
att := &pb.DeviceAttitude{}
|
||||||
|
if proto.Unmarshal(pkt.GetData(), att) == nil {
|
||||||
|
fmt.Printf("\r [ATTITUDE] pitch=%.1f yaw=%.1f roll=%.1f \n", att.GetPitch(), att.GetYaw(), att.GetRoll())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fmt.Println("Interactive slew. Commands: <angle> <speed> [duration_s], stop, photo, state, q")
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
for {
|
||||||
|
fmt.Print("> ")
|
||||||
|
if !scanner.Scan() { break }
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" { continue }
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
switch parts[0] {
|
||||||
|
case "q", "quit", "exit":
|
||||||
|
return
|
||||||
|
case "stop":
|
||||||
|
must(scope.MotorJoystickStop())
|
||||||
|
fmt.Println("stopped")
|
||||||
|
case "photo":
|
||||||
|
must(scope.TakePhoto(api.CameraTele))
|
||||||
|
fmt.Println("photo taken")
|
||||||
|
case "state":
|
||||||
|
st, err := scope.GetDeviceState()
|
||||||
|
must(err)
|
||||||
|
fmt.Printf("battery=%d%% focus=%d\n", st.GetDeviceStateInfo().GetBatteryInfo().GetPercentage(), st.GetFocusMotorStateInfo().GetFocusPosition().GetPos())
|
||||||
|
default:
|
||||||
|
if len(parts) < 2 { fmt.Println("usage: <angle> <speed> [duration]"); continue }
|
||||||
|
angle := parseFloat(parts[0])
|
||||||
|
speed := parseFloat(parts[1])
|
||||||
|
dur := 1.0
|
||||||
|
if len(parts) > 2 { dur = parseFloat(parts[2]) }
|
||||||
|
must(scope.SlewJoystick(angle, speed))
|
||||||
|
time.Sleep(time.Duration(dur * float64(time.Second)))
|
||||||
|
must(scope.SlewJoystick(0, 0))
|
||||||
|
fmt.Println("done")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user