Compare commits

...

15 Commits

Author SHA1 Message Date
87f0cbac01 Fix typos, update TODO
All checks were successful
Build and Publish / release (push) Successful in 3m44s
2025-01-07 11:28:34 -05:00
4c93303f27 Consolidate battery metrics 2025-01-07 10:27:02 -05:00
e8654e76bc Update TODO 2025-01-07 08:53:06 -05:00
54f725c822 Update go-app 2025-01-05 20:33:28 -05:00
c002331fd4 Fix NaN with zero values
All checks were successful
Build and Publish / release (push) Successful in 3m7s
2025-01-05 19:05:24 -05:00
2ed4eca56c Add support for co2 battery
All checks were successful
Build and Publish / release (push) Successful in 2m57s
2025-01-05 18:39:58 -05:00
2e3cfb44f4 Don't enrich nil updates
All checks were successful
Build and Publish / release (push) Successful in 2m48s
2025-01-05 17:37:14 -05:00
be8a4f3bca Don't enrich nil updates 2025-01-05 17:36:39 -05:00
96dfcf7e53 Update sample
All checks were successful
Build and Publish / release (push) Successful in 3m58s
2025-01-05 16:24:52 -05:00
5fc16b39af Upgrade go-app 2025-01-05 16:23:15 -05:00
1ae58f64ca Move docker compose sample 2025-01-05 15:16:16 -05:00
bb4e4aa2af Update TODO 2025-01-05 13:19:30 -05:00
f102ef50bf Upgrade to fix env overrides
All checks were successful
Build and Publish / release (push) Successful in 4m7s
2025-01-05 13:13:19 -05:00
d3d251ef30 Update TODO 2025-01-05 11:47:06 -05:00
eecaf2a82e Add latest tag
All checks were successful
Build and Publish / release (push) Successful in 2m44s
2025-01-05 11:44:48 -05:00
14 changed files with 161 additions and 106 deletions

View File

@ -79,7 +79,9 @@ jobs:
with:
context: .
push: true
tags: ${{ env.DOCKER_IMG }}:${{ github.ref_name }}
tags: |
${{ env.DOCKER_IMG }}:${{ github.ref_name }}
${{ env.DOCKER_IMG }}:latest
build-args: |
VER_PKG=${{ env.VER_PKG }}
VERSION=${{ github.ref_name }}

4
.gitignore vendored
View File

