package control import ( "sync" "time" offpkg "github.com/jan/fm-rds-tx/internal/offline" ) type TelemetryMessage struct { Type string `json:"type"` TS time.Time `json:"ts"` Seq uint64 `json:"seq"` Data *offpkg.MeasurementSnapshot `json:"data,omitempty"` } type telemetrySubscriber struct { ch chan TelemetryMessage } type TelemetryHub struct { mu sync.Mutex subscribers map[*telemetrySubscriber]struct{} } func NewTelemetryHub() *TelemetryHub { return &TelemetryHub{subscribers: make(map[*telemetrySubscriber]struct{})} } func (h *TelemetryHub) Subscribe() (*telemetrySubscriber, func()) { sub := &telemetrySubscriber{ch: make(chan TelemetryMessage, 1)} h.mu.Lock() h.subscribers[sub] = struct{}{} h.mu.Unlock() return sub, func() { h.mu.Lock() if _, ok := h.subscribers[sub]; ok { delete(h.subscribers, sub) close(sub.ch) } h.mu.Unlock() } } func (h *TelemetryHub) PublishMeasurement(snapshot *offpkg.MeasurementSnapshot) { if h == nil || snapshot == nil { return } msg := TelemetryMessage{ Type: "measurement", TS: snapshot.Timestamp, Seq: snapshot.Sequence, Data: snapshot, } h.mu.Lock() subs := make([]*telemetrySubscriber, 0, len(h.subscribers)) for sub := range h.subscribers { subs = append(subs, sub) } h.mu.Unlock() for _, sub := range subs { select { case sub.ch <- msg: default: select { case <-sub.ch: default: } select { case sub.ch <- msg: default: } } } }