From 400b6efef7711b054ea06980bc8cd47257014a20 Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 29 Sep 2026 15:23:37 +0200 Subject: [PATCH] feat: gate video and terminal logs on AUX2 state --- cmd/groundstation/main.go | 44 ++++++++++++++++++------------ cmd/groundstation/video.go | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 cmd/groundstation/video.go diff --git a/cmd/groundstation/main.go b/cmd/groundstation/main.go index 8372767..ceae894 100644 --- a/cmd/groundstation/main.go +++ b/cmd/groundstation/main.go @@ -7,7 +7,6 @@ import ( "io" "log" "os" - "os/exec" "os/signal" "syscall" @@ -18,23 +17,21 @@ import ( func main() { serialPath := flag.String("serial", "/dev/ttyS0", "GX12 AUX1 UART device") stdin := flag.Bool("stdin", false, "read framed data from standard input") - startVideo := flag.Bool("start-video", false, "start fpv-video with Ground Station") + videoByAux2 := flag.Bool("video-by-aux2", true, "start fpv-video when AUX2 is above -512") + videoCommand := flag.String("video-command", "fpv-video", "video launcher command") flag.Parse() - if *stdin && *startVideo { - log.Fatal("-stdin is intended for protocol tests; omit -start-video") + if *stdin { + *videoByAux2 = false } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() - if *startVideo { - cmd := exec.CommandContext(ctx, "fpv-video") - cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr - if err := cmd.Start(); err != nil { - log.Fatalf("start fpv-video: %v", err) - } - defer func() { _ = cmd.Wait() }() + var video *videoController + if *videoByAux2 { + video = newVideoController(*videoCommand) + defer func() { _ = video.Stop() }() } var input io.Reader = os.Stdin @@ -49,13 +46,14 @@ func main() { input = port } - if err := run(ctx, input, os.Stdout); err != nil && err != context.Canceled { + if err := run(ctx, input, os.Stdout, video); err != nil && err != context.Canceled { log.Fatal(err) } } -func run(ctx context.Context, input io.Reader, output io.Writer) error { +func run(ctx context.Context, input io.Reader, output io.Writer, video *videoController) error { buf := make([]byte, protocol.RCSize) + var previous *protocol.RCFrame for { if err := readRCFrame(ctx, input, buf); err != nil { return err @@ -65,10 +63,22 @@ func run(ctx context.Context, input io.Reader, output io.Writer) error { log.Printf("discard frame: %v", err) continue } - fmt.Fprintf(output, "seq=%d t=%d ch1=%d ch2=%d ch3=%d ch4=%d aux1=%d aux2=%d\n", - frame.Sequence, frame.Timestamp, - frame.Channels[0], frame.Channels[1], frame.Channels[2], frame.Channels[3], - frame.Channels[4], frame.Channels[5]) + // AUX2 is the dedicated GS video switch: -1024 means management/off, + // while its middle position (0) means preflight/WFB/on. + if video != nil { + if err := video.Set(frame.Channels[5] > -512); err != nil { + return err + } + } + + if previous == nil || previous.Channels != frame.Channels { + fmt.Fprintf(output, "seq=%d t=%d ch1=%d ch2=%d ch3=%d ch4=%d aux1=%d aux2=%d\n", + frame.Sequence, frame.Timestamp, + frame.Channels[0], frame.Channels[1], frame.Channels[2], frame.Channels[3], + frame.Channels[4], frame.Channels[5]) + copy := frame + previous = © + } } } diff --git a/cmd/groundstation/video.go b/cmd/groundstation/video.go new file mode 100644 index 0000000..c6fcc7a --- /dev/null +++ b/cmd/groundstation/video.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "sync" +) + +// videoController owns the optional fpv-video launcher. It never touches WFB +// directly; fpv-video remains the sole owner of the video receiver lifecycle. +type videoController struct { + command string + mu sync.Mutex + cmd *exec.Cmd + done chan error +} + +func newVideoController(command string) *videoController { + return &videoController{command: command} +} + +func (v *videoController) Set(active bool) error { + v.mu.Lock() + defer v.mu.Unlock() + + if active { + if v.cmd != nil { + return nil + } + v.cmd = exec.Command(v.command) + v.cmd.Stdout, v.cmd.Stderr = os.Stdout, os.Stderr + if err := v.cmd.Start(); err != nil { + v.cmd = nil + return fmt.Errorf("start %s: %w", v.command, err) + } + v.done = make(chan error, 1) + cmd := v.cmd + go func() { v.done <- cmd.Wait() }() + return nil + } + + if v.cmd == nil { + return nil + } + if err := v.cmd.Process.Signal(os.Interrupt); err != nil { + return fmt.Errorf("stop %s: %w", v.command, err) + } + <-v.done + v.cmd, v.done = nil, nil + return nil +} + +func (v *videoController) Stop() error { + return v.Set(false) +}