Refactor and complete providers
This commit is contained in:
parent
9094cb3d3e
commit
398cfdb77c
2
go.mod
2
go.mod
@ -6,6 +6,7 @@ require (
|
||||
gitea.libretechconsulting.com/rmcguire/go-app v0.1.0
|
||||
github.com/gorilla/schema v1.4.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
go.opentelemetry.io/otel v1.33.0
|
||||
golang.org/x/sys v0.29.0
|
||||
)
|
||||
|
||||
@ -29,7 +30,6 @@ require (
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect
|
||||
|
81
pkg/ambient/ambient.go
Normal file
81
pkg/ambient/ambient.go
Normal file
@ -0,0 +1,81 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// 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
|
||||
var (
|
||||
awnProvider = &awn.AWNProvider{}
|
||||
wuProvider = &wunderground.WUProvider{}
|
||||
)
|
||||
|
||||
func GetAWNHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
handleProviderRequest(appCtx, awnProvider, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func GetWundergroundHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
handleProviderRequest(appCtx, wuProvider, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Takes an HTTP requests and convers 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 handleProviderRequest(
|
||||
appCtx context.Context,
|
||||
p provider.AmbientProvider,
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
) {
|
||||
l := zerolog.Ctx(appCtx)
|
||||
tracer := otel.GetTracer(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()))
|
||||
}
|
||||
|
||||
// Calculate any fields that may be missing
|
||||
// such as dew point and wind chill
|
||||
update.Enrich()
|
||||
|
||||
l.Trace().Any("update", update).Send()
|
||||
w.Write([]byte("ok"))
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package ambient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/awn"
|
||||
)
|
||||
|
||||
func GetAWNHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
|
||||
l := zerolog.Ctx(appCtx)
|
||||
tracer := otel.GetTracer(appCtx, "awn.http.handler")
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := tracer.Start(r.Context(), "update")
|
||||
defer span.End()
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
l.Trace().Bytes("body", bodyBytes)
|
||||
l.Trace().Any("request", r.URL.Query()).Send()
|
||||
|
||||
update, err := awn.UnmarshalQueryParams(r.URL.Query())
|
||||
if err != nil {
|
||||
l.Err(err).Send()
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
|
||||
l.Trace().Any("update", update).Send()
|
||||
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
}
|
||||
|
||||
func GetWundergroundHandlerFunc(appCtx context.Context) func(http.ResponseWriter, *http.Request) {
|
||||
l := zerolog.Ctx(appCtx)
|
||||
tracer := otel.GetTracer(appCtx, "wunderground.http.handler")
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := tracer.Start(r.Context(), "update")
|
||||
defer span.End()
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
l.Trace().Bytes("body", bodyBytes)
|
||||
l.Trace().Any("request", r.URL.Query()).Send()
|
||||
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package awn
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
)
|
||||
|
||||
type AmbientWeatherUpdate struct {
|
||||
PassKey string `json:"PASSKEY,omitempty"`
|
||||
StationType string `json:"stationtype,omitempty"`
|
||||
DateUTC string `json:"dateutc,omitempty"`
|
||||
TempF float32 `json:"tempf,omitempty"`
|
||||
Humidity int `json:"humidity,omitempty"`
|
||||
WindSpeedMPH float32 `json:"windspeedmph,omitempty"`
|
||||
WindGustMPH float32 `json:"windgustmph,omitempty"`
|
||||
MaxDailyGust float32 `json:"maxdailygust,omitempty"`
|
||||
WindDir int `json:"winddir,omitempty"`
|
||||
WindDirAVG10m int `json:"winddir_avg10m,omitempty"`
|
||||
UV int `json:"uv,omitempty"`
|
||||
SolarRadiation float32 `json:"solarradiation,omitempty"`
|
||||
HourlyRainIn float32 `json:"hourlyrainin,omitempty"`
|
||||
EventRainIn float32 `json:"eventrainin,omitempty"`
|
||||
DailyRainIn float32 `json:"dailyrainin,omitempty"`
|
||||
WeeklyRainIn float32 `json:"weeklyrainin,omitempty"`
|
||||
MonthlyRainIn float32 `json:"monthlyrainin,omitempty"`
|
||||
YearlyRainIn float32 `json:"yearlyrainin,omitempty"`
|
||||
TotalRainIn float32 `json:"totalrainin,omitempty"`
|
||||
BattOut int `json:"battout,omitempty"`
|
||||
BattRain int `json:"battrain,omitempty"`
|
||||
TempInF float32 `json:"tempinf,omitempty"`
|
||||
HumidityIn int `json:"humidityin,omitempty"`
|
||||
BaromRelIn float32 `json:"baromrelin,omitempty"`
|
||||
BaromAbsIn float32 `json:"baromabsin,omitempty"`
|
||||
BattIn int `json:"battin,omitempty"`
|
||||
}
|
||||
|
||||
func UnmarshalQueryParams(query url.Values) (*AmbientWeatherUpdate, error) {
|
||||
update := new(AmbientWeatherUpdate)
|
||||
|
||||
decoder := schema.NewDecoder()
|
||||
if err := decoder.Decode(update, query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return update, nil
|
||||
}
|
79
pkg/provider/awn/provider.go
Normal file
79
pkg/provider/awn/provider.go
Normal file
@ -0,0 +1,79 @@
|
||||
package awn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather"
|
||||
)
|
||||
|
||||
type AWNProvider struct{}
|
||||
|
||||
const providerName = "awn"
|
||||
|
||||
func (awn *AWNProvider) Name() string {
|
||||
return providerName
|
||||
}
|
||||
|
||||
// Takes an inbound request from the ambient device and maps
|
||||
// to a stable struct for weather updates
|
||||
func (awn *AWNProvider) ReqToWeather(_ context.Context, r *http.Request) (
|
||||
*weather.WeatherUpdate, error,
|
||||
) {
|
||||
awnUpdate, err := UnmarshalQueryParams(r.URL.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MapAwnUpdate(awnUpdate), nil
|
||||
}
|
||||
|
||||
func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate {
|
||||
updateTime, err := time.Parse(time.DateTime, awnUpdate.DateUTC)
|
||||
if err != nil {
|
||||
updateTime = time.Now()
|
||||
}
|
||||
|
||||
return &weather.WeatherUpdate{
|
||||
StationType: awnUpdate.StationType,
|
||||
DateUTC: &updateTime,
|
||||
TempF: awnUpdate.TempF,
|
||||
Humidity: awnUpdate.Humidity,
|
||||
WindSpeedMPH: awnUpdate.WindGustMPH,
|
||||
WindGustMPH: awnUpdate.WindGustMPH,
|
||||
MaxDailyGust: awnUpdate.MaxDailyGust,
|
||||
WindDir: awnUpdate.WindDir,
|
||||
WindDirAVG10m: awnUpdate.WindDirAVG10m,
|
||||
UV: awnUpdate.UV,
|
||||
SolarRadiation: awnUpdate.SolarRadiation,
|
||||
HourlyRainIn: awnUpdate.HourlyRainIn,
|
||||
EventRainIn: awnUpdate.EventRainIn,
|
||||
DailyRainIn: awnUpdate.DailyRainIn,
|
||||
WeeklyRainIn: awnUpdate.WeeklyRainIn,
|
||||
MonthlyRainIn: awnUpdate.MonthlyRainIn,
|
||||
YearlyRainIn: awnUpdate.YearlyRainIn,
|
||||
TotalRainIn: awnUpdate.TotalRainIn,
|
||||
BattOutdoorSensor: awnUpdate.BattOut,
|
||||
BattIndoorSensor: awnUpdate.BattIn,
|
||||
BattRainSensor: awnUpdate.BattRain,
|
||||
TempInsideF: awnUpdate.TempInF,
|
||||
HumidityInside: awnUpdate.HumidityIn,
|
||||
BaromRelativeIn: awnUpdate.BaromRelIn,
|
||||
BaromAbsoluteIn: awnUpdate.BaromAbsIn,
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalQueryParams(query url.Values) (*AmbientWeatherUpdate, error) {
|
||||
update := new(AmbientWeatherUpdate)
|
||||
|
||||
decoder := schema.NewDecoder()
|
||||
if err := decoder.Decode(update, query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return update, nil
|
||||
}
|
30
pkg/provider/awn/types.go
Normal file
30
pkg/provider/awn/types.go
Normal file
@ -0,0 +1,30 @@
|
||||
package awn
|
||||
|
||||
type AmbientWeatherUpdate struct {
|
||||
PassKey string `json:"PASSKEY,omitempty" schema:"PASSKEY"`
|
||||
StationType string `json:"stationtype,omitempty" schema:"stationtype"`
|
||||
DateUTC string `json:"dateutc,omitempty" schema:"dateutc"`
|
||||
TempF float32 `json:"tempf,omitempty" schema:"tempf"`
|
||||
Humidity int `json:"humidity,omitempty" schema:"humidity"`
|
||||
WindSpeedMPH float32 `json:"windspeedmph,omitempty" schema:"windspeedmph"`
|
||||
WindGustMPH float32 `json:"windgustmph,omitempty" schema:"windgustmph"`
|
||||
MaxDailyGust float32 `json:"maxdailygust,omitempty" schema:"maxdailygust"`
|
||||
WindDir int `json:"winddir,omitempty" schema:"winddir"`
|
||||
WindDirAVG10m int `json:"winddir_avg10m,omitempty" schema:"winddir_avg10m"`
|
||||
UV int `json:"uv,omitempty" schema:"uv"`
|
||||
SolarRadiation float32 `json:"solarradiation,omitempty" schema:"solarradiation"`
|
||||
HourlyRainIn float32 `json:"hourlyrainin,omitempty" schema:"hourlyrainin"`
|
||||
EventRainIn float32 `json:"eventrainin,omitempty" schema:"eventrainin"`
|
||||
DailyRainIn float32 `json:"dailyrainin,omitempty" schema:"dailyrainin"`
|
||||
WeeklyRainIn float32 `json:"weeklyrainin,omitempty" schema:"weeklyrainin"`
|
||||
MonthlyRainIn float32 `json:"monthlyrainin,omitempty" schema:"monthlyrainin"`
|
||||
YearlyRainIn float32 `json:"yearlyrainin,omitempty" schema:"yearlyrainin"`
|
||||
TotalRainIn float32 `json:"totalrainin,omitempty" schema:"totalrainin"`
|
||||
BattOut int `json:"battout,omitempty" schema:"battout"`
|
||||
BattRain int `json:"battrain,omitempty" schema:"battrain"`
|
||||
TempInF float32 `json:"tempinf,omitempty" schema:"tempinf"`
|
||||
HumidityIn int `json:"humidityin,omitempty" schema:"humidityin"`
|
||||
BaromRelIn float32 `json:"baromrelin,omitempty" schema:"baromrelin"`
|
||||
BaromAbsIn float32 `json:"baromabsin,omitempty" schema:"baromabsin"`
|
||||
BattIn int `json:"battin,omitempty" schema:"battin"`
|
||||
}
|
15
pkg/provider/provider.go
Normal file
15
pkg/provider/provider.go
Normal file
@ -0,0 +1,15 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather"
|
||||
)
|
||||
|
||||
// Simple interface used for converting Wunderground and
|
||||
// Ambient Weather Network HTTP requests to a stable struct
|
||||
type AmbientProvider interface {
|
||||
ReqToWeather(context.Context, *http.Request) (*weather.WeatherUpdate, error)
|
||||
Name() string
|
||||
}
|
70
pkg/provider/wunderground/provider.go
Normal file
70
pkg/provider/wunderground/provider.go
Normal file
@ -0,0 +1,70 @@
|
||||
package wunderground
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/ambient-weather-local-exporter/pkg/weather"
|
||||
)
|
||||
|
||||
type WUProvider struct{}
|
||||
|
||||
const providerName = "weatherunderground"
|
||||
|
||||
func (wu *WUProvider) Name() string {
|
||||
return providerName
|
||||
}
|
||||
|
||||
// Takes an inbound request from the ambient device and maps
|
||||
// to a stable struct for weather updates
|
||||
func (wu *WUProvider) ReqToWeather(_ context.Context, r *http.Request) (
|
||||
*weather.WeatherUpdate, error,
|
||||
) {
|
||||
wuUpdate, err := UnmarshalQueryParams(r.URL.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MapWUUpdate(wuUpdate), nil
|
||||
}
|
||||
|
||||
func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate {
|
||||
updateTime, err := time.Parse(time.DateTime, wuUpdate.DateUTC)
|
||||
if err != nil {
|
||||
updateTime = time.Now()
|
||||
}
|
||||
|
||||
return &weather.WeatherUpdate{
|
||||
StationType: wuUpdate.SoftwareType,
|
||||
DateUTC: &updateTime,
|
||||
TempF: wuUpdate.Tempf,
|
||||
Humidity: wuUpdate.Humidity,
|
||||
WindSpeedMPH: wuUpdate.WindGustMPH,
|
||||
WindGustMPH: wuUpdate.WindGustMPH,
|
||||
WindDir: wuUpdate.WindDir,
|
||||
UV: wuUpdate.UV,
|
||||
SolarRadiation: wuUpdate.SolarRadiation,
|
||||
HourlyRainIn: wuUpdate.RainIn,
|
||||
DailyRainIn: wuUpdate.DailyRainIn,
|
||||
WeeklyRainIn: wuUpdate.WeeklyRainIn,
|
||||
MonthlyRainIn: wuUpdate.MonthlyRainIn,
|
||||
YearlyRainIn: wuUpdate.YearlyRainIn,
|
||||
TempInsideF: wuUpdate.IndoorTempF,
|
||||
HumidityInside: wuUpdate.IndoorHumidity,
|
||||
BaromRelativeIn: wuUpdate.BaromIn,
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalQueryParams(query url.Values) (*WundergroundUpdate, error) {
|
||||
update := new(WundergroundUpdate)
|
||||
|
||||
if err := schema.NewDecoder().Decode(update, query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return update, nil
|
||||
}
|
29
pkg/provider/wunderground/types.go
Normal file
29
pkg/provider/wunderground/types.go
Normal file
@ -0,0 +1,29 @@
|
||||
package wunderground
|
||||
|
||||
type WundergroundUpdate struct {
|
||||
ID string `json:"ID,omitempty" schema:"ID"`
|
||||
Password string `json:"PASSWORD,omitempty" schema:"PASSWORD"`
|
||||
UV int `json:"UV,omitempty" schema:"UV"`
|
||||
Action string `json:"action,omitempty" schema:"action"`
|
||||
BaromIn float32 `json:"baromin,omitempty" schema:"baromin"`
|
||||
DailyRainIn float32 `json:"dailyrainin,omitempty" schema:"dailyrainin"`
|
||||
DateUTC string `json:"dateutc,omitempty" schema:"dateutc"`
|
||||
DewPtF float32 `json:"dewptf,omitempty" schema:"dewptf"`
|
||||
Humidity int `json:"humidity,omitempty" schema:"humidity"`
|
||||
IndoorHumidity int `json:"indoorhumidity,omitempty" schema:"indoorhumidity"`
|
||||
IndoorTempF float32 `json:"indoortempf,omitempty" schema:"indoortempf"`
|
||||
LowBatt bool `json:"lowbatt,omitempty" schema:"lowbatt"`
|
||||
MonthlyRainIn float32 `json:"monthlyrainin,omitempty" schema:"monthlyrainin"`
|
||||
RainIn float32 `json:"rainin,omitempty" schema:"rainin"`
|
||||
Realtime bool `json:"realtime,omitempty" schema:"realtime"`
|
||||
Rtfreq int `json:"rtfreq,omitempty" schema:"rtfreq"`
|
||||
SoftwareType string `json:"softwaretype,omitempty" schema:"softwaretype"`
|
||||
SolarRadiation float32 `json:"solarradiation,omitempty" schema:"solarradiation"`
|
||||
Tempf float32 `json:"tempf,omitempty" schema:"tempf"`
|
||||
WeeklyRainIn float32 `json:"weeklyrainin,omitempty" schema:"weeklyrainin"`
|
||||
WindChillF float32 `json:"windchillf,omitempty" schema:"windchillf"`
|
||||
WindDir int `json:"winddir,omitempty" schema:"winddir"`
|
||||
WindGustMPH float32 `json:"windgustmph,omitempty" schema:"windgustmph"`
|
||||
WindSpeedMPH float32 `json:"windspeedmph,omitempty" schema:"windspeedmph"`
|
||||
YearlyRainIn float32 `json:"yearlyrainin,omitempty" schema:"yearlyrainin"`
|
||||
}
|
54
pkg/weather/enrich.go
Normal file
54
pkg/weather/enrich.go
Normal file
@ -0,0 +1,54 @@
|
||||
package weather
|
||||
|
||||
import "math"
|
||||
|
||||
// Attempts to complete missing fields that may not
|
||||
// be set by a specific provider, such as DewPoint and WindChill
|
||||
//
|
||||
// If enrich is called repeated with the same station ID, measurements
|
||||
// will be recorded to produce averages. This will be more stable
|
||||
// and support scaling if Redis is available
|
||||
// TODO: Implement average tracker
|
||||
func (u *WeatherUpdate) Enrich() {
|
||||
if u.WindChillF == 0 {
|
||||
u.WindChillF = CalculateWindChill(u.TempF, u.WindSpeedMPH)
|
||||
}
|
||||
|
||||
if u.DewPointF == 0 {
|
||||
u.DewPointF = CalculateDewPoint(u.TempF, float32(u.Humidity))
|
||||
}
|
||||
|
||||
if u.BaromAbsoluteIn == 0 {
|
||||
u.BaromAbsoluteIn = u.BaromRelativeIn
|
||||
}
|
||||
}
|
||||
|
||||
func CalculateDewPoint(tempF, humidity float32) float32 {
|
||||
// Convert temperature from Fahrenheit to Celsius
|
||||
tempC := (tempF - 32) * 5 / 9
|
||||
|
||||
// Calculate the dew point using the Magnus-Tetens approximation
|
||||
a := float32(17.27)
|
||||
b := float32(237.7)
|
||||
|
||||
alpha := (a*tempC)/(b+tempC) + float32(math.Log(float64(humidity)/100))
|
||||
dewPointC := (b * alpha) / (a - alpha)
|
||||
|
||||
// Convert dew point back to Fahrenheit
|
||||
dewPointF := (dewPointC * 9 / 5) + 32
|
||||
return dewPointF
|
||||
}
|
||||
|
||||
func CalculateWindChill(tempF float32, windSpeedMPH float32) float32 {
|
||||
if windSpeedMPH <= 3 {
|
||||
// Wind chill calculation doesn't apply for very low wind speeds
|
||||
return tempF
|
||||
}
|
||||
|
||||
// Formula for calculating wind chill
|
||||
return float32(
|
||||
35.74 + 0.6215*float64(tempF) -
|
||||
35.75*math.Pow(float64(windSpeedMPH), 0.16) +
|
||||
0.4275*float64(tempF)*
|
||||
math.Pow(float64(windSpeedMPH), 0.16))
|
||||
}
|
3
pkg/weather/metrics.go
Normal file
3
pkg/weather/metrics.go
Normal file
@ -0,0 +1,3 @@
|
||||
package weather
|
||||
|
||||
// TODO: Add OTEL Metrics
|
39
pkg/weather/types.go
Normal file
39
pkg/weather/types.go
Normal file
@ -0,0 +1,39 @@
|
||||
package weather
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Stable intermediate struct containing superset of fields
|
||||
// between AWN and Wunderground style updates from Ambient devices
|
||||
type WeatherUpdate struct {
|
||||
DateUTC *time.Time
|
||||
StationType string
|
||||
TempF float32
|
||||
TempInsideF float32
|
||||
Humidity int
|
||||
HumidityInside int
|
||||
WindSpeedMPH float32
|
||||
WindGustMPH float32
|
||||
MaxDailyGust float32
|
||||
WindDir int
|
||||
WindDirAVG10m int
|
||||
UV int
|
||||
SolarRadiation float32
|
||||
HourlyRainIn float32
|
||||
EventRainIn float32
|
||||
DailyRainIn float32
|
||||
WeeklyRainIn float32
|
||||
MonthlyRainIn float32
|
||||
YearlyRainIn float32
|
||||
TotalRainIn float32
|
||||
BattOutdoorSensor int
|
||||
BattIndoorSensor int
|
||||
BattRainSensor int
|
||||
BaromRelativeIn float32
|
||||
BaromAbsoluteIn float32
|
||||
// These fields may be calculated
|
||||
// if not otherwise set
|
||||
DewPointF float32
|
||||
WindChillF float32
|
||||
}
|
Loading…
Reference in New Issue
Block a user