From 7fc1fc9b56c082d0ba2a30ca1127e1008c04ef21 Mon Sep 17 00:00:00 2001 From: Ryan D McGuire Date: Sun, 12 Jan 2025 15:30:37 -0500 Subject: [PATCH] Proxy support for AWN/Wunderground --- go.mod | 3 +- go.sum | 19 ++---- pkg/ambient/ambient.go | 20 ++++++ pkg/ambient/config.go | 9 +++ pkg/provider/awn/provider.go | 93 +++++++++++++++++++++++++-- pkg/provider/provider.go | 1 + pkg/provider/wunderground/provider.go | 71 ++++++++++++++------ pkg/weather/enrich.go | 34 +++++++++- pkg/weather/types.go | 17 +++++ 9 files changed, 228 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index d360c3e..5c354f8 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.4 require ( gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 + github.com/go-resty/resty/v2 v2.16.3 github.com/gorilla/schema v1.4.1 github.com/rs/zerolog v1.33.0 go.opentelemetry.io/otel v1.33.0 @@ -22,7 +23,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/prometheus/client_golang v1.20.5 // indirect diff --git a/go.sum b/go.sum index 86f94bc..8a6345b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -gitea.libretechconsulting.com/rmcguire/go-app v0.3.0 h1:TSR6oEDBX+83975gmgGgU/cTFgfG999+9N/1h4RAXq0= -gitea.libretechconsulting.com/rmcguire/go-app v0.3.0/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38= gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 h1:jydcJ+Vv8sk+Le7nTI2+b6E7FfV+ShBJo7YdxmdaCYc= gitea.libretechconsulting.com/rmcguire/go-app v0.3.1/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -20,6 +18,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E= +github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -39,8 +39,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -91,14 +92,10 @@ go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCt go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= -go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -108,18 +105,14 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d h1:H8tOf8XM88HvKqLTxe755haY6r1fqqzLbEnfrmLXlSA= -google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d/go.mod h1:2v7Z7gP2ZUOGsaFyxATQSRoBnKygqVq2Cwnvom7QiqY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/ambient/ambient.go b/pkg/ambient/ambient.go index 5bfeda3..a4fd114 100644 --- a/pkg/ambient/ambient.go +++ b/pkg/ambient/ambient.go @@ -114,6 +114,26 @@ func (aw *AmbientWeather) handleProviderRequest( Any("update", update). Msg("successfully handled update") w.Write([]byte("ok")) + + // Proxy update to one or both services if configured to do so + // Uses a weather update to allow awn to publish to wunderground and + // visa versa. + if station := aw.config.GetStation(update.GetStationName()); station != nil { + if station.ProxyToAWN { + err := aw.awnProvider.ProxyReq(ctx, update) + if err != nil { + zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") + } + } + if station.ProxyToWunderground { + err := aw.awnProvider.ProxyReq(ctx, update) + if err != nil { + zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") + } + } + } + + return } func (aw *AmbientWeather) InitMetrics() { diff --git a/pkg/ambient/config.go b/pkg/ambient/config.go index 631a15e..b706771 100644 --- a/pkg/ambient/config.go +++ b/pkg/ambient/config.go @@ -35,3 +35,12 @@ type WeatherStation struct { KeepMetrics []weather.WeatherUpdateField `yaml:"keepMetrics"` DropMetrics []weather.WeatherUpdateField `yaml:"dropMetrics"` } + +func (wc *AmbientLocalExporterConfig) GetStation(name string) *WeatherStation { + for _, station := range wc.WeatherStations { + if station.Name == name { + return &station + } + } + return nil +} diff --git a/pkg/provider/awn/provider.go b/pkg/provider/awn/provider.go index f9cc95d..80f90ec 100644 --- a/pkg/provider/awn/provider.go +++ b/pkg/provider/awn/provider.go @@ -2,18 +2,27 @@ package awn import ( "context" + "errors" "net/http" "net/url" "time" + "github.com/go-resty/resty/v2" "github.com/gorilla/schema" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + + "gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" "gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" ) type AWNProvider struct{} -const providerName = "awn" +const ( + providerName = "awn" + awnURL = "http://ambientweather.net/data/report" +) func (awn *AWNProvider) Name() string { return providerName @@ -32,6 +41,74 @@ func (awn *AWNProvider) ReqToWeather(_ context.Context, r *http.Request) ( return MapAwnUpdate(awnUpdate), nil } +// Attempts to proxy the weather station update to awn +// SAMPLE: +// {"PASSKEY":["ABF7E052BC7325A32300ACC89112AA91"],"baromabsin":["28.895"], +// "baromrelin":["29.876"],"battin":["1"],"battout":["1"],"battrain":["1"], +// "dailyrainin":["0.000"],"dateutc":["2025-01-11 22:07:57"],"eventrainin":["0.000"], +// "hourlyrainin":["0.000"],"humidity":["76"],"humidityin":["31"],"maxdailygust":["7.83"], +// "monthlyrainin":["0.000"],"solarradiation":["14.21"],"stationtype":["WeatherHub_V1.0.1"], +// "tempf":["29.48"],"tempinf":["66.20"],"totalrainin":["0.000"],"uv":["0"], +// "weeklyrainin":["0.000"],"winddir":["66"],"winddir_avg10m":["268"],"windgustmph":["2.68"], +// "windspeedmph":["0.00"],"yearlyrainin":["0.000"]} +func (awn *AWNProvider) ProxyReq(ctx context.Context, update *weather.WeatherUpdate) error { + tracer := otel.GetTracer(ctx, "awnProvider", "proxyReq") + ctx, span := tracer.Start(ctx, "proxyToAWN") + defer span.End() + + if update.WeatherServiceCredentials["PASSKEY"] == "" { + err := errors.New("no PASSKEY set in update") + span.RecordError(err) + return err + } + + params := updateToAWNParams(update) + + resp, err := resty.New().R(). + SetContext(ctx). + SetQueryParamsFromValues(*params). + Get(awnURL) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + span.SetAttributes( + attribute.Int("statusCode", resp.StatusCode()), + attribute.String("body", string(resp.Body())), + ) + + return err +} + +func updateToAWNParams(update *weather.WeatherUpdate) *url.Values { + params := &url.Values{} + params.Set("PASSKEY", update.WeatherServiceCredentials["PASSKEY"]) + params.Set("dateutc", time.Now().Format(time.DateTime)) + weather.SetURLVal(params, "baromabsin", update.BaromAbsoluteIn) + weather.SetURLVal(params, "baromrelin", update.BaromRelativeIn) + weather.SetURLVal(params, "dailyrainin", update.DailyRainIn) + weather.SetURLVal(params, "weeklyrainin", update.WeeklyRainIn) + weather.SetURLVal(params, "eventrainin", update.EventRainIn) + weather.SetURLVal(params, "hourlyrainin", update.HourlyRainIn) + weather.SetURLVal(params, "monthlyrainin", update.MonthlyRainIn) + weather.SetURLVal(params, "yearlyrainin", update.YearlyRainIn) + weather.SetURLVal(params, "totalrainin", update.TotalRainIn) + weather.SetURLVal(params, "humidity", update.HumidityOudoor) + weather.SetURLVal(params, "humidityin", update.HumidityIndoor) + weather.SetURLVal(params, "solarradiation", update.SolarRadiation) + weather.SetURLVal(params, "uv", update.UV) + weather.SetURLVal(params, "stationtype", update.StationType) + weather.SetURLVal(params, "tempf", update.TempOutdoorF) + weather.SetURLVal(params, "tempinf", update.TempIndoorF) + weather.SetURLVal(params, "winddir", update.WindDir) + weather.SetURLVal(params, "winddir_avg10m", update.WindDirAvg10m) + weather.SetURLVal(params, "windgustmph", update.WindGustMPH) + weather.SetURLVal(params, "windspeedmph", update.WindSpeedMPH) + weather.SetURLVal(params, "maxdailygust", update.MaxDailyGust) + return params +} + func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { updateTime := time.Now() if awnUpdate.DateUTC != nil { @@ -41,6 +118,11 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { } } + credentials := make(map[string]string) + if awnUpdate.PassKey != nil { + credentials["PASSKEY"] = *awnUpdate.PassKey + } + return &weather.WeatherUpdate{ DateUTC: &updateTime, StationID: awnUpdate.PassKey, @@ -79,10 +161,11 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { Status: awnUpdate.BattCO2, }, }, - TempIndoorF: awnUpdate.TempInF, - HumidityIndoor: awnUpdate.HumidityIn, - BaromRelativeIn: awnUpdate.BaromRelIn, - BaromAbsoluteIn: awnUpdate.BaromAbsIn, + TempIndoorF: awnUpdate.TempInF, + HumidityIndoor: awnUpdate.HumidityIn, + BaromRelativeIn: awnUpdate.BaromRelIn, + BaromAbsoluteIn: awnUpdate.BaromAbsIn, + WeatherServiceCredentials: credentials, } } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 3370705..f0eeb05 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -11,5 +11,6 @@ import ( // Ambient Weather Network HTTP requests to a stable struct type AmbientProvider interface { ReqToWeather(context.Context, *http.Request) (*weather.WeatherUpdate, error) + ProxyReq(context.Context, *weather.WeatherUpdate) error Name() string } diff --git a/pkg/provider/wunderground/provider.go b/pkg/provider/wunderground/provider.go index 6383b8c..837107b 100644 --- a/pkg/provider/wunderground/provider.go +++ b/pkg/provider/wunderground/provider.go @@ -13,7 +13,10 @@ import ( type WUProvider struct{} -const providerName = "weatherunderground" +const ( + providerName = "weatherunderground" + wuURL = "http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php" +) func (wu *WUProvider) Name() string { return providerName @@ -32,6 +35,29 @@ func (wu *WUProvider) ReqToWeather(_ context.Context, r *http.Request) ( return MapWUUpdate(wuUpdate), nil } +func (wu *WUProvider) ProxyReq(ctx context.Context, update *weather.WeatherUpdate) error { + // tracer := otel.GetTracer(ctx, "wuProvider", "proxyReq") + // ctx, span := tracer.Start(ctx, "proxyToWunderground") + // defer span.End() + // + // resp, err := resty.New().R(). + // SetContext(ctx). + // SetQueryParamsFromValues(r.URL.Query()). + // Get(wuURL) + // if err != nil { + // span.SetStatus(codes.Error, err.Error()) + // span.RecordError(err) + // } + // + // span.SetAttributes( + // attribute.Int("statusCode", resp.StatusCode()), + // attribute.String("body", string(resp.Body())), + // ) + // + // return err + return nil +} + func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate { updateTime := time.Now() @@ -42,25 +68,32 @@ func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate { } } + credentials := make(map[string]string) + if wuUpdate.ID != nil && wuUpdate.Password != nil { + credentials["ID"] = *wuUpdate.ID + credentials["Password"] = *wuUpdate.Password + } + return &weather.WeatherUpdate{ - DateUTC: &updateTime, - StationID: wuUpdate.ID, - StationType: wuUpdate.SoftwareType, - TempOutdoorF: wuUpdate.Tempf, - HumidityOudoor: wuUpdate.Humidity, - WindSpeedMPH: wuUpdate.WindGustMPH, - WindGustMPH: wuUpdate.WindGustMPH, - WindDir: wuUpdate.WindDir, - UV: wuUpdate.UV, - SolarRadiation: wuUpdate.SolarRadiation, - HourlyRainIn: wuUpdate.RainIn, - DailyRainIn: wuUpdate.DailyRainIn, - WeeklyRainIn: wuUpdate.WeeklyRainIn, - MonthlyRainIn: wuUpdate.MonthlyRainIn, - YearlyRainIn: wuUpdate.YearlyRainIn, - TempIndoorF: wuUpdate.IndoorTempF, - HumidityIndoor: wuUpdate.IndoorHumidity, - BaromRelativeIn: wuUpdate.BaromIn, + DateUTC: &updateTime, + StationID: wuUpdate.ID, + StationType: wuUpdate.SoftwareType, + TempOutdoorF: wuUpdate.Tempf, + HumidityOudoor: wuUpdate.Humidity, + WindSpeedMPH: wuUpdate.WindGustMPH, + WindGustMPH: wuUpdate.WindGustMPH, + WindDir: wuUpdate.WindDir, + UV: wuUpdate.UV, + SolarRadiation: wuUpdate.SolarRadiation, + HourlyRainIn: wuUpdate.RainIn, + DailyRainIn: wuUpdate.DailyRainIn, + WeeklyRainIn: wuUpdate.WeeklyRainIn, + MonthlyRainIn: wuUpdate.MonthlyRainIn, + YearlyRainIn: wuUpdate.YearlyRainIn, + TempIndoorF: wuUpdate.IndoorTempF, + HumidityIndoor: wuUpdate.IndoorHumidity, + BaromRelativeIn: wuUpdate.BaromIn, + WeatherServiceCredentials: credentials, } } diff --git a/pkg/weather/enrich.go b/pkg/weather/enrich.go index 566cb49..03ef95e 100644 --- a/pkg/weather/enrich.go +++ b/pkg/weather/enrich.go @@ -1,6 +1,10 @@ package weather -import "math" +import ( + "math" + "net/url" + "strconv" +) // Attempts to complete missing fields that may not // be set by a specific provider, such as DewPoint and WindChill @@ -53,3 +57,31 @@ func CalculateWindChill(tempF float64, windSpeedMPH float64) float64 { 35.75*math.Pow(windSpeedMPH, 0.16) + 0.4275*tempF*math.Pow(windSpeedMPH, 0.16) } + +// Helper function to set values from fields +// typically from a WeatherUpdate +func SetURLVal(vals *url.Values, key string, value any) { + if value == nil { + return + } + + switch v := value.(type) { + case *float64: + if v == nil { + return + } + str := strconv.FormatFloat(*v, 'f', 4, 64) + vals.Set(key, str) + case *int: + if v == nil { + return + } + str := strconv.FormatInt(int64(*v), 10) + vals.Set(key, str) + case *string: + if v == nil { + return + } + vals.Set(key, *v) + } +} diff --git a/pkg/weather/types.go b/pkg/weather/types.go index f5eef00..dc4cc9c 100644 --- a/pkg/weather/types.go +++ b/pkg/weather/types.go @@ -36,6 +36,9 @@ type WeatherUpdate struct { // if not otherwise set DewPointF *float64 WindChillF *float64 + // First URL parameters given to AWN/Wunderground + // if proxying is enabled + WeatherServiceCredentials map[string]string } type StationInfo struct { @@ -82,3 +85,17 @@ const ( FieldDewPointF WeatherUpdateField = "DewPointF" FieldWindChillF WeatherUpdateField = "WindChillF" ) + +func (u *WeatherUpdate) GetStationName() string { + if u.StationInfo != nil { + return u.StationInfo.GetName() + } + return "" +} + +func (si *StationInfo) GetName() string { + if si.Name != nil { + return *si.Name + } + return "" +}