@ -20,7 +20,11 @@
# Go workspace file
go.work
go.work.sum
.env
bin/*
config.yaml
docker-compose.yml

18
TODO.md
View File

@ -1,10 +1,16 @@
# TODO
- [ ] Fix shutdown
- [ ] Configuration for app
- [ ] Makefile
- [ ] Dockerfile
- [ ] Helm Chart
- [ ] Gitea CI
- [ ] Update README
- [ ] Version flag
- [ ] Add Grafana dashboard
- [ ] Add device name field with ID/Key mappings
- [ ] Add device type field with ID/Key mappings
## Done
- [x] Consolidate battery status into one metric with device label
- [x] Fix shutdown
- [x] Add new fields from WS-2192
- [x] Gitea CI
- [x] Version flag
- [x] Makefile
- [x] Dockerfile

View File

@ -10,6 +10,10 @@ services:
APP_LOG_FORMAT: json ## console, json
APP_LOG_TIME_FORMAT: rfc3339 ## long, short, unix, rfc3339, off
APP_HTTP_LISTEN: 0.0.0.0:8080
APP_HTTP_READ_TIMEOUT: 10s
APP_HTTP_WRITE_TIMEOUT: 10s
APP_HTTP_IDLE_TIMEOUT: 30s
APP_HTTP_LOG_REQUESTS: true
APP_OTEL_STDOUT_ENABLED: false
APP_OTEL_METRIC_INTERVAL_SECS: 30
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel.libretechconsulting.com:4317 # Set to your otel collector

4
go.mod
View File

@ -3,7 +3,7 @@ module gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter
go 1.23.4
require (
gitea.libretechconsulting.com/rmcguire/go-app v0.1.1
gitea.libretechconsulting.com/rmcguire/go-app v0.3.0
github.com/gorilla/schema v1.4.1
github.com/rs/zerolog v1.33.0
go.opentelemetry.io/otel v1.33.0
@ -13,7 +13,7 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/caarlos0/env/v9 v9.0.0 // indirect
github.com/caarlos0/env/v11 v11.3.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect

14
go.sum
View File

@ -1,11 +1,13 @@
gitea.libretechconsulting.com/rmcguire/go-app v0.1.0 h1:H4TMgQ463oRNOyoi0FAvfGtOoDn651zNZStxM+sdNuU=
gitea.libretechconsulting.com/rmcguire/go-app v0.1.0/go.mod h1:p0ajkpFvzzD6VZ4xSjuowtwGRb1DjMfo/iG6LyFqFCs=
gitea.libretechconsulting.com/rmcguire/go-app v0.1.1 h1:Hrxqi1tqz8mf0baBsWgFe/S4jyMtIuPqH2FlanJUMNc=
gitea.libretechconsulting.com/rmcguire/go-app v0.1.1/go.mod h1:p0ajkpFvzzD6VZ4xSjuowtwGRb1DjMfo/iG6LyFqFCs=
gitea.libretechconsulting.com/rmcguire/go-app v0.1.3 h1:EwmEJLpN+rQjJ5stGEkZsqEDa5F/YnDAEeqJB9XlFn4=
gitea.libretechconsulting.com/rmcguire/go-app v0.1.3/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38=
gitea.libretechconsulting.com/rmcguire/go-app v0.2.0 h1:pOm/PysC0IWPuEbmEjNSHHa8Qc5OhuoksYExcuJMFE4=
gitea.libretechconsulting.com/rmcguire/go-app v0.2.0/go.mod h1:wHOWh4O4AMDATQ3WEUYjq5a5bnICPBpu5G6BsNxqN38=
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=
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/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc=
github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=

View File

@ -16,10 +16,10 @@ func main() {
ctx, cncl := signal.NotifyContext(context.Background(), os.Kill, unix.SIGTERM)
defer cncl()
// Read config and environment, set up logging, load up
// an appCtx, and prepare ambient weather local exporter
ctx = app.MustSetupConfigAndLogging(ctx)
aw := ambient.New(ctx)
aw.MustInit()
aw := ambient.New(ctx).Init()
awApp := app.App{
AppContext: ctx,

View File

@ -37,10 +37,12 @@ func New(appCtx context.Context) *AmbientWeather {
}
}
func (aw *AmbientWeather) MustInit() {
// Initialize with defaults, set logger from context
func (aw *AmbientWeather) Init() *AmbientWeather {
aw.awnProvider = &awn.AWNProvider{}
aw.wuProvider = &wunderground.WUProvider{}
aw.l = zerolog.Ctx(aw.appCtx)
return aw
}
func (aw *AmbientWeather) GetAWNHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
@ -55,7 +57,7 @@ func (aw *AmbientWeather) GetWundergroundHandlerFunc(appCtx context.Context) fun
}
}
// Takes an HTTP requests and convers it to a
// Takes an HTTP requests and converts it to a
// stable type. Enrich is called on the type to complete
// any missing fields as the two providers supported by Ambient
// devices (awn/wunderground) produce different fields
@ -85,6 +87,7 @@ func (aw *AmbientWeather) handleProviderRequest(
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
// Calculate any fields that may be missing

View File

@ -57,9 +57,24 @@ func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate {
MonthlyRainIn: awnUpdate.MonthlyRainIn,
YearlyRainIn: awnUpdate.YearlyRainIn,
TotalRainIn: awnUpdate.TotalRainIn,
BattOutdoorSensor: awnUpdate.BattOut,
BattIndoorSensor: awnUpdate.BattIn,
BattRainSensor: awnUpdate.BattRain,
Batteries: []weather.BatteryStatus{
{
Component: "OutdoorSensor",
Status: awnUpdate.BattOut,
},
{
Component: "IndoorSensor",
Status: awnUpdate.BattIn,
},
{
Component: "RainSensor",
Status: awnUpdate.BattRain,
},
{
Component: "CO2Sensor",
Status: awnUpdate.BattCO2,
},
},
TempIndoorF: awnUpdate.TempInF,
HumidityIndoor: awnUpdate.HumidityIn,
BaromRelativeIn: awnUpdate.BaromRelIn,

View File

@ -27,4 +27,5 @@ type AmbientWeatherUpdate struct {
BaromRelIn float64 `json:"baromrelin,omitempty" schema:"baromrelin"`
BaromAbsIn float64 `json:"baromabsin,omitempty" schema:"baromabsin"`
BattIn int `json:"battin,omitempty" schema:"battin"`
BattCO2 int `json:"batt_co2,omitempty" schema:"batt_co2"`
}

View File

@ -5,11 +5,15 @@ import "math"
// Attempts to complete missing fields that may not
// be set by a specific provider, such as DewPoint and WindChill
func (u *WeatherUpdate) Enrich() {
if u == nil {
return
}
if u.WindChillF == 0 {
u.WindChillF = CalculateWindChill(u.TempOutdoorF, u.WindSpeedMPH)
}
if u.DewPointF == 0 {
if u.DewPointF == 0 && (u.TempOutdoorF != 0 && u.HumidityOudoor != 0) {
u.DewPointF = CalculateDewPoint(u.TempOutdoorF, float64(u.HumidityOudoor))
}

View File

@ -30,9 +30,7 @@ type WeatherMetrics struct {
MonthlyRainIn metric.Float64Gauge
YearlyRainIn metric.Float64Gauge
TotalRainIn metric.Float64Gauge
BattOutdoorSensor metric.Int64Gauge
BattIndoorSensor metric.Int64Gauge
BattRainSensor metric.Int64Gauge
BatteryStatus metric.Int64Gauge
BaromRelativeIn metric.Float64Gauge
BaromAbsoluteIn metric.Float64Gauge
DewPointF metric.Float64Gauge
@ -58,7 +56,7 @@ func MustInitMetrics(appCtx context.Context) *WeatherMetrics {
metric.WithDescription("Outdoor Temperature in Faherenheit"))
wm.TempIndoorF, _ = wm.meter.Float64Gauge("weather_temp_indoor_f",
metric.WithDescription("Indoor Temperature in Faherenheit"))
wm.HumidityOudoor, _ = wm.meter.Int64Gauge("weather_humidity_oudoor",
wm.HumidityOudoor, _ = wm.meter.Int64Gauge("weather_humidity_outdoor",
metric.WithDescription("Outdoor Humidity %"))
wm.HumidityIndoor, _ = wm.meter.Int64Gauge("weather_humidity_indoor",
metric.WithDescription("Indoor Humidity %"))
@ -90,12 +88,8 @@ func MustInitMetrics(appCtx context.Context) *WeatherMetrics {
metric.WithDescription("Yearly Rain in Inches"))
wm.TotalRainIn, _ = wm.meter.Float64Gauge("weather_total_rain_in",
metric.WithDescription("Total Rain in Inches"))
wm.BattOutdoorSensor, _ = wm.meter.Int64Gauge("weather_batt_outdoor_sensor",
metric.WithDescription("Outdoor Equipment Battery"))
wm.BattIndoorSensor, _ = wm.meter.Int64Gauge("weather_batt_indoor_sensor",
metric.WithDescription("Indoor Equipmenet Battery"))
wm.BattRainSensor, _ = wm.meter.Int64Gauge("weather_batt_rain_sensor",
metric.WithDescription("Rain Sensor Battery"))
wm.BatteryStatus, _ = wm.meter.Int64Gauge("battery_status",
metric.WithDescription("Per-component battery status"))
wm.BaromRelativeIn, _ = wm.meter.Float64Gauge("weather_barometric_pressure_relative_in",
metric.WithDescription("Relative Pressure in Inches of Mercury"))
wm.BaromAbsoluteIn, _ = wm.meter.Float64Gauge("weather_barometric_pressure_absolute_in",
@ -136,13 +130,18 @@ func (wm *WeatherMetrics) Update(u *WeatherUpdate) {
wm.MonthlyRainIn.Record(wm.appCtx, u.MonthlyRainIn, metric.WithAttributeSet(attributes))
wm.YearlyRainIn.Record(wm.appCtx, u.YearlyRainIn, metric.WithAttributeSet(attributes))
wm.TotalRainIn.Record(wm.appCtx, u.TotalRainIn, metric.WithAttributeSet(attributes))
wm.BattOutdoorSensor.Record(wm.appCtx, int64(u.BattOutdoorSensor), metric.WithAttributeSet(attributes))
wm.BattIndoorSensor.Record(wm.appCtx, int64(u.BattIndoorSensor), metric.WithAttributeSet(attributes))
wm.BattRainSensor.Record(wm.appCtx, int64(u.BattRainSensor), metric.WithAttributeSet(attributes))
wm.BaromRelativeIn.Record(wm.appCtx, u.BaromRelativeIn, metric.WithAttributeSet(attributes))
wm.BaromAbsoluteIn.Record(wm.appCtx, u.BaromAbsoluteIn, metric.WithAttributeSet(attributes))
wm.DewPointF.Record(wm.appCtx, u.DewPointF, metric.WithAttributeSet(attributes))
wm.WindChillF.Record(wm.appCtx, u.WindChillF, metric.WithAttributeSet(attributes))
// Batteries
for _, battery := range u.Batteries {
wm.BatteryStatus.Record(wm.appCtx, int64(battery.Status),
metric.WithAttributeSet(attributes),
metric.WithAttributes(attribute.String("component", battery.Component)),
)
}
wm.UpdatesReceived.Add(wm.appCtx, 1)
}

View File

@ -27,9 +27,11 @@ type WeatherUpdate struct {
MonthlyRainIn float64
YearlyRainIn float64
TotalRainIn float64
BattOutdoorSensor int
BattIndoorSensor int
BattRainSensor int
Batteries []BatteryStatus
// BattOutdoorSensor int
// BattIndoorSensor int
// BattRainSensor int
// BattCO2Sensor int
BaromRelativeIn float64
BaromAbsoluteIn float64
// These fields may be calculated
@ -37,3 +39,8 @@ type WeatherUpdate struct {
DewPointF float64
WindChillF float64
}
type BatteryStatus struct {
Component string
Status int
}

8
unset_env.sh Executable file
View File

@ -0,0 +1,8 @@
#!env sh
unset APP_NAME
unset APP_LOG_LEVEL
unset APP_LOG_FORMAT
unset APP_LOG_TIME_FORMAT
unset APP_HTTP_LISTEN
unset APP_OTEL_STDOUT_ENABLED
unset APP_OTEL_METRIC_INTERVAL_SECS