ambient-local-exporter/pkg/ambient/ambient.go

141 lines
4.1 KiB
Go
Raw Normal View History

2025-01-04 22:51:55 +00:00
// This provides a shim between HTTP GET requests sent
// by ambient devices, and the providers that may be
// configured (awn, wunderground)
package ambient
import (
"context"
"fmt"
"net/http"
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"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/wunderground"
2025-01-05 01:54:36 +00:00
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather"
2025-01-04 22:51:55 +00:00
)
2025-01-05 01:54:36 +00:00
type AmbientWeather struct {
// These providers implement support for the update sent
// when either "AmbientWeather" or "Wunderground" are selected
// in the "Custom" section of the AWNet app, or the web UI
// of an Ambient WeatherHub
2025-01-08 21:49:31 +00:00
config *AmbientLocalExporterConfig
2025-01-05 01:54:36 +00:00
awnProvider provider.AmbientProvider
wuProvider provider.AmbientProvider
appCtx context.Context
metrics *weather.WeatherMetrics
l *zerolog.Logger
}
2025-01-08 21:49:31 +00:00
func New(appCtx context.Context, awConfig *AmbientLocalExporterConfig) *AmbientWeather {
2025-01-05 01:54:36 +00:00
return &AmbientWeather{
2025-01-09 02:28:38 +00:00
config: awConfig,
2025-01-05 01:54:36 +00:00
appCtx: appCtx,
}
}
2025-01-04 22:51:55 +00:00
2025-01-07 15:27:02 +00:00
// Initialize with defaults, set logger from context
func (aw *AmbientWeather) Init() *AmbientWeather {
2025-01-05 01:54:36 +00:00
aw.awnProvider = &awn.AWNProvider{}
aw.wuProvider = &wunderground.WUProvider{}
aw.l = zerolog.Ctx(aw.appCtx)
2025-01-09 02:28:38 +00:00
aw.l.Trace().Any("awConfig", aw.config).Send()
2025-01-07 15:27:02 +00:00
return aw
2025-01-05 01:54:36 +00:00
}
func (aw *AmbientWeather) GetAWNHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
2025-01-04 22:51:55 +00:00
return func(w http.ResponseWriter, r *http.Request) {
2025-01-05 01:54:36 +00:00
aw.handleProviderRequest(aw.awnProvider, w, r)
2025-01-04 22:51:55 +00:00
}
}
2025-01-05 01:54:36 +00:00
func (aw *AmbientWeather) GetWundergroundHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
2025-01-04 22:51:55 +00:00
return func(w http.ResponseWriter, r *http.Request) {
2025-01-05 01:54:36 +00:00
aw.handleProviderRequest(aw.wuProvider, w, r)
2025-01-04 22:51:55 +00:00
}
}
2025-01-07 16:28:34 +00:00
// Takes an HTTP requests and converts it to a
2025-01-04 22:51:55 +00:00
// 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
2025-01-05 01:54:36 +00:00
func (aw *AmbientWeather) handleProviderRequest(
2025-01-04 22:51:55 +00:00
p provider.AmbientProvider,
w http.ResponseWriter,
r *http.Request,
) {
2025-01-05 01:54:36 +00:00
l := zerolog.Ctx(aw.appCtx)
tracer := otel.GetTracer(aw.appCtx, p.Name()+".http.handler")
2025-01-04 22:51:55 +00:00
ctx, span := tracer.Start(r.Context(), p.Name()+".update")
span.SetAttributes(attribute.String("provider", p.Name()))
defer span.End()
l.Trace().Str("p", p.Name()).
Any("query", r.URL.Query()).Send()
// Convert to WeatherUpdate
update, err := p.ReqToWeather(ctx, r)
if err != nil {
l.Err(err).Send()
span.RecordError(err)
span.SetStatus(codes.Error,
fmt.Sprintf("failed to handle %s update: %s",
p.Name(), err.Error()))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
2025-01-05 22:37:14 +00:00
return
2025-01-04 22:51:55 +00:00
}
// Calculate any fields that may be missing
// such as dew point and wind chill
update.Enrich()
2025-01-09 02:28:38 +00:00
// Prepare metrics if this is the first update
2025-01-05 01:54:36 +00:00
if aw.metrics == nil {
2025-01-09 02:28:38 +00:00
aw.InitMetrics()
2025-01-05 01:54:36 +00:00
}
2025-01-09 02:28:38 +00:00
// Enrich station if configured
aw.enrichStation(update)
// Update metrics
2025-01-05 01:54:36 +00:00
aw.metrics.Update(update)
2025-01-04 22:55:09 +00:00
l.Debug().
Str("provider", p.Name()).
Any("update", update).
Msg("successfully handled update")
2025-01-04 22:51:55 +00:00
w.Write([]byte("ok"))
}
2025-01-09 02:28:38 +00:00
func (aw *AmbientWeather) InitMetrics() {
if aw.config.MetricPrefix != "" {
weather.MetricPrefix = aw.config.MetricPrefix
}
aw.metrics = weather.MustInitMetrics(aw.appCtx)
}
func (aw *AmbientWeather) enrichStation(update *weather.WeatherUpdate) {
if update != nil && update.StationID != nil && *update.StationID != "" {
for _, station := range aw.config.WeatherStations {
if *update.StationID == station.AWNPassKey || *update.StationID == station.WundergroundID {
update.StationInfo = &weather.StationInfo{
Type: update.StationType,
Equipment: &station.Equipment,
Name: &station.Name,
Keep: station.KeepMetrics,
Drop: station.DropMetrics,
}
}
}
}
}