diff --git a/dist/roverd b/dist/roverd index 2aaa2b3a..d9201195 100755 Binary files a/dist/roverd and b/dist/roverd differ diff --git a/dist/servoverifier b/dist/servoverifier index 05731deb..2976e5ee 100755 Binary files a/dist/servoverifier and b/dist/servoverifier differ diff --git a/pi/roverd/serial.go b/pi/roverd/serial.go index 22f1f37c..2cc0ad28 100644 --- a/pi/roverd/serial.go +++ b/pi/roverd/serial.go @@ -92,6 +92,10 @@ func (s *SerialAdapter) SendRaw(raw []byte) error { return s.write(raw) } +func (s *SerialAdapter) StartOI() error { + return s.write([]byte{128}) +} + func (s *SerialAdapter) SeekDock() error { return s.write([]byte{143}) } diff --git a/pi/roverd/serial_dummy.go b/pi/roverd/serial_dummy.go index 439fdc74..8b4c421b 100644 --- a/pi/roverd/serial_dummy.go +++ b/pi/roverd/serial_dummy.go @@ -52,6 +52,11 @@ func (s *SerialAdapter) SendRaw(raw []byte) error { return nil } +func (s *SerialAdapter) StartOI() error { + s.log.Printf("[dummy] start OI") + return nil +} + func (s *SerialAdapter) SeekDock() error { s.log.Printf("[dummy] seek dock") return nil diff --git a/pi/roverd/wsclient.go b/pi/roverd/wsclient.go index 10f1a42b..becb83aa 100644 --- a/pi/roverd/wsclient.go +++ b/pi/roverd/wsclient.go @@ -15,14 +15,14 @@ type WSClient struct { cfg *Config adapter *SerialAdapter sensorFrames <-chan []byte - events <-chan RoverEvent + events chan RoverEvent media *MediaSupervisor servo *CameraServo nightVision *NightVisionLight log *log.Logger } -func NewWSClient(cfg *Config, adapter *SerialAdapter, frames <-chan []byte, events <-chan RoverEvent, media *MediaSupervisor, servo *CameraServo, nightVision *NightVisionLight, logger *log.Logger) *WSClient { +func NewWSClient(cfg *Config, adapter *SerialAdapter, frames <-chan []byte, events chan RoverEvent, media *MediaSupervisor, servo *CameraServo, nightVision *NightVisionLight, logger *log.Logger) *WSClient { return &WSClient{ cfg: cfg, adapter: adapter, @@ -186,11 +186,64 @@ func (c *WSClient) handleServoCommand(payload *servoPayload) error { } func (c *WSClient) forwardSensors(ctx context.Context, conn *websocket.Conn) { + const ( + sensorSilenceTimeout = 5 * time.Second + sensorRecoveryCooldown = 3 * time.Second + ) + + timer := time.NewTimer(sensorSilenceTimeout) + defer timer.Stop() + + resetTimer := func() { + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer.Reset(sensorSilenceTimeout) + } + + lastRecovery := time.Time{} + lastFrame := time.Now() + for { select { case <-ctx.Done(): return + case <-timer.C: + now := time.Now() + if !lastRecovery.IsZero() && now.Sub(lastRecovery) < sensorRecoveryCooldown { + resetTimer() + continue + } + + idleFor := now.Sub(lastFrame) + if idleFor < 0 { + idleFor = sensorSilenceTimeout + } + + c.log.Printf("no sensor frames for %v; restarting OI and sensor stream", idleFor) + c.emitEvent("sensorWatchdog.restart", map[string]any{ + "idleMs": idleFor.Milliseconds(), + }) + if err := c.adapter.StartOI(); err != nil { + c.log.Printf("start OI failed: %v", err) + c.emitEvent("sensorWatchdog.startOI.error", map[string]any{"error": err.Error()}) + } + if err := c.ensureSensorStream(); err != nil { + c.log.Printf("sensor stream restart failed: %v", err) + c.emitEvent("sensorWatchdog.streamRestart.error", map[string]any{"error": err.Error()}) + } else { + c.emitEvent("sensorWatchdog.streamRestart.ok", map[string]any{ + "idleMs": idleFor.Milliseconds(), + }) + } + lastRecovery = now + resetTimer() case frame := <-c.sensorFrames: + resetTimer() + lastFrame = time.Now() msg := sensorMessage{ Type: "sensor", Timestamp: time.Now().UnixMilli(), @@ -224,6 +277,21 @@ func (c *WSClient) forwardEvents(ctx context.Context, conn *websocket.Conn) { } } +func (c *WSClient) emitEvent(event string, data map[string]any) { + if c.events == nil { + return + } + select { + case c.events <- RoverEvent{ + Type: "event", + Event: event, + Ts: time.Now().UnixMilli(), + Data: data, + }: + default: + } +} + func writeJSON(ctx context.Context, conn *websocket.Conn, v any) error { data, err := json.Marshal(v) if err != nil { diff --git a/plans/ui_themeing_outline.md b/plans/ui_themeing_outline.md new file mode 100644 index 00000000..67bbd788 --- /dev/null +++ b/plans/ui_themeing_outline.md @@ -0,0 +1,14 @@ +# general idea +- users can choose from preset, and create UI themes +- the default theme is the current in place theme +- themes will NOT change margins, borders, padding, spacing, etc. +- should be as simple as changing the global css stuff +- themes can change colors, fonts, border radius, borders, etc + +## technical stuff +- save theme info in + +## UI for themeing +- allow people to tune every theme element that there is to tune +- allow people to export themes as json files, and import them +- \ No newline at end of file