// 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" "gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather" ) 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 config *AmbientLocalExporterConfig awnProvider provider.AmbientProvider wuProvider provider.AmbientProvider appCtx context.Context metrics *weather.WeatherMetrics l *zerolog.Logger } func New(appCtx context.Context, awConfig *AmbientLocalExporterConfig) *AmbientWeather { return &AmbientWeather{ appCtx: appCtx, } } // 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) { return func(w http.ResponseWriter, r *http.Request) { aw.handleProviderRequest(aw.awnProvider, w, r) } } func (aw *AmbientWeather) GetWundergroundHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { aw.handleProviderRequest(aw.wuProvider, w, r) } } // 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 func (aw *AmbientWeather) handleProviderRequest( p provider.AmbientProvider, w http.ResponseWriter, r *http.Request, ) { l := zerolog.Ctx(aw.appCtx) tracer := otel.GetTracer(aw.appCtx, p.Name()+".http.handler") 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())) return } // Calculate any fields that may be missing // such as dew point and wind chill update.Enrich() // Update metrics if aw.metrics == nil { aw.metrics = weather.MustInitMetrics(aw.appCtx) } aw.metrics.Update(update) l.Debug(). Str("provider", p.Name()). Any("update", update). Msg("successfully handled update") w.Write([]byte("ok")) }