diff --git a/pkg/provider/awn/provider.go b/pkg/provider/awn/provider.go index 8226c68..0dd4236 100644 --- a/pkg/provider/awn/provider.go +++ b/pkg/provider/awn/provider.go @@ -24,6 +24,7 @@ const ( BattIndoorSensor = "IndoorSensor" BattRainSensor = "RainSensor" BattCO2Sensor = "CO2Sensor" + THSensor = "TempHumiditySensor" ) func (awn *AWNProvider) Name() string { @@ -89,11 +90,55 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate { Component: BattCO2Sensor, Status: awnUpdate.BattCO2, }, + // Temp and Humidity Sensors + { + Component: THSensor + "1", + Status: awnUpdate.Batt1, + }, + { + Component: THSensor + "2", + Status: awnUpdate.Batt2, + }, + { + Component: THSensor + "3", + Status: awnUpdate.Batt3, + }, + { + Component: THSensor + "4", + Status: awnUpdate.Batt4, + }, + { + Component: THSensor + "5", + Status: awnUpdate.Batt5, + }, + { + Component: THSensor + "6", + Status: awnUpdate.Batt6, + }, + { + Component: THSensor + "7", + Status: awnUpdate.Batt7, + }, + { + Component: THSensor + "8", + Status: awnUpdate.Batt8, + }, }, TempIndoorF: awnUpdate.TempInF, HumidityIndoor: awnUpdate.HumidityIn, BaromRelativeIn: awnUpdate.BaromRelIn, BaromAbsoluteIn: awnUpdate.BaromAbsIn, + // TODO: Permit mapping to config name + TempHumiditySensors: []*weather.TempHumiditySensor{ + {Name: "Sensor1", TempF: awnUpdate.Temp1F, Humidity: awnUpdate.Humidity1}, + {Name: "Sensor2", TempF: awnUpdate.Temp2F, Humidity: awnUpdate.Humidity2}, + {Name: "Sensor3", TempF: awnUpdate.Temp3F, Humidity: awnUpdate.Humidity3}, + {Name: "Sensor4", TempF: awnUpdate.Temp4F, Humidity: awnUpdate.Humidity4}, + {Name: "Sensor5", TempF: awnUpdate.Temp5F, Humidity: awnUpdate.Humidity5}, + {Name: "Sensor6", TempF: awnUpdate.Temp6F, Humidity: awnUpdate.Humidity6}, + {Name: "Sensor7", TempF: awnUpdate.Temp7F, Humidity: awnUpdate.Humidity7}, + {Name: "Sensor8", TempF: awnUpdate.Temp8F, Humidity: awnUpdate.Humidity8}, + }, } } diff --git a/pkg/provider/awn/types.go b/pkg/provider/awn/types.go index 839e087..150b422 100644 --- a/pkg/provider/awn/types.go +++ b/pkg/provider/awn/types.go @@ -28,4 +28,32 @@ type AmbientWeatherUpdate struct { BaromAbsIn *float64 `json:"baromabsin,omitempty" schema:"baromabsin"` BattIn *int `json:"battin,omitempty" schema:"battin"` BattCO2 *int `json:"batt_co2,omitempty" schema:"batt_co2"` + *AmbientTempHumiditySensors +} + +type AmbientTempHumiditySensors struct { + Temp1F *float64 `json:"temp1f,omitempty" schema:"temp1f"` + Temp2F *float64 `json:"temp2f,omitempty" schema:"temp2f"` + Temp3F *float64 `json:"temp3f,omitempty" schema:"temp3f"` + Temp4F *float64 `json:"temp4f,omitempty" schema:"temp4f"` + Temp5F *float64 `json:"temp5f,omitempty" schema:"temp5f"` + Temp6F *float64 `json:"temp6f,omitempty" schema:"temp6f"` + Temp7F *float64 `json:"temp7f,omitempty" schema:"temp7f"` + Temp8F *float64 `json:"temp8f,omitempty" schema:"temp8f"` + Humidity1 *int `json:"humidity1,omitempty" schema:"humidity1"` + Humidity2 *int `json:"humidity2,omitempty" schema:"humidity2"` + Humidity3 *int `json:"humidity3,omitempty" schema:"humidity3"` + Humidity4 *int `json:"humidity4,omitempty" schema:"humidity4"` + Humidity5 *int `json:"humidity5,omitempty" schema:"humidity5"` + Humidity6 *int `json:"humidity6,omitempty" schema:"humidity6"` + Humidity7 *int `json:"humidity7,omitempty" schema:"humidity7"` + Humidity8 *int `json:"humidity8,omitempty" schema:"humidity8"` + Batt1 *int `json:"batt1,omitempty" schema:"batt1"` + Batt2 *int `json:"batt2,omitempty" schema:"batt2"` + Batt3 *int `json:"batt3,omitempty" schema:"batt3"` + Batt4 *int `json:"batt4,omitempty" schema:"batt4"` + Batt5 *int `json:"batt5,omitempty" schema:"batt5"` + Batt6 *int `json:"batt6,omitempty" schema:"batt6"` + Batt7 *int `json:"batt7,omitempty" schema:"batt7"` + Batt8 *int `json:"batt8,omitempty" schema:"batt8"` } diff --git a/pkg/weather/metrics.go b/pkg/weather/metrics.go index 3f1992e..7093e76 100644 --- a/pkg/weather/metrics.go +++ b/pkg/weather/metrics.go @@ -4,13 +4,13 @@ import ( "context" "gitea.libretechconsulting.com/rmcguire/go-app/pkg/config" - "gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" - "github.com/rs/zerolog" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) +var MetricPrefix = "weather" + type WeatherMetrics struct { // Weather Metrics TempOutdoorF metric.Float64Gauge @@ -37,6 +37,10 @@ type WeatherMetrics struct { DewPointF metric.Float64Gauge WindChillF metric.Float64Gauge + // Temp and Humidity Sensors + SensorTempF metric.Float64Gauge + SensorHumidity metric.Int64Gauge + // Internal Telemetry UpdatesReceived metric.Int64Counter appCtx context.Context @@ -45,92 +49,8 @@ type WeatherMetrics struct { recorder *MetricRecorder } -var MetricPrefix = "weather" - -func MustInitMetrics(appCtx context.Context) *WeatherMetrics { - wm := &WeatherMetrics{ - appCtx: appCtx, - cfg: config.MustFromCtx(appCtx), - recorder: &MetricRecorder{ctx: appCtx, l: zerolog.Ctx(appCtx)}, - } - - wm.meter = otel.GetMeter(appCtx, "weather", "metrics") - - // Weather Metrics - wm.TempOutdoorF, _ = wm.meter.Float64Gauge(MetricPrefix+"_temp_outdoor_f", - metric.WithDescription("Outdoor Temperature in Faherenheit")) - wm.TempIndoorF, _ = wm.meter.Float64Gauge(MetricPrefix+"_temp_indoor_f", - metric.WithDescription("Indoor Temperature in Faherenheit")) - wm.HumidityOudoor, _ = wm.meter.Int64Gauge(MetricPrefix+"_humidity_outdoor", - metric.WithDescription("Outdoor Humidity %")) - wm.HumidityIndoor, _ = wm.meter.Int64Gauge(MetricPrefix+"_humidity_indoor", - metric.WithDescription("Indoor Humidity %")) - wm.WindSpeedMPH, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_speed_mph", - metric.WithDescription("Wind Speed in MPH")) - wm.WindGustMPH, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_gust_mph", - metric.WithDescription("Wind Gust in MPH")) - wm.MaxDailyGust, _ = wm.meter.Float64Gauge(MetricPrefix+"_max_daily_gust", - metric.WithDescription("Max Daily Wind Gust")) - wm.WindDir, _ = wm.meter.Int64Gauge(MetricPrefix+"_wind_dir", - metric.WithDescription("Wind Direction in Degrees")) - wm.WindDirAvg10m, _ = wm.meter.Int64Gauge(MetricPrefix+"_wind_dir_avg_10m", - metric.WithDescription("Wind Direction 10m Average")) - wm.UV, _ = wm.meter.Int64Gauge(MetricPrefix+"_uv", - metric.WithDescription("UV Index")) - wm.SolarRadiation, _ = wm.meter.Float64Gauge(MetricPrefix+"_solar_radiation", - metric.WithDescription("Solar Radiation in W/㎡")) - wm.HourlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_hourly_rain_in", - metric.WithDescription("Hourly Rain in Inches")) - wm.EventRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_event_rain_in", - metric.WithDescription("Event Rain in Inches")) - wm.DailyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_daily_rain_in", - metric.WithDescription("Daily Rain in Inches")) - wm.WeeklyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_weekly_rain_in", - metric.WithDescription("Weekly Rain in Inches")) - wm.MonthlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_monthly_rain_in", - metric.WithDescription("Monthly Rain in Inches")) - wm.YearlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_yearly_rain_in", - metric.WithDescription("Yearly Rain in Inches")) - wm.TotalRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_total_rain_in", - metric.WithDescription("Total Rain in Inches")) - wm.BatteryStatus, _ = wm.meter.Int64Gauge(MetricPrefix+"_battery_status", - metric.WithDescription("Per-component battery status")) - wm.BaromRelativeIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_barometric_pressure_relative_in", - metric.WithDescription("Relative Pressure in Inches of Mercury")) - wm.BaromAbsoluteIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_barometric_pressure_absolute_in", - metric.WithDescription("Absolute Pressure in Inches of Mercury")) - wm.DewPointF, _ = wm.meter.Float64Gauge(MetricPrefix+"_dew_point_f", - metric.WithDescription("Dew Point in Faherenheit")) - wm.WindChillF, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_chill_f", - metric.WithDescription("Wind Chill in Faherenheit")) - - // Internal Telemetry - wm.UpdatesReceived, _ = wm.meter.Int64Counter(MetricPrefix+"_updates_received", - metric.WithDescription("Metric Updates Processed by Exporter")) - - return wm -} - func (wm *WeatherMetrics) Update(u *WeatherUpdate) { - attributes := []attribute.KeyValue{ - semconv.ServiceVersion(wm.cfg.Version), - semconv.ServiceName(wm.cfg.Name), - semconv.DeploymentEnvironment(wm.cfg.Environment), - } - if u.StationType != nil { - attributes = append(attributes, - attribute.String("station_type", *u.StationType)) - } - if u.StationConfig != nil { - if u.StationConfig.Name != "" { - attributes = append(attributes, - attribute.String("station_name", u.StationConfig.Name)) - } - if u.StationConfig.Equipment != "" { - attributes = append(attributes, - attribute.String("station_equipment", u.StationConfig.Equipment)) - } - } + attributes := wm.GetAttributes(u) 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, Station: u.StationConfig}) @@ -155,13 +75,73 @@ func (wm *WeatherMetrics) Update(u *WeatherUpdate) { 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, Station: u.StationConfig}) - // Batteries - for _, battery := range u.Batteries { - batAttr := attributes - batAttr = append(batAttr, attribute.String("component", battery.Component)) - - wm.recorder.Record(&RecordOpts{Int64Gauge: wm.BatteryStatus, IntVal: battery.Status, Field: FieldBatteries, Attributes: batAttr, Station: u.StationConfig}) - } + wm.RecordBatteries(u, attributes) + wm.RecordTempHumiditySensors(u, attributes) wm.UpdatesReceived.Add(wm.appCtx, 1) } + +func (wm *WeatherMetrics) RecordBatteries(u *WeatherUpdate, attr []attribute.KeyValue) { + for _, battery := range u.Batteries { + batAttr := attr + batAttr = append(batAttr, attribute.String("component", battery.Component)) + + wm.recorder.Record(&RecordOpts{ + Int64Gauge: wm.BatteryStatus, + IntVal: battery.Status, + Field: FieldBatteries, + Attributes: batAttr, + Station: u.StationConfig, + }) + } +} + +func (wm *WeatherMetrics) RecordTempHumiditySensors(u *WeatherUpdate, attr []attribute.KeyValue) { + if u == nil || u.TempHumiditySensors == nil { + return + } + + for _, sensor := range u.TempHumiditySensors { + sensorAttr := attr + sensorAttr = append(sensorAttr, attribute.String("sensorName", sensor.Name)) + + wm.recorder.Record(&RecordOpts{ + Float64Gauge: wm.SensorTempF, + FloatVal: sensor.TempF, + Field: FieldSensorTempF, + Attributes: sensorAttr, + Station: u.StationConfig, + }) + wm.recorder.Record(&RecordOpts{ + Int64Gauge: wm.SensorHumidity, + IntVal: sensor.Humidity, + Field: FieldSensorHumidity, + Attributes: sensorAttr, + Station: u.StationConfig, + }) + } +} + +func (wm *WeatherMetrics) GetAttributes(u *WeatherUpdate) []attribute.KeyValue { + attributes := []attribute.KeyValue{ + semconv.ServiceVersion(wm.cfg.Version), + semconv.ServiceName(wm.cfg.Name), + semconv.DeploymentEnvironment(wm.cfg.Environment), + } + if u.StationType != nil { + attributes = append(attributes, + attribute.String("station_type", *u.StationType)) + } + if u.StationConfig != nil { + if u.StationConfig.Name != "" { + attributes = append(attributes, + attribute.String("station_name", u.StationConfig.Name)) + } + if u.StationConfig.Equipment != "" { + attributes = append(attributes, + attribute.String("station_equipment", u.StationConfig.Equipment)) + } + } + + return attributes +} diff --git a/pkg/weather/metrics_init.go b/pkg/weather/metrics_init.go new file mode 100644 index 0000000..54203aa --- /dev/null +++ b/pkg/weather/metrics_init.go @@ -0,0 +1,80 @@ +package weather + +import ( + "context" + + "gitea.libretechconsulting.com/rmcguire/go-app/pkg/config" + "gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" + "github.com/rs/zerolog" + "go.opentelemetry.io/otel/metric" +) + +func MustInitMetrics(appCtx context.Context) *WeatherMetrics { + wm := &WeatherMetrics{ + appCtx: appCtx, + cfg: config.MustFromCtx(appCtx), + recorder: &MetricRecorder{ctx: appCtx, l: zerolog.Ctx(appCtx)}, + } + + wm.meter = otel.GetMeter(appCtx, "weather", "metrics") + + // Weather Metrics + wm.TempOutdoorF, _ = wm.meter.Float64Gauge(MetricPrefix+"_temp_outdoor_f", + metric.WithDescription("Outdoor Temperature in Faherenheit")) + wm.TempIndoorF, _ = wm.meter.Float64Gauge(MetricPrefix+"_temp_indoor_f", + metric.WithDescription("Indoor Temperature in Faherenheit")) + wm.HumidityOudoor, _ = wm.meter.Int64Gauge(MetricPrefix+"_humidity_outdoor", + metric.WithDescription("Outdoor Humidity %")) + wm.HumidityIndoor, _ = wm.meter.Int64Gauge(MetricPrefix+"_humidity_indoor", + metric.WithDescription("Indoor Humidity %")) + wm.WindSpeedMPH, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_speed_mph", + metric.WithDescription("Wind Speed in MPH")) + wm.WindGustMPH, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_gust_mph", + metric.WithDescription("Wind Gust in MPH")) + wm.MaxDailyGust, _ = wm.meter.Float64Gauge(MetricPrefix+"_max_daily_gust", + metric.WithDescription("Max Daily Wind Gust")) + wm.WindDir, _ = wm.meter.Int64Gauge(MetricPrefix+"_wind_dir", + metric.WithDescription("Wind Direction in Degrees")) + wm.WindDirAvg10m, _ = wm.meter.Int64Gauge(MetricPrefix+"_wind_dir_avg_10m", + metric.WithDescription("Wind Direction 10m Average")) + wm.UV, _ = wm.meter.Int64Gauge(MetricPrefix+"_uv", + metric.WithDescription("UV Index")) + wm.SolarRadiation, _ = wm.meter.Float64Gauge(MetricPrefix+"_solar_radiation", + metric.WithDescription("Solar Radiation in W/㎡")) + wm.HourlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_hourly_rain_in", + metric.WithDescription("Hourly Rain in Inches")) + wm.EventRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_event_rain_in", + metric.WithDescription("Event Rain in Inches")) + wm.DailyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_daily_rain_in", + metric.WithDescription("Daily Rain in Inches")) + wm.WeeklyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_weekly_rain_in", + metric.WithDescription("Weekly Rain in Inches")) + wm.MonthlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_monthly_rain_in", + metric.WithDescription("Monthly Rain in Inches")) + wm.YearlyRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_yearly_rain_in", + metric.WithDescription("Yearly Rain in Inches")) + wm.TotalRainIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_total_rain_in", + metric.WithDescription("Total Rain in Inches")) + wm.BatteryStatus, _ = wm.meter.Int64Gauge(MetricPrefix+"_battery_status", + metric.WithDescription("Per-component battery status")) + wm.BaromRelativeIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_barometric_pressure_relative_in", + metric.WithDescription("Relative Pressure in Inches of Mercury")) + wm.BaromAbsoluteIn, _ = wm.meter.Float64Gauge(MetricPrefix+"_barometric_pressure_absolute_in", + metric.WithDescription("Absolute Pressure in Inches of Mercury")) + wm.DewPointF, _ = wm.meter.Float64Gauge(MetricPrefix+"_dew_point_f", + metric.WithDescription("Dew Point in Faherenheit")) + wm.WindChillF, _ = wm.meter.Float64Gauge(MetricPrefix+"_wind_chill_f", + metric.WithDescription("Wind Chill in Faherenheit")) + + // Temp and Humidity Sensors + wm.SensorTempF, _ = wm.meter.Float64Gauge(MetricPrefix+"_sensor_temp_f", + metric.WithDescription("Temperature Sensor in Faherenheit")) + wm.SensorHumidity, _ = wm.meter.Int64Gauge(MetricPrefix+"_sensor_humidity", + metric.WithDescription("Humidity % Sensor")) + + // Internal Telemetry + wm.UpdatesReceived, _ = wm.meter.Int64Counter(MetricPrefix+"_updates_received", + metric.WithDescription("Metric Updates Processed by Exporter")) + + return wm +} diff --git a/pkg/weather/types.go b/pkg/weather/types.go index b7b97f6..e474c92 100644 --- a/pkg/weather/types.go +++ b/pkg/weather/types.go @@ -38,8 +38,14 @@ type WeatherUpdate struct { // if not otherwise set DewPointF *float64 WindChillF *float64 - // First URL parameters given to AWN/Wunderground - // if proxying is enabled + // Extra Temp+Humidity Sensors + TempHumiditySensors []*TempHumiditySensor +} + +type TempHumiditySensor struct { + Name string + TempF *float64 + Humidity *int } type BatteryStatus struct { @@ -76,6 +82,8 @@ const ( FieldBaromAbsoluteIn = "BaromAbsoluteIn" FieldDewPointF = "DewPointF" FieldWindChillF = "WindChillF" + FieldSensorTempF = "SensorTempF" + FieldSensorHumidity = "SensorHumidity" ) func (u *WeatherUpdate) GetStationName() string {