Nik Afiq 8f7024edfa
All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 24s
CI / build-ai-gateway (push) Successful in 1m6s
CI / build-ha-gateway (push) Successful in 1m3s
CI / build-discord-bot (push) Successful in 1m4s
CI / build-alexa-bridge (push) Successful in 1m15s
CI / build-tts-gateway (push) Successful in 1m5s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat(climate): add SetTemperature method to ClimateService
- Implemented SetTemperature in ClimateService for setting an absolute target temperature.
- Updated ClimateServiceClient and ClimateServiceServer interfaces to include SetTemperature.
- Added corresponding handler and tests for SetTemperature in ClimateGRPC.
- Modified ClimateApp to handle SetTemperature requests without clamping.
- Updated climate.proto to define SetTemperatureRequest message.
- Adjusted Dockerfiles to include alexa-bridge dependencies.
2026-07-25 12:08:24 +09:00

63 lines
1.7 KiB
Go

package entities
import (
"context"
"log/slog"
"time"
)
// Refresher fetches entities from ha-gateway on startup (blocking, fail
// loud — without an initial entity list nothing can resolve) and then
// periodically in the background (non-blocking; a failed periodic refresh
// only logs and keeps serving the last-known-good snapshot, since going
// down over a transient ha-gateway hiccup would be worse than serving
// slightly stale entity names).
type Refresher struct {
client *Client
resolver *Resolver
interval time.Duration
log *slog.Logger
}
// NewRefresher constructs a Refresher. client fetches entities, resolver is
// the snapshot it populates, interval controls the periodic refresh cadence.
func NewRefresher(client *Client, resolver *Resolver, interval time.Duration, log *slog.Logger) *Refresher {
return &Refresher{client: client, resolver: resolver, interval: interval, log: log}
}
// Start performs the blocking initial fetch, then launches a background
// goroutine for periodic refreshes until ctx is cancelled. It returns an
// error only from the initial fetch — the caller should fail startup on it.
func (r *Refresher) Start(ctx context.Context) error {
if err := r.refreshOnce(ctx); err != nil {
return err
}
go r.loop(ctx)
return nil
}
func (r *Refresher) refreshOnce(ctx context.Context) error {
list, err := r.client.FetchAll(ctx)
if err != nil {
return err
}
r.resolver.Replace(list)
return nil
}
func (r *Refresher) loop(ctx context.Context) {
ticker := time.NewTicker(r.interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := r.refreshOnce(ctx); err != nil {
r.log.Error("periodic entity refresh failed, keeping stale data", "err", err)
}
}
}
}