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
- 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.
119 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|