Weather service proxy support
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build and Publish / release (push) Successful in 3m51s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build and Publish / release (push) Successful in 3m51s
				
			This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -10,6 +10,7 @@ require ( | |||||||
| 	go.opentelemetry.io/otel v1.33.0 | 	go.opentelemetry.io/otel v1.33.0 | ||||||
| 	go.opentelemetry.io/otel/metric v1.33.0 | 	go.opentelemetry.io/otel/metric v1.33.0 | ||||||
| 	golang.org/x/sys v0.29.0 | 	golang.org/x/sys v0.29.0 | ||||||
|  | 	k8s.io/utils v0.0.0-20241210054802-24370beab758 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -120,3 +120,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN | |||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= | ||||||
|  | k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								main.go
									
									
									
									
									
								
							| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"golang.org/x/sys/unix" | 	"golang.org/x/sys/unix" | ||||||
|  |  | ||||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient" | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient" | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient/config" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const defaultMetricPrefix = "weather" | const defaultMetricPrefix = "weather" | ||||||
| @@ -19,9 +20,9 @@ func main() { | |||||||
| 	defer cncl() | 	defer cncl() | ||||||
|  |  | ||||||
| 	// Config type for app, which implements go-app/config.AppConfig | 	// Config type for app, which implements go-app/config.AppConfig | ||||||
| 	awConfig := &ambient.AmbientLocalExporterConfig{ | 	awConfig := &config.AmbientLocalExporterConfig{ | ||||||
| 		MetricPrefix:    defaultMetricPrefix, | 		MetricPrefix:    defaultMetricPrefix, | ||||||
| 		WeatherStations: make([]ambient.WeatherStation, 0), | 		WeatherStations: make([]config.WeatherStation, 0), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Read config and environment, set up logging, load up | 	// Read config and environment, set up logging, load up | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import ( | |||||||
| 	"go.opentelemetry.io/otel/attribute" | 	"go.opentelemetry.io/otel/attribute" | ||||||
| 	"go.opentelemetry.io/otel/codes" | 	"go.opentelemetry.io/otel/codes" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient/config" | ||||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/provider" | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/provider" | ||||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/provider/awn" | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/provider/awn" | ||||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/provider/wunderground" | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/provider/wunderground" | ||||||
| @@ -24,7 +25,7 @@ type AmbientWeather struct { | |||||||
| 	// when either "AmbientWeather" or "Wunderground" are selected | 	// when either "AmbientWeather" or "Wunderground" are selected | ||||||
| 	// in the "Custom" section of the AWNet app, or the web UI | 	// in the "Custom" section of the AWNet app, or the web UI | ||||||
| 	// of an Ambient WeatherHub | 	// of an Ambient WeatherHub | ||||||
| 	config      *AmbientLocalExporterConfig | 	config      *config.AmbientLocalExporterConfig | ||||||
| 	awnProvider provider.AmbientProvider | 	awnProvider provider.AmbientProvider | ||||||
| 	wuProvider  provider.AmbientProvider | 	wuProvider  provider.AmbientProvider | ||||||
| 	appCtx      context.Context | 	appCtx      context.Context | ||||||
| @@ -32,7 +33,7 @@ type AmbientWeather struct { | |||||||
| 	l           *zerolog.Logger | 	l           *zerolog.Logger | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(appCtx context.Context, awConfig *AmbientLocalExporterConfig) *AmbientWeather { | func New(appCtx context.Context, awConfig *config.AmbientLocalExporterConfig) *AmbientWeather { | ||||||
| 	return &AmbientWeather{ | 	return &AmbientWeather{ | ||||||
| 		config: awConfig, | 		config: awConfig, | ||||||
| 		appCtx: appCtx, | 		appCtx: appCtx, | ||||||
| @@ -118,7 +119,7 @@ func (aw *AmbientWeather) handleProviderRequest( | |||||||
| 	// Proxy update to one or both services if configured to do so | 	// Proxy update to one or both services if configured to do so | ||||||
| 	// 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 := aw.config.GetStation(update.GetStationName()); station != nil { | 	if station := update.StationConfig; station != nil { | ||||||
| 		if station.ProxyToAWN { | 		if station.ProxyToAWN { | ||||||
| 			err := aw.awnProvider.ProxyReq(ctx, update) | 			err := aw.awnProvider.ProxyReq(ctx, update) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -126,14 +127,12 @@ func (aw *AmbientWeather) handleProviderRequest( | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if station.ProxyToWunderground { | 		if station.ProxyToWunderground { | ||||||
| 			err := aw.awnProvider.ProxyReq(ctx, update) | 			err := aw.wuProvider.ProxyReq(ctx, update) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") | 				zerolog.Ctx(aw.appCtx).Err(err).Msg("failed to proxy to ambient weather") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (aw *AmbientWeather) InitMetrics() { | func (aw *AmbientWeather) InitMetrics() { | ||||||
| @@ -147,13 +146,7 @@ func (aw *AmbientWeather) enrichStation(update *weather.WeatherUpdate) { | |||||||
| 	if update != nil && update.StationID != nil && *update.StationID != "" { | 	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 { | 			if *update.StationID == station.AWNPassKey || *update.StationID == station.WundergroundID { | ||||||
| 				update.StationInfo = &weather.StationInfo{ | 				update.StationConfig = &station | ||||||
| 					Type:      update.StationType, |  | ||||||
| 					Equipment: &station.Equipment, |  | ||||||
| 					Name:      &station.Name, |  | ||||||
| 					Keep:      station.KeepMetrics, |  | ||||||
| 					Drop:      station.DropMetrics, |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| package ambient | package config | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config" | 	"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config" | ||||||
| 
 |  | ||||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // This configuration includes all config from go-app/config.AppConfig | // This configuration includes all config from go-app/config.AppConfig | ||||||
| @@ -17,10 +15,9 @@ type WeatherStation struct { | |||||||
| 	Name      string `yaml:"name"`      // Human Friendly Name (e.g. Back Yard Weather) | 	Name      string `yaml:"name"`      // Human Friendly Name (e.g. Back Yard Weather) | ||||||
| 	Equipment string `yaml:"equipment"` // Equipment Type (e.g. WS-5000) | 	Equipment string `yaml:"equipment"` // Equipment Type (e.g. WS-5000) | ||||||
| 
 | 
 | ||||||
| 	// One of these is required, based on the type | 	// Required if proxying to awn/wu is enabled | ||||||
| 	// set in the "Custom" weather service on your |  | ||||||
| 	// console, weather station, or weather hub |  | ||||||
| 	WundergroundID       string `yaml:"wundergroundID"` | 	WundergroundID       string `yaml:"wundergroundID"` | ||||||
|  | 	WundergroundPassword string `yaml:"wundergroundPassword"` | ||||||
| 	AWNPassKey           string `yaml:"awnPassKey"` | 	AWNPassKey           string `yaml:"awnPassKey"` | ||||||
| 
 | 
 | ||||||
| 	// Proxy updates to AWN or Wunderground | 	// Proxy updates to AWN or Wunderground | ||||||
| @@ -31,16 +28,9 @@ type WeatherStation struct { | |||||||
| 	// will be excluded if present in discardMetrics | 	// will be excluded if present in discardMetrics | ||||||
| 	// | 	// | ||||||
| 	// If anything is present in keepMetrics, it is solely applied, | 	// If anything is present in keepMetrics, it is solely applied, | ||||||
| 	// ignoring discardMetrics | 	// ignoring discardMetrics. | ||||||
| 	KeepMetrics []weather.WeatherUpdateField `yaml:"keepMetrics"` | 	// | ||||||
| 	DropMetrics []weather.WeatherUpdateField `yaml:"dropMetrics"` | 	// Check weather.WeatherUpdateField for options | ||||||
| } | 	KeepMetrics []string `yaml:"keepMetrics"` | ||||||
| 
 | 	DropMetrics []string `yaml:"dropMetrics"` | ||||||
| func (wc *AmbientLocalExporterConfig) GetStation(name string) *WeatherStation { |  | ||||||
| 	for _, station := range wc.WeatherStations { |  | ||||||
| 		if station.Name == name { |  | ||||||
| 			return &station |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
| @@ -2,17 +2,11 @@ package awn | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/go-resty/resty/v2" |  | ||||||
| 	"github.com/gorilla/schema" | 	"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" | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" | ||||||
| ) | ) | ||||||
| @@ -41,74 +35,6 @@ func (awn *AWNProvider) ReqToWeather(_ context.Context, r *http.Request) ( | |||||||
| 	return MapAwnUpdate(awnUpdate), nil | 	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 { | func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { | ||||||
| 	updateTime := time.Now() | 	updateTime := time.Now() | ||||||
| 	if awnUpdate.DateUTC != nil { | 	if awnUpdate.DateUTC != nil { | ||||||
| @@ -118,11 +44,6 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	credentials := make(map[string]string) |  | ||||||
| 	if awnUpdate.PassKey != nil { |  | ||||||
| 		credentials["PASSKEY"] = *awnUpdate.PassKey |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &weather.WeatherUpdate{ | 	return &weather.WeatherUpdate{ | ||||||
| 		DateUTC:        &updateTime, | 		DateUTC:        &updateTime, | ||||||
| 		StationID:      awnUpdate.PassKey, | 		StationID:      awnUpdate.PassKey, | ||||||
| @@ -165,7 +86,6 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { | |||||||
| 		HumidityIndoor:  awnUpdate.HumidityIn, | 		HumidityIndoor:  awnUpdate.HumidityIn, | ||||||
| 		BaromRelativeIn: awnUpdate.BaromRelIn, | 		BaromRelativeIn: awnUpdate.BaromRelIn, | ||||||
| 		BaromAbsoluteIn: awnUpdate.BaromAbsIn, | 		BaromAbsoluteIn: awnUpdate.BaromAbsIn, | ||||||
| 		WeatherServiceCredentials: credentials, |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										83
									
								
								pkg/provider/awn/proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								pkg/provider/awn/proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | package awn | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"net/url" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" | ||||||
|  | 	"github.com/go-resty/resty/v2" | ||||||
|  | 	"go.opentelemetry.io/otel/attribute" | ||||||
|  | 	"go.opentelemetry.io/otel/codes" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // 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.StationConfig.AWNPassKey == "" { | ||||||
|  | 		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("dateutc", time.Now().Format(time.DateTime)) | ||||||
|  | 	weather.SetURLVal(params, "PASSKEY", &update.StationConfig.AWNPassKey) | ||||||
|  | 	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 | ||||||
|  | } | ||||||
| @@ -35,29 +35,6 @@ func (wu *WUProvider) ReqToWeather(_ context.Context, r *http.Request) ( | |||||||
| 	return MapWUUpdate(wuUpdate), nil | 	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 { | func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate { | ||||||
| 	updateTime := time.Now() | 	updateTime := time.Now() | ||||||
|  |  | ||||||
| @@ -68,12 +45,6 @@ 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{ | 	return &weather.WeatherUpdate{ | ||||||
| 		DateUTC:         &updateTime, | 		DateUTC:         &updateTime, | ||||||
| 		StationID:       wuUpdate.ID, | 		StationID:       wuUpdate.ID, | ||||||
| @@ -93,7 +64,6 @@ func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate { | |||||||
| 		TempIndoorF:     wuUpdate.IndoorTempF, | 		TempIndoorF:     wuUpdate.IndoorTempF, | ||||||
| 		HumidityIndoor:  wuUpdate.IndoorHumidity, | 		HumidityIndoor:  wuUpdate.IndoorHumidity, | ||||||
| 		BaromRelativeIn: wuUpdate.BaromIn, | 		BaromRelativeIn: wuUpdate.BaromIn, | ||||||
| 		WeatherServiceCredentials: credentials, |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										81
									
								
								pkg/provider/wunderground/proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								pkg/provider/wunderground/proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | package wunderground | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"net/url" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-resty/resty/v2" | ||||||
|  | 	"go.opentelemetry.io/otel/attribute" | ||||||
|  | 	"go.opentelemetry.io/otel/codes" | ||||||
|  | 	"k8s.io/utils/ptr" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | 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() | ||||||
|  |  | ||||||
|  | 	if update.StationConfig.WundergroundID == "" { | ||||||
|  | 		err := errors.New("no wunderground id set in update") | ||||||
|  | 		span.RecordError(err) | ||||||
|  | 		return err | ||||||
|  | 	} else if update.StationConfig.WundergroundPassword == "" { | ||||||
|  | 		err := errors.New("no wunderground id set in update") | ||||||
|  | 		span.RecordError(err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params := updateToWuParams(update) | ||||||
|  |  | ||||||
|  | 	resp, err := resty.New().R(). | ||||||
|  | 		SetContext(ctx). | ||||||
|  | 		SetQueryParamsFromValues(*params). | ||||||
|  | 		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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func updateToWuParams(u *weather.WeatherUpdate) *url.Values { | ||||||
|  | 	params := &url.Values{} | ||||||
|  | 	params.Set("dateutc", time.Now().Format(time.DateTime)) | ||||||
|  | 	weather.SetURLVal(params, "ID", &u.StationConfig.WundergroundID) | ||||||
|  | 	weather.SetURLVal(params, "PASSWORD", &u.StationConfig.WundergroundPassword) | ||||||
|  | 	weather.SetURLVal(params, "UV", u.UV) | ||||||
|  | 	weather.SetURLVal(params, "action", ptr.To("updateraw")) | ||||||
|  | 	weather.SetURLVal(params, "baromin", u.BaromRelativeIn) | ||||||
|  | 	weather.SetURLVal(params, "dailyrainin", u.DailyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "dewptf", u.DewPointF) | ||||||
|  | 	weather.SetURLVal(params, "humidity", u.HumidityOudoor) | ||||||
|  | 	weather.SetURLVal(params, "indoorhumidity", u.HumidityIndoor) | ||||||
|  | 	weather.SetURLVal(params, "indoortempf", u.TempIndoorF) | ||||||
|  | 	weather.SetURLVal(params, "lowbatt", ptr.To(0)) | ||||||
|  | 	weather.SetURLVal(params, "monthlyrainin", u.MonthlyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "rainin", u.HourlyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "realtime", ptr.To(1)) | ||||||
|  | 	weather.SetURLVal(params, "rtfreq", ptr.To(60)) | ||||||
|  | 	weather.SetURLVal(params, "softwaretype", u.StationType) | ||||||
|  | 	weather.SetURLVal(params, "solarradiation", u.SolarRadiation) | ||||||
|  | 	weather.SetURLVal(params, "tempf", u.TempOutdoorF) | ||||||
|  | 	weather.SetURLVal(params, "weeklyrainin", u.WeeklyRainIn) | ||||||
|  | 	weather.SetURLVal(params, "windchillf", u.WindChillF) | ||||||
|  | 	weather.SetURLVal(params, "winddir", u.WindDir) | ||||||
|  | 	weather.SetURLVal(params, "windgustmph", u.WindGustMPH) | ||||||
|  | 	weather.SetURLVal(params, "windspeedmph", u.WindSpeedMPH) | ||||||
|  | 	weather.SetURLVal(params, "yearlyrainin", u.YearlyRainIn) | ||||||
|  | 	return params | ||||||
|  | } | ||||||
| @@ -4,11 +4,13 @@ import ( | |||||||
| 	"math" | 	"math" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient/config" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Attempts to complete missing fields that may not | // Attempts to complete missing fields that may not | ||||||
| // be set by a specific provider, such as DewPoint and WindChill | // be set by a specific provider, such as DewPoint and WindChill | ||||||
| func (u *WeatherUpdate) Enrich() { | func (u *WeatherUpdate) Enrich(weatherStations ...*config.WeatherStation) { | ||||||
| 	if u == nil { | 	if u == nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -119,46 +119,46 @@ func (wm *WeatherMetrics) Update(u *WeatherUpdate) { | |||||||
| 		attributes = append(attributes, | 		attributes = append(attributes, | ||||||
| 			attribute.String("station_type", *u.StationType)) | 			attribute.String("station_type", *u.StationType)) | ||||||
| 	} | 	} | ||||||
| 	if u.StationInfo != nil { | 	if u.StationConfig != nil { | ||||||
| 		if u.StationInfo.Name != nil { | 		if u.StationConfig.Name != "" { | ||||||
| 			attributes = append(attributes, | 			attributes = append(attributes, | ||||||
| 				attribute.String("station_name", *u.StationInfo.Name)) | 				attribute.String("station_name", u.StationConfig.Name)) | ||||||
| 		} | 		} | ||||||
| 		if u.StationInfo.Equipment != nil { | 		if u.StationConfig.Equipment != "" { | ||||||
| 			attributes = append(attributes, | 			attributes = append(attributes, | ||||||
| 				attribute.String("station_equipment", *u.StationInfo.Equipment)) | 				attribute.String("station_equipment", u.StationConfig.Equipment)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TempOutdoorF, FloatVal: u.TempOutdoorF, Field: FieldTempOutdoorF, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TempOutdoorF, FloatVal: u.TempOutdoorF, Field: FieldTempOutdoorF, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TempIndoorF, FloatVal: u.TempIndoorF, Field: FieldTempIndoorF, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TempIndoorF, FloatVal: u.TempIndoorF, Field: FieldTempIndoorF, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.HumidityOudoor, IntVal: u.HumidityOudoor, Field: FieldHumidityOudoor, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.HumidityOudoor, IntVal: u.HumidityOudoor, Field: FieldHumidityOudoor, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.HumidityIndoor, IntVal: u.HumidityIndoor, Field: FieldHumidityIndoor, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.HumidityIndoor, IntVal: u.HumidityIndoor, Field: FieldHumidityIndoor, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindSpeedMPH, FloatVal: u.WindSpeedMPH, Field: FieldWindSpeedMPH, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindSpeedMPH, FloatVal: u.WindSpeedMPH, Field: FieldWindSpeedMPH, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindGustMPH, FloatVal: u.WindGustMPH, Field: FieldWindGustMPH, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindGustMPH, FloatVal: u.WindGustMPH, Field: FieldWindGustMPH, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.MaxDailyGust, FloatVal: u.MaxDailyGust, Field: FieldMaxDailyGust, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.MaxDailyGust, FloatVal: u.MaxDailyGust, Field: FieldMaxDailyGust, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.WindDir, IntVal: u.WindDir, Field: FieldWindDir, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.WindDir, IntVal: u.WindDir, Field: FieldWindDir, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.WindDirAvg10m, IntVal: u.WindDirAvg10m, Field: FieldWindDirAvg10m, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.WindDirAvg10m, IntVal: u.WindDirAvg10m, Field: FieldWindDirAvg10m, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.UV, IntVal: u.UV, Field: FieldUV, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Int64Gauge: wm.UV, IntVal: u.UV, Field: FieldUV, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.SolarRadiation, FloatVal: u.SolarRadiation, Field: FieldSolarRadiation, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.SolarRadiation, FloatVal: u.SolarRadiation, Field: FieldSolarRadiation, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.HourlyRainIn, FloatVal: u.HourlyRainIn, Field: FieldHourlyRainIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.HourlyRainIn, FloatVal: u.HourlyRainIn, Field: FieldHourlyRainIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.EventRainIn, FloatVal: u.EventRainIn, Field: FieldEventRainIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.EventRainIn, FloatVal: u.EventRainIn, Field: FieldEventRainIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.DailyRainIn, FloatVal: u.DailyRainIn, Field: FieldDailyRainIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.DailyRainIn, FloatVal: u.DailyRainIn, Field: FieldDailyRainIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WeeklyRainIn, FloatVal: u.WeeklyRainIn, Field: FieldWeeklyRainIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WeeklyRainIn, FloatVal: u.WeeklyRainIn, Field: FieldWeeklyRainIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.MonthlyRainIn, FloatVal: u.MonthlyRainIn, Field: FieldMonthlyRainIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.MonthlyRainIn, FloatVal: u.MonthlyRainIn, Field: FieldMonthlyRainIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.YearlyRainIn, FloatVal: u.YearlyRainIn, Field: FieldYearlyRainIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.YearlyRainIn, FloatVal: u.YearlyRainIn, Field: FieldYearlyRainIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TotalRainIn, FloatVal: u.TotalRainIn, Field: FieldTotalRainIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TotalRainIn, FloatVal: u.TotalRainIn, Field: FieldTotalRainIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.BaromRelativeIn, FloatVal: u.BaromRelativeIn, Field: FieldBaromRelativeIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.BaromRelativeIn, FloatVal: u.BaromRelativeIn, Field: FieldBaromRelativeIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.BaromAbsoluteIn, FloatVal: u.BaromAbsoluteIn, Field: FieldBaromAbsoluteIn, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.BaromAbsoluteIn, FloatVal: u.BaromAbsoluteIn, Field: FieldBaromAbsoluteIn, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.DewPointF, FloatVal: u.DewPointF, Field: FieldDewPointF, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.DewPointF, FloatVal: u.DewPointF, Field: FieldDewPointF, Attributes: attributes, Station: u.StationConfig}) | ||||||
| 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindChillF, FloatVal: u.WindChillF, Field: FieldWindChillF, Attributes: attributes, StationInfo: u.StationInfo}) | 	wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindChillF, FloatVal: u.WindChillF, Field: FieldWindChillF, Attributes: attributes, Station: u.StationConfig}) | ||||||
|  |  | ||||||
| 	// Batteries | 	// Batteries | ||||||
| 	for _, battery := range u.Batteries { | 	for _, battery := range u.Batteries { | ||||||
| 		batAttr := attributes | 		batAttr := attributes | ||||||
| 		batAttr = append(batAttr, attribute.String("component", battery.Component)) | 		batAttr = append(batAttr, attribute.String("component", battery.Component)) | ||||||
|  |  | ||||||
| 		wm.recorder.Record(&RecordOpts{Int64Gauge: wm.BatteryStatus, IntVal: battery.Status, Field: FieldBatteries, Attributes: batAttr, StationInfo: u.StationInfo}) | 		wm.recorder.Record(&RecordOpts{Int64Gauge: wm.BatteryStatus, IntVal: battery.Status, Field: FieldBatteries, Attributes: batAttr, Station: u.StationConfig}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	wm.UpdatesReceived.Add(wm.appCtx, 1) | 	wm.UpdatesReceived.Add(wm.appCtx, 1) | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ import ( | |||||||
| 	"github.com/rs/zerolog" | 	"github.com/rs/zerolog" | ||||||
| 	"go.opentelemetry.io/otel/attribute" | 	"go.opentelemetry.io/otel/attribute" | ||||||
| 	"go.opentelemetry.io/otel/metric" | 	"go.opentelemetry.io/otel/metric" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient/config" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type MetricRecorder struct { | type MetricRecorder struct { | ||||||
| @@ -20,15 +22,15 @@ type RecordOpts struct { | |||||||
| 	IntVal       *int | 	IntVal       *int | ||||||
| 	FloatVal     *float64 | 	FloatVal     *float64 | ||||||
| 	Attributes   []attribute.KeyValue | 	Attributes   []attribute.KeyValue | ||||||
| 	Field        WeatherUpdateField | 	Field        string | ||||||
| 	StationInfo  *StationInfo | 	Station      *config.WeatherStation | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *MetricRecorder) Record(opts *RecordOpts) { | func (r *MetricRecorder) Record(opts *RecordOpts) { | ||||||
| 	if opts.StationInfo != nil && !opts.keep() { | 	if opts.Station != nil && !opts.keep() { | ||||||
| 		r.l.Trace(). | 		r.l.Trace(). | ||||||
| 			Str("field", string(opts.Field)). | 			Str("field", string(opts.Field)). | ||||||
| 			Str("station", *opts.StationInfo.Name). | 			Str("station", opts.Station.Name). | ||||||
| 			Msg("Metric dropped by station config") | 			Msg("Metric dropped by station config") | ||||||
| 		return | 		return | ||||||
| 	} else if opts.Int64Gauge == nil && opts.Float64Gauge == nil { | 	} else if opts.Int64Gauge == nil && opts.Float64Gauge == nil { | ||||||
| @@ -39,8 +41,8 @@ func (r *MetricRecorder) Record(opts *RecordOpts) { | |||||||
| 	if opts.Int64Gauge != nil { | 	if opts.Int64Gauge != nil { | ||||||
| 		if opts.IntVal == nil { | 		if opts.IntVal == nil { | ||||||
| 			log := r.l.Trace().Str("field", string(opts.Field)) | 			log := r.l.Trace().Str("field", string(opts.Field)) | ||||||
| 			if opts.StationInfo != nil { | 			if opts.Station != nil { | ||||||
| 				log = log.Str("station", *opts.StationInfo.Name) | 				log = log.Str("station", opts.Station.Name) | ||||||
| 			} | 			} | ||||||
| 			log.Msg("Dropping nil int metric") | 			log.Msg("Dropping nil int metric") | ||||||
| 			return | 			return | ||||||
| @@ -49,8 +51,8 @@ func (r *MetricRecorder) Record(opts *RecordOpts) { | |||||||
| 	} else if opts.Float64Gauge != nil { | 	} else if opts.Float64Gauge != nil { | ||||||
| 		if opts.FloatVal == nil { | 		if opts.FloatVal == nil { | ||||||
| 			log := r.l.Trace().Str("field", string(opts.Field)) | 			log := r.l.Trace().Str("field", string(opts.Field)) | ||||||
| 			if opts.StationInfo != nil { | 			if opts.Station != nil { | ||||||
| 				log = log.Str("station", *opts.StationInfo.Name) | 				log = log.Str("station", opts.Station.Name) | ||||||
| 			} | 			} | ||||||
| 			log.Msg("Dropping nil float metric") | 			log.Msg("Dropping nil float metric") | ||||||
| 			return | 			return | ||||||
| @@ -61,8 +63,8 @@ func (r *MetricRecorder) Record(opts *RecordOpts) { | |||||||
|  |  | ||||||
| func (o *RecordOpts) keep() bool { | func (o *RecordOpts) keep() bool { | ||||||
| 	// If keep fields are given, only check keep fields | 	// If keep fields are given, only check keep fields | ||||||
| 	if len(o.StationInfo.Keep) > 0 { | 	if len(o.Station.KeepMetrics) > 0 { | ||||||
| 		for _, f := range o.StationInfo.Keep { | 		for _, f := range o.Station.KeepMetrics { | ||||||
| 			if f == o.Field { | 			if f == o.Field { | ||||||
| 				return true | 				return true | ||||||
| 			} | 			} | ||||||
| @@ -70,7 +72,7 @@ func (o *RecordOpts) keep() bool { | |||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, f := range o.StationInfo.Drop { | 	for _, f := range o.Station.DropMetrics { | ||||||
| 		if f == o.Field { | 		if f == o.Field { | ||||||
| 			return false | 			return false | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -2,13 +2,15 @@ package weather | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/ambient/config" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Stable intermediate struct containing superset of fields | // Stable intermediate struct containing superset of fields | ||||||
| // between AWN and Wunderground style updates from Ambient devices | // between AWN and Wunderground style updates from Ambient devices | ||||||
| type WeatherUpdate struct { | type WeatherUpdate struct { | ||||||
| 	DateUTC         *time.Time | 	DateUTC         *time.Time | ||||||
| 	StationInfo     *StationInfo | 	StationConfig   *config.WeatherStation | ||||||
| 	StationID       *string | 	StationID       *string | ||||||
| 	StationType     *string | 	StationType     *string | ||||||
| 	TempOutdoorF    *float64 | 	TempOutdoorF    *float64 | ||||||
| @@ -38,15 +40,6 @@ type WeatherUpdate struct { | |||||||
| 	WindChillF *float64 | 	WindChillF *float64 | ||||||
| 	// First URL parameters given to AWN/Wunderground | 	// First URL parameters given to AWN/Wunderground | ||||||
| 	// if proxying is enabled | 	// if proxying is enabled | ||||||
| 	WeatherServiceCredentials map[string]string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type StationInfo struct { |  | ||||||
| 	Type      *string |  | ||||||
| 	Equipment *string |  | ||||||
| 	Name      *string |  | ||||||
| 	Keep      []WeatherUpdateField |  | ||||||
| 	Drop      []WeatherUpdateField |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type BatteryStatus struct { | type BatteryStatus struct { | ||||||
| @@ -54,48 +47,40 @@ type BatteryStatus struct { | |||||||
| 	Status    *int | 	Status    *int | ||||||
| } | } | ||||||
|  |  | ||||||
| type WeatherUpdateField string | // CHORE: Maintain this, used to check against | ||||||
|  | // keep and drop lists | ||||||
| // NOTE: Annoyance to avoid string constant comparisons | // TODO: Use refelct/ast to generate code | ||||||
| // CHORE: Maintain this |  | ||||||
| const ( | const ( | ||||||
| 	FieldDateUTC         WeatherUpdateField = "DateUTC" | 	FieldDateUTC         = "DateUTC" | ||||||
| 	FieldStationType     WeatherUpdateField = "StationType" | 	FieldStationType     = "StationType" | ||||||
| 	FieldTempOutdoorF    WeatherUpdateField = "TempOutdoorF" | 	FieldTempOutdoorF    = "TempOutdoorF" | ||||||
| 	FieldTempIndoorF     WeatherUpdateField = "TempIndoorF" | 	FieldTempIndoorF     = "TempIndoorF" | ||||||
| 	FieldHumidityOudoor  WeatherUpdateField = "HumidityOudoor" | 	FieldHumidityOudoor  = "HumidityOudoor" | ||||||
| 	FieldHumidityIndoor  WeatherUpdateField = "HumidityIndoor" | 	FieldHumidityIndoor  = "HumidityIndoor" | ||||||
| 	FieldWindSpeedMPH    WeatherUpdateField = "WindSpeedMPH" | 	FieldWindSpeedMPH    = "WindSpeedMPH" | ||||||
| 	FieldWindGustMPH     WeatherUpdateField = "WindGustMPH" | 	FieldWindGustMPH     = "WindGustMPH" | ||||||
| 	FieldMaxDailyGust    WeatherUpdateField = "MaxDailyGust" | 	FieldMaxDailyGust    = "MaxDailyGust" | ||||||
| 	FieldWindDir         WeatherUpdateField = "WindDir" | 	FieldWindDir         = "WindDir" | ||||||
| 	FieldWindDirAvg10m   WeatherUpdateField = "WindDirAvg10m" | 	FieldWindDirAvg10m   = "WindDirAvg10m" | ||||||
| 	FieldUV              WeatherUpdateField = "UV" | 	FieldUV              = "UV" | ||||||
| 	FieldSolarRadiation  WeatherUpdateField = "SolarRadiation" | 	FieldSolarRadiation  = "SolarRadiation" | ||||||
| 	FieldHourlyRainIn    WeatherUpdateField = "HourlyRainIn" | 	FieldHourlyRainIn    = "HourlyRainIn" | ||||||
| 	FieldEventRainIn     WeatherUpdateField = "EventRainIn" | 	FieldEventRainIn     = "EventRainIn" | ||||||
| 	FieldDailyRainIn     WeatherUpdateField = "DailyRainIn" | 	FieldDailyRainIn     = "DailyRainIn" | ||||||
| 	FieldWeeklyRainIn    WeatherUpdateField = "WeeklyRainIn" | 	FieldWeeklyRainIn    = "WeeklyRainIn" | ||||||
| 	FieldMonthlyRainIn   WeatherUpdateField = "MonthlyRainIn" | 	FieldMonthlyRainIn   = "MonthlyRainIn" | ||||||
| 	FieldYearlyRainIn    WeatherUpdateField = "YearlyRainIn" | 	FieldYearlyRainIn    = "YearlyRainIn" | ||||||
| 	FieldTotalRainIn     WeatherUpdateField = "TotalRainIn" | 	FieldTotalRainIn     = "TotalRainIn" | ||||||
| 	FieldBatteries       WeatherUpdateField = "Batteries" | 	FieldBatteries       = "Batteries" | ||||||
| 	FieldBaromRelativeIn WeatherUpdateField = "BaromRelativeIn" | 	FieldBaromRelativeIn = "BaromRelativeIn" | ||||||
| 	FieldBaromAbsoluteIn WeatherUpdateField = "BaromAbsoluteIn" | 	FieldBaromAbsoluteIn = "BaromAbsoluteIn" | ||||||
| 	FieldDewPointF       WeatherUpdateField = "DewPointF" | 	FieldDewPointF       = "DewPointF" | ||||||
| 	FieldWindChillF      WeatherUpdateField = "WindChillF" | 	FieldWindChillF      = "WindChillF" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (u *WeatherUpdate) GetStationName() string { | func (u *WeatherUpdate) GetStationName() string { | ||||||
| 	if u.StationInfo != nil { | 	if u.StationConfig != nil { | ||||||
| 		return u.StationInfo.GetName() | 		return u.StationConfig.Name | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (si *StationInfo) GetName() string { |  | ||||||
| 	if si.Name != nil { |  | ||||||
| 		return *si.Name |  | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user