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

119 lines
3.5 KiB
Go

package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/joho/godotenv"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/alexa"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/config"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/directive"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/entities"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/haclient"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/logger"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/telemetry"
)
// entityDomains are the ha-gateway entity domains alexa-bridge resolves and
// controls. RemoteService/SwitchBot entities are deliberately excluded —
// see plan.md's "Decisions on open questions" #3.
var entityDomains = []string{"light", "switch", "climate"}
var version = "dev"
func main() {
_ = godotenv.Load()
cfg, err := config.Load()
if err != nil {
os.Stderr.WriteString("config error: " + err.Error() + "\n")
os.Exit(1)
}
log := logger.New(cfg.LogFormat, cfg.LogLevel)
slog.SetDefault(log)
log.Info("starting alexa-bridge",
"version", version,
"http_port", cfg.HTTPPort,
"ha_gateway_addr", cfg.HAGatewayAddr,
"tls_dir", cfg.TLSDir,
"entity_refresh_interval", cfg.EntityRefreshInterval.String(),
"otel_endpoint", cfg.OTELEndpoint,
)
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
ctx = logger.WithLogger(ctx, log)
shutdown, err := telemetry.Setup(ctx, "alexa-bridge", version, cfg)
if err != nil {
log.Error("telemetry setup failed", "err", err)
os.Exit(1)
}
haClient, err := haclient.New(ctx, cfg.HAGatewayAddr, cfg.TLSDir, cfg.HAGatewayServerName, log)
if err != nil {
log.Error("ha-gateway client setup failed", "err", err)
os.Exit(1)
}
defer func() {
if err := haClient.Close(); err != nil {
log.Error("ha-gateway client close failed", "err", err)
}
}()
entityClient := entities.NewClient(haClient.EntityServiceClient(), entityDomains)
resolver := entities.NewResolver()
refresher := entities.NewRefresher(entityClient, resolver, cfg.EntityRefreshInterval, log)
// Blocking: without an initial entity list nothing can resolve, so a
// failure here should fail startup rather than serve with an empty
// resolver. Periodic refreshes after this point only log on failure.
if err := refresher.Start(ctx); err != nil {
log.Error("initial entity fetch failed", "err", err)
os.Exit(1)
}
router := directive.NewRouter(resolver, haClient, log)
validator := alexa.NewSignatureValidator(nil)
alexaHandler := alexa.NewHandler(validator, cfg.AlexaSkillID, router)
mux := http.NewServeMux()
mux.Handle("/", alexaHandler)
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv := &http.Server{
Addr: ":" + cfg.HTTPPort,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
log.Info("alexa-bridge listening", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Error("serve failed", "err", err)
}
}()
<-ctx.Done()
log.Info("shutdown signal received, draining")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Error("http server shutdown failed", "err", err)
}
log.Info("shutdown complete")
if err := shutdown(context.Background()); err != nil {
log.Error("telemetry shutdown error", "err", err)
}
}