Compare commits

..

6 Commits

Author SHA1 Message Date
e93c7ec5c0 Update go-app
All checks were successful
Build and Publish / release (push) Successful in 6m10s
2025-01-12 20:22:56 -05:00
c2bde1e1dd Update TODO 2025-01-12 19:27:10 -05:00
44bf293eab Proxy observability updates
All checks were successful
Build and Publish / release (push) Successful in 2m51s
2025-01-12 19:14:24 -05:00
f87b35b306 Perform proxy updates in goroutines 2025-01-12 19:10:30 -05:00
eaf2cf211d Update wunderground url
All checks were successful
Build and Publish / release (push) Successful in 2m36s
2025-01-12 19:06:45 -05:00
e8fbacfe6d Proxy updates for AWN
All checks were successful
Build and Publish / release (push) Successful in 2m53s
2025-01-12 17:49:54 -05:00
8 changed files with 84 additions and 22 deletions

View File

@ -2,10 +2,12 @@
- [ ] Helm Chart - [ ] Helm Chart
- [ ] Update README - [ ] Update README
- [ ] Add Grafana dashboard - [ ] Add Grafana dashboard
- [ ] Add proxy to upstream support
- [ ] Add new spans - [ ] Add new spans
## Done ## Done
- [x] Add proxy to upstream support
- [x] Fix wunderground 401
- [x] Perform proxy calls in goroutines
- [x] Configuration for app - [x] Configuration for app
- [x] Configurable metric prefix - [x] Configurable metric prefix
- [x] Add device name field with ID/Key mappings - [x] Add device name field with ID/Key mappings

2
go.mod
View File

