package sensor import ( "context" "sync" "time" "mqtt_washingmachine/internal/status" ) // Sampler collects the latest valid message in each interval, independently of // HTTP requests. Its zero value is ready to use; all methods are concurrency-safe. type Sampler struct { // OnReceive, if set, runs after each valid message is stored and the lock // is released. Set it before using the sampler; it must be concurrency-safe // and return promptly so it does not delay MQTT handling. OnReceive func() mu sync.Mutex window status.Window power float64 receivedAt time.Time received bool } // Receive updates the pending measurement. Invalid payloads leave it untouched. func (s *Sampler) Receive(payload []byte) error { power, err := ParsePower(payload) if err != nil { return err } s.mu.Lock() s.power, s.receivedAt, s.received = power, time.Now(), true s.mu.Unlock() if s.OnReceive != nil { s.OnReceive() } return nil } // Run records one sample per tick, then clears the received flag. An interval // without valid messages is missing; a previous measurement is never reused. // The caller supplies the ticker and runs only one Run loop per sampler. func (s *Sampler) Run(ctx context.Context, ticks <-chan time.Time) { for { select { case <-ctx.Done(): return case _, ok := <-ticks: if !ok { return } s.mu.Lock() sample := status.Sample{} if s.received { sample = status.Sample{Power: s.power, Valid: true} } s.window.Add(sample) s.received = false s.mu.Unlock() } } } // Status returns an independent snapshot of the current window and its state. func (s *Sampler) Status() status.Result { s.mu.Lock() defer s.mu.Unlock() return status.Evaluate(s.window.Readings()) }