feat(agent): mqttsubscriber abonniert Command-Topic

This commit is contained in:
Jesko Anschütz 2026-03-26 23:23:23 +01:00
parent bcc50635e5
commit 1047572157

View file

@ -4,6 +4,7 @@
package mqttsubscriber package mqttsubscriber
import ( import (
"encoding/json"
"time" "time"
mqtt "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang"
@ -20,6 +21,8 @@ const (
// screenshotRequestTopicTemplate is the topic the backend publishes to for on-demand screenshots. // screenshotRequestTopicTemplate is the topic the backend publishes to for on-demand screenshots.
screenshotRequestTopicTemplate = "signage/screen/%s/screenshot-request" screenshotRequestTopicTemplate = "signage/screen/%s/screenshot-request"
commandTopicTemplate = "signage/screen/%s/command"
) )
// PlaylistChangedFunc is called when a debounced playlist-changed notification arrives. // PlaylistChangedFunc is called when a debounced playlist-changed notification arrives.
@ -28,6 +31,9 @@ type PlaylistChangedFunc func()
// ScreenshotRequestFunc is called when a screenshot-request notification arrives. // ScreenshotRequestFunc is called when a screenshot-request notification arrives.
type ScreenshotRequestFunc func() type ScreenshotRequestFunc func()
// DisplayCommandFunc wird aufgerufen wenn ein display-command eintrifft.
type DisplayCommandFunc func(action string)
// Subscriber listens for playlist-changed notifications on MQTT and calls the // Subscriber listens for playlist-changed notifications on MQTT and calls the
// provided callback at most once per debounceDuration. // provided callback at most once per debounceDuration.
type Subscriber struct { type Subscriber struct {
@ -35,10 +41,12 @@ type Subscriber struct {
timer *time.Timer timer *time.Timer
onChange PlaylistChangedFunc onChange PlaylistChangedFunc
onScreenshotRequest ScreenshotRequestFunc onScreenshotRequest ScreenshotRequestFunc
onDisplayCommand DisplayCommandFunc
// timerC serializes timer resets through a dedicated goroutine. // timerC serializes timer resets through a dedicated goroutine.
resetC chan struct{} resetC chan struct{}
screenshotReqC chan struct{} screenshotReqC chan struct{}
commandC chan string
stopC chan struct{} stopC chan struct{}
} }
@ -52,13 +60,19 @@ func ScreenshotRequestTopic(screenSlug string) string {
return "signage/screen/" + screenSlug + "/screenshot-request" return "signage/screen/" + screenSlug + "/screenshot-request"
} }
// CommandTopic returns the MQTT topic for display commands for a given screenSlug.
func CommandTopic(screenSlug string) string {
return "signage/screen/" + screenSlug + "/command"
}
// New creates a Subscriber that connects to broker and subscribes to the // New creates a Subscriber that connects to broker and subscribes to the
// playlist-changed topic for screenSlug. onChange is called (in its own // playlist-changed topic for screenSlug. onChange is called (in its own
// goroutine) at most once per debounceDuration. // goroutine) at most once per debounceDuration.
// onScreenshotRequest is called (in its own goroutine) when a screenshot-request message arrives. // onScreenshotRequest is called (in its own goroutine) when a screenshot-request message arrives.
// onDisplayCommand is called (in its own goroutine) when a display command arrives.
// //
// Returns nil when broker is empty — callers must handle nil. // Returns nil when broker is empty — callers must handle nil.
func New(broker, screenSlug, username, password string, onChange PlaylistChangedFunc, onScreenshotRequest ScreenshotRequestFunc) *Subscriber { func New(broker, screenSlug, username, password string, onChange PlaylistChangedFunc, onScreenshotRequest ScreenshotRequestFunc, onDisplayCommand DisplayCommandFunc) *Subscriber {
if broker == "" { if broker == "" {
return nil return nil
} }
@ -66,8 +80,10 @@ func New(broker, screenSlug, username, password string, onChange PlaylistChanged
s := &Subscriber{ s := &Subscriber{
onChange: onChange, onChange: onChange,
onScreenshotRequest: onScreenshotRequest, onScreenshotRequest: onScreenshotRequest,
onDisplayCommand: onDisplayCommand,
resetC: make(chan struct{}, 16), resetC: make(chan struct{}, 16),
screenshotReqC: make(chan struct{}, 16), screenshotReqC: make(chan struct{}, 16),
commandC: make(chan string, 8),
stopC: make(chan struct{}), stopC: make(chan struct{}),
} }
@ -95,6 +111,19 @@ func New(broker, screenSlug, username, password string, onChange PlaylistChanged
default: // channel full — request already pending default: // channel full — request already pending
} }
}) })
commandTopic := CommandTopic(screenSlug)
c.Subscribe(commandTopic, 1, func(_ mqtt.Client, m mqtt.Message) { //nolint:errcheck
var cmd struct {
Action string `json:"action"`
}
if err := json.Unmarshal(m.Payload(), &cmd); err != nil {
return
}
select {
case s.commandC <- cmd.Action:
default:
}
})
}) })
if username != "" { if username != "" {
@ -131,6 +160,10 @@ func (s *Subscriber) run() {
if s.onScreenshotRequest != nil { if s.onScreenshotRequest != nil {
go s.onScreenshotRequest() go s.onScreenshotRequest()
} }
case action := <-s.commandC:
if s.onDisplayCommand != nil {
go s.onDisplayCommand(action)
}
} }
} }
} }