@ -3,7 +3,7 @@ module gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter
go 1.23.4 go 1.23.4
require ( require (
gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 gitea.libretechconsulting.com/rmcguire/go-app v0.3.2
github.com/go-resty/resty/v2 v2.16.3 github.com/go-resty/resty/v2 v2.16.3
github.com/gorilla/schema v1.4.1 github.com/gorilla/schema v1.4.1
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0

4
go.sum
View File

@ -1,5 +1,5 @@
gitea.libretechconsulting.com/rmcguire/go-app v0.3.1 h1:jydcJ+Vv8sk+Le7nTI2+b6E7FfV+ShBJo7YdxmdaCYc= gitea.libretechconsulting.com/rmcguire/go-app v0.3.2 h1:zAT6wmEEODYWx+iU3adlmFNW4FCTHzRvIepFIOQZGjk=
gitea.libretechconsulting.com/rmcguire/go-app v0.3.1/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38= gitea.libretechconsulting.com/rmcguire/go-app v0.3.2/go.mod h1:ug6g+FyEi2LguWTQfd+bZrTd1ECsot8BylxgMFEO5DM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=

View File

@ -7,6 +7,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" "gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -120,18 +121,40 @@ func (aw *AmbientWeather) handleProviderRequest(
// Uses a weather update to allow awn to publish to wunderground and // Uses a weather update to allow awn to publish to wunderground and
// visa versa. // visa versa.
if station := update.StationConfig; station != nil { if station := update.StationConfig; station != nil {
// Perform proxy updates in parallel if enabled
var proxyWg sync.WaitGroup
if station.ProxyToAWN { if station.ProxyToAWN {
err := aw.awnProvider.ProxyReq(ctx, update) proxyWg.Add(1)
if err != nil { go func() {
zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") defer proxyWg.Done()
} err := aw.awnProvider.ProxyReq(ctx, update)
if err != nil {
zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather")
return
}
zerolog.Ctx(aw.appCtx).Debug().
Str("station", station.Name).
Msg("proxied weather update to awn")
}()
} }
if station.ProxyToWunderground { if station.ProxyToWunderground {
err := aw.wuProvider.ProxyReq(ctx, update) proxyWg.Add(1)
if err != nil { go func() {
zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") defer proxyWg.Done()
} err := aw.wuProvider.ProxyReq(ctx, update)
if err != nil {
zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather")
return
}
zerolog.Ctx(aw.appCtx).Debug().
Str("station", station.Name).
Msg("proxied weather update to wunderground")
}()
} }
proxyWg.Wait()
} }
} }

View File

@ -18,6 +18,14 @@ const (
awnURL = "http://ambientweather.net/data/report" awnURL = "http://ambientweather.net/data/report"
) )
// Battery Sensors
const (
BattOutdoorSensor = "OutdoorSensor"
BattIndoorSensor = "IndoorSensor"
BattRainSensor = "RainSensor"
BattCO2Sensor = "CO2Sensor"
)
func (awn *AWNProvider) Name() string { func (awn *AWNProvider) Name() string {
return providerName return providerName
} }
@ -66,19 +74,19 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate {
TotalRainIn: awnUpdate.TotalRainIn, TotalRainIn: awnUpdate.TotalRainIn,
Batteries: []weather.BatteryStatus{ Batteries: []weather.BatteryStatus{
{ {
Component: "OutdoorSensor", Component: BattOutdoorSensor,
Status: awnUpdate.BattOut, Status: awnUpdate.BattOut,
}, },
{ {
Component: "IndoorSensor", Component: BattIndoorSensor,
Status: awnUpdate.BattIn, Status: awnUpdate.BattIn,
}, },
{ {
Component: "RainSensor", Component: BattRainSensor,
Status: awnUpdate.BattRain, Status: awnUpdate.BattRain,
}, },
{ {
Component: "CO2Sensor", Component: BattCO2Sensor,
Status: awnUpdate.BattCO2, Status: awnUpdate.BattCO2,
}, },
}, },

View File

@ -8,6 +8,7 @@ import (
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" "gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/rs/zerolog/log"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
@ -43,12 +44,18 @@ func (awn *AWNProvider) ProxyReq(ctx context.Context, update *weather.WeatherUpd
Get(awnURL) Get(awnURL)
if err != nil { if err != nil {
span.RecordError(err) span.RecordError(err)
span.SetAttributes(
attribute.String("query", resp.Request.QueryParam.Encode()),
attribute.String("body", string(resp.Body())),
)
span.SetStatus(codes.Error, err.Error()) span.SetStatus(codes.Error, err.Error())
log.Err(err).Any("query", resp.Request.PathParams).
Int("statusCode", resp.StatusCode()).
Msg("awn proxy failed")
} }
span.SetAttributes( span.SetAttributes(
attribute.Int("statusCode", resp.StatusCode()), attribute.Int("statusCode", resp.StatusCode()),
attribute.String("body", string(resp.Body())),
) )
return err return err
@ -79,5 +86,20 @@ func updateToAWNParams(update *weather.WeatherUpdate) *url.Values {
weather.SetURLVal(params, "windgustmph", update.WindGustMPH) weather.SetURLVal(params, "windgustmph", update.WindGustMPH)
weather.SetURLVal(params, "windspeedmph", update.WindSpeedMPH) weather.SetURLVal(params, "windspeedmph", update.WindSpeedMPH)
weather.SetURLVal(params, "maxdailygust", update.MaxDailyGust) weather.SetURLVal(params, "maxdailygust", update.MaxDailyGust)
// Batteries
for _, status := range update.Batteries {
switch status.Component {
case BattOutdoorSensor:
weather.SetURLVal(params, "battin", status.Status)
case BattIndoorSensor:
weather.SetURLVal(params, "battout", status.Status)
case BattRainSensor:
weather.SetURLVal(params, "battrain", status.Status)
case BattCO2Sensor:
weather.SetURLVal(params, "batt_co2", status.Status)
}
}
return params return params
} }

View File

@ -15,7 +15,7 @@ type WUProvider struct{}
const ( const (
providerName = "weatherunderground" providerName = "weatherunderground"
wuURL = "http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php" wuURL = "https://rtupdate.wunderground.com/weatherstation/updateweatherstation.php"
) )
func (wu *WUProvider) Name() string { func (wu *WUProvider) Name() string {

View File

@ -4,9 +4,9 @@ import (
"context" "context"
"errors" "errors"
"net/url" "net/url"
"time"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/rs/zerolog/log"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
@ -39,12 +39,19 @@ func (wu *WUProvider) ProxyReq(ctx context.Context, update *weather.WeatherUpdat
Get(wuURL) Get(wuURL)
if err != nil { if err != nil {
span.SetStatus(codes.Error, err.Error()) span.SetStatus(codes.Error, err.Error())
span.SetAttributes(
attribute.String("query", resp.Request.QueryParam.Encode()),
attribute.String("body", string(resp.Body())),
)
span.RecordError(err) span.RecordError(err)
log.Err(err).
Int("statusCode", resp.StatusCode()).
Any("query", resp.Request.PathParams).
Msg("wunderground proxy failed")
} }
span.SetAttributes( span.SetAttributes(
attribute.Int("statusCode", resp.StatusCode()), attribute.Int("statusCode", resp.StatusCode()),
attribute.String("body", string(resp.Body())),
) )
return err return err
@ -52,11 +59,11 @@ func (wu *WUProvider) ProxyReq(ctx context.Context, update *weather.WeatherUpdat
func updateToWuParams(u *weather.WeatherUpdate) *url.Values { func updateToWuParams(u *weather.WeatherUpdate) *url.Values {
params := &url.Values{} params := &url.Values{}
params.Set("dateutc", time.Now().Format(time.DateTime))
weather.SetURLVal(params, "ID", &u.StationConfig.WundergroundID) weather.SetURLVal(params, "ID", &u.StationConfig.WundergroundID)
weather.SetURLVal(params, "PASSWORD", &u.StationConfig.WundergroundPassword) weather.SetURLVal(params, "PASSWORD", &u.StationConfig.WundergroundPassword)
params.Set("action", "updateraw")
params.Set("dateutc", "now")
weather.SetURLVal(params, "UV", u.UV) weather.SetURLVal(params, "UV", u.UV)
weather.SetURLVal(params, "action", ptr.To("updateraw"))
weather.SetURLVal(params, "baromin", u.BaromRelativeIn) weather.SetURLVal(params, "baromin", u.BaromRelativeIn)
weather.SetURLVal(params, "dailyrainin", u.DailyRainIn) weather.SetURLVal(params, "dailyrainin", u.DailyRainIn)
weather.SetURLVal(params, "dewptf", u.DewPointF) weather.SetURLVal(params, "dewptf", u.DewPointF)