From 220cc818e7c3bef5dbf5c8038328532cb3cc25aa Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Sat, 15 Mar 2025 09:53:13 -0400 Subject: [PATCH] continue working on weather mapping --- main.go | 64 ++++++++++++++++++++++++----------- pkg/ambient/ambient.go | 12 +++---- pkg/ambient/config/config.go | 3 +- pkg/weather/grpc/mapupdate.go | 55 ++++++++++++++++++++---------- pkg/weather/grpc/weather.go | 1 - 5 files changed, 90 insertions(+), 45 deletions(-) diff --git a/main.go b/main.go index c85481f..84e5d60 100644 --- a/main.go +++ b/main.go @@ -9,36 +9,54 @@ import ( grpcopts "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/grpc/opts" httpopts "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/http/opts" "golang.org/x/sys/unix" + "k8s.io/utils/ptr" weatherpb "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/api/v1alpha1/weather" "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/ambient" "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/ambient/ambienthttp" "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/ambient/config" weathergrpc "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/weather/grpc" + "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/weather/state" ) -const defaultMetricPrefix = "weather" +const ( + defaultMetricPrefix = "weather" + defaultUpdatesToKeep = 120 +) func main() { ctx, cncl := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt, unix.SIGTERM) defer cncl() - // Config type for app, which implements go-app/config.AppConfig - awConfig := &config.AmbientLocalExporterConfig{ + // Read config and environment, prepare an appCtx, and merge + // go-app config into ambient weather exporter app config + ctx, awConfig := app.MustLoadConfigInto(ctx, &config.AmbientLocalExporterConfig{ MetricPrefix: defaultMetricPrefix, WeatherStations: make([]config.WeatherStation, 0), - } + }) - // Read config and environment, set up logging, load up - // an appCtx, and prepare ambient weather local exporter - ctx, awConfig = app.MustLoadConfigInto(ctx, awConfig) - - // Prepare the exporter + // Prepare the ambient exporter with our prepared config + // and set up logging, tracing, etc.. aw := ambient.New(ctx, awConfig).Init() - // Define and prepare the app - awApp := app.App{ + // Load http and grpc routes, prepare the app + awApp := prepareApp(ctx, aw) + + // Run app and wait + awApp.MustRun() + <-awApp.Done() +} + +func prepareApp(ctx context.Context, aw *ambient.AmbientWeather) *app.App { + // Config updates / defaults + if aw.Config.UpdatesToKeep == nil || *aw.Config.UpdatesToKeep < 1 { + aw.Config.UpdatesToKeep = ptr.To(defaultUpdatesToKeep) + } + + // Load ambient routes into app + awApp := &app.App{ AppContext: ctx, + // HTTP Endpoints for Ambient Weather Stations HTTP: &httpopts.AppHTTP{ Funcs: []httpopts.HTTPFunc{ @@ -51,8 +69,13 @@ func main() { HandlerFunc: aw.GetAWNHandlerFunc(ctx), }, }, + + // HTTP Listener that fixes broken requests generated by + // some versions of awn firmware CustomListener: ambienthttp.NewAWNMutatingListener(ctx, - awConfig.HTTP.Listen), // Necessary to fix certain bad AWN firmware + aw.Config.HTTP.Listen), // Necessary to fix certain bad AWN firmware + + // Health check funcs HealthChecks: []httpopts.HealthCheckFunc{ // TODO: Implement func(ctx context.Context) error { @@ -60,19 +83,22 @@ func main() { }, }, }, - // GRPC Service for retrieving current weather + + // GRPC Service for retrieving weather GRPC: &grpcopts.AppGRPC{ Services: []*grpcopts.GRPCService{ { - Name: "Weather Service", - Type: &weatherpb.AmbientLocalWeatherService_ServiceDesc, - Service: weathergrpc.GRPCWeather{}, + Name: "Weather Service", + Type: &weatherpb.AmbientLocalWeatherService_ServiceDesc, + Service: weathergrpc.NewGRPCWeather(ctx, state.NewWeatherState( + &state.Opts{ + Ctx: ctx, + KeepLast: *aw.Config.UpdatesToKeep, + })), }, }, }, } - // Run and wait - awApp.MustRun() - <-awApp.Done() + return awApp } diff --git a/pkg/ambient/ambient.go b/pkg/ambient/ambient.go index 1a31295..c4ddccb 100644 --- a/pkg/ambient/ambient.go +++ b/pkg/ambient/ambient.go @@ -27,7 +27,7 @@ type AmbientWeather struct { // when either "AmbientWeather" or "Wunderground" are selected // in the "Custom" section of the AWNet app, or the web UI // of an Ambient WeatherHub - config *config.AmbientLocalExporterConfig + Config *config.AmbientLocalExporterConfig awnProvider provider.AmbientProvider wuProvider provider.AmbientProvider appCtx context.Context @@ -37,7 +37,7 @@ type AmbientWeather struct { func New(appCtx context.Context, awConfig *config.AmbientLocalExporterConfig) *AmbientWeather { return &AmbientWeather{ - config: awConfig, + Config: awConfig, appCtx: appCtx, } } @@ -48,7 +48,7 @@ func (aw *AmbientWeather) Init() *AmbientWeather { aw.wuProvider = &wunderground.WUProvider{} aw.l = zerolog.Ctx(aw.appCtx) - aw.l.Trace().Any("awConfig", aw.config).Send() + aw.l.Trace().Any("awConfig", aw.Config).Send() return aw } @@ -227,15 +227,15 @@ func (aw *AmbientWeather) proxyUpdate( } func (aw *AmbientWeather) InitMetrics() { - if aw.config.MetricPrefix != "" { - weather.MetricPrefix = aw.config.MetricPrefix + if aw.Config.MetricPrefix != "" { + weather.MetricPrefix = aw.Config.MetricPrefix } aw.metrics = weather.MustInitMetrics(aw.appCtx) } func (aw *AmbientWeather) enrichStation(update *weather.WeatherUpdate) { if update != nil && update.StationID != nil && *update.StationID != "" { - for _, station := range aw.config.WeatherStations { + for _, station := range aw.Config.WeatherStations { if *update.StationID == station.AWNPassKey || *update.StationID == station.WundergroundID { update.StationConfig = &station } diff --git a/pkg/ambient/config/config.go b/pkg/ambient/config/config.go index f0bf6a9..1f9e697 100644 --- a/pkg/ambient/config/config.go +++ b/pkg/ambient/config/config.go @@ -7,7 +7,8 @@ import ( // This configuration includes all config from go-app/config.AppConfig type AmbientLocalExporterConfig struct { MetricPrefix string `yaml:"metricPrefix" default:"weather" env:"AMBIENT_METRIC_PREFIX"` - WeatherStations []WeatherStation `yaml:"weatherStations"` // No env, too complex, not worth the time + UpdatesToKeep *int `yaml:"updatesToKeep" default:"1" env:"AMBIENT_UPDATES_TO_KEEP"` + WeatherStations []WeatherStation `yaml:"weatherStations" env:"weatherStations"` // No env, too complex, not worth the time *config.AppConfig // Extends app config } diff --git a/pkg/weather/grpc/mapupdate.go b/pkg/weather/grpc/mapupdate.go index 7cab38f..3234355 100644 --- a/pkg/weather/grpc/mapupdate.go +++ b/pkg/weather/grpc/mapupdate.go @@ -25,25 +25,44 @@ func UpdateToPbUpdate(u *weather.WeatherUpdate) *pb.WeatherUpdate { TempIndoorF: u.TempIndoorF, HumidityOutdoor: ptr.To(int32(*u.HumidityOudoor)), HumidityIndoor: ptr.To(int32(*u.HumidityIndoor)), - WindSpeedMph: new(float64), - WindGustMph: new(float64), - MaxDailyGust: new(float64), - WindDir: new(int32), - WindDirAvg_10M: new(int32), - Uv: new(int32), - SolarRadiation: new(float64), - HourlyRainIn: new(float64), - EventRainIn: new(float64), - DailyRainIn: new(float64), - WeeklyRainIn: new(float64), - MonthlyRainIn: new(float64), - YearlyRainIn: new(float64), - TotalRainIn: new(float64), + WindSpeedMph: ptr.To(*u.WindSpeedMPH), + WindGustMph: ptr.To(*u.WindGustMPH), + MaxDailyGust: ptr.To(*u.MaxDailyGust), + WindDir: ptr.To(int32(*u.WindDir)), + WindDirAvg_10M: ptr.To(int32(*u.WindDirAvg10m)), + Uv: ptr.To(int32(*u.UV)), + SolarRadiation: ptr.To(*u.SolarRadiation), + HourlyRainIn: ptr.To(*u.HourlyRainIn), + EventRainIn: ptr.To(*u.EventRainIn), + DailyRainIn: ptr.To(*u.DailyRainIn), + WeeklyRainIn: ptr.To(*u.WeeklyRainIn), + MonthlyRainIn: ptr.To(*u.MonthlyRainIn), + YearlyRainIn: ptr.To(*u.YearlyRainIn), + TotalRainIn: ptr.To(*u.TotalRainIn), Batteries: []*pb.BatteryStatus{}, - BaromRelativeIn: new(float64), - BaromAbsoluteIn: new(float64), - DewPointF: new(float64), - WindChillF: new(float64), + BaromRelativeIn: ptr.To(*u.BaromRelativeIn), + BaromAbsoluteIn: ptr.To(*u.BaromAbsoluteIn), + DewPointF: ptr.To(*u.DewPointF), + WindChillF: ptr.To(*u.WindChillF), TempHumiditySensors: []*pb.TempHumiditySensor{}, } } + +func BatteriesToPbBatteries(batteries []weather.BatteryStatus) []*pb.BatteryStatus { + pbBatteries := make([]*pb.BatteryStatus, len(batteries)) + for i, b := range batteries { + pbBatteries[i] = &pb.BatteryStatus{ + Component: b.Component, + Status: ptr.To(int32(*b.Status)), + } + } + return pbBatteries +} + +func nilOrValPtr[T *int32 | *float64](v T) *T { + switch T.(type) { + case *int32: + return v.(*int32) + } + return nil +} diff --git a/pkg/weather/grpc/weather.go b/pkg/weather/grpc/weather.go index 202185f..c41928d 100644 --- a/pkg/weather/grpc/weather.go +++ b/pkg/weather/grpc/weather.go @@ -16,7 +16,6 @@ import ( "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/weather/state" ) -// TODO: Implement type GRPCWeather struct { ctx context.Context state *state.WeatherState