126 lines
3.0 KiB
Go
Raw Normal View History

2025-01-04 17:51:55 -05:00
package weather
2025-01-12 15:30:37 -05:00
import (
"math"
"net/url"
"strconv"
2025-01-12 17:23:32 -05:00
2025-03-06 17:20:02 -05:00
"gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/ambient/config"
2025-01-12 15:30:37 -05:00
)
2025-01-04 17:51:55 -05:00
// Attempts to complete missing fields that may not
// be set by a specific provider, such as DewPoint and WindChill
2025-03-05 12:53:57 -05:00
// TODO: Add span
2025-01-12 17:23:32 -05:00
func (u *WeatherUpdate) Enrich(weatherStations ...*config.WeatherStation) {
2025-01-05 17:36:39 -05:00
if u == nil {
return
}
2025-01-28 19:41:57 -05:00
// Clear invalid measurements, would be better if these weren't
// sent when sensor was out of range.
// TODO: This should probably just be done for all fields where
// the value is -9999
2025-01-28 19:26:40 -05:00
if u.BaromAbsoluteIn != nil && *u.BaromAbsoluteIn < 20 {
u.BaromAbsoluteIn = nil
}
if u.BaromRelativeIn != nil && *u.BaromRelativeIn < 20 {
u.BaromRelativeIn = nil
}
2025-01-28 19:41:57 -05:00
if u.TempIndoorF != nil && *u.TempIndoorF < -1000 {
u.TempIndoorF = nil
}
2025-01-28 19:26:40 -05:00
// Calculate Wind Chill
2025-01-08 21:28:38 -05:00
if u.WindChillF == nil && u.TempOutdoorF != nil && u.WindSpeedMPH != nil {
wc := CalculateWindChill(*u.TempOutdoorF, *u.WindSpeedMPH)
u.WindChillF = &wc
2025-01-04 17:51:55 -05:00
}
2025-01-28 19:26:40 -05:00
// Calculate Dew Point
2025-01-08 21:28:38 -05:00
if u.DewPointF == nil && (u.TempOutdoorF != nil && u.HumidityOudoor != nil) {
if *u.TempOutdoorF != 0 || *u.HumidityOudoor != 0 {
dp := CalculateDewPoint(*u.TempOutdoorF, float64(*u.HumidityOudoor))
u.DewPointF = &dp
}
2025-01-04 17:51:55 -05:00
}
2025-01-28 19:26:40 -05:00
// Use relative pressure if absolute isn't provided
2025-01-08 21:28:38 -05:00
if u.BaromAbsoluteIn == nil && u.BaromRelativeIn != nil {
2025-01-04 17:51:55 -05:00
u.BaromAbsoluteIn = u.BaromRelativeIn
}
}
2025-03-05 12:53:57 -05:00
// Swaps sensor and component names based on
// user provided configuration
func (u *WeatherUpdate) MapSensors() {
if u.StationConfig == nil {
return
}
// Map sensor battery components
for i, batt := range u.Batteries {
u.Batteries[i].Component = u.StationConfig.MapSensor(batt.Component)
}
// Map other sensors
for _, th := range u.TempHumiditySensors {
th.Name = u.StationConfig.MapSensor(th.Name)
}
}
2025-01-04 20:54:36 -05:00
func CalculateDewPoint(tempF, humidity float64) float64 {
2025-01-04 17:51:55 -05:00
// Convert temperature from Fahrenheit to Celsius
tempC := (tempF - 32) * 5 / 9
// Calculate the dew point using the Magnus-Tetens approximation
2025-01-04 20:54:36 -05:00
a := float64(17.27)
b := float64(237.7)
2025-01-04 17:51:55 -05:00
2025-01-04 20:54:36 -05:00
alpha := (a*tempC)/(b+tempC) + math.Log(humidity/100)
2025-01-04 17:51:55 -05:00
dewPointC := (b * alpha) / (a - alpha)
// Convert dew point back to Fahrenheit
dewPointF := (dewPointC * 9 / 5) + 32
return dewPointF
}
2025-01-04 20:54:36 -05:00
func CalculateWindChill(tempF float64, windSpeedMPH float64) float64 {
2025-01-04 17:51:55 -05:00
if windSpeedMPH <= 3 {
// Wind chill calculation doesn't apply for very low wind speeds
return tempF
}
// Formula for calculating wind chill
2025-01-04 20:54:36 -05:00
return 35.74 + 0.6215*tempF -
35.75*math.Pow(windSpeedMPH, 0.16) +
0.4275*tempF*math.Pow(windSpeedMPH, 0.16)
2025-01-04 17:51:55 -05:00
}
2025-01-12 15:30:37 -05:00
// Helper function to set values from fields
// typically from a WeatherUpdate
func SetURLVal(vals *url.Values, key string, value any) {
if value == nil {
return
}
switch v := value.(type) {
case *float64:
if v == nil {
return
}
str := strconv.FormatFloat(*v, 'f', 4, 64)
vals.Set(key, str)
case *int:
if v == nil {
return
}
str := strconv.FormatInt(int64(*v), 10)
vals.Set(key, str)
case *string:
if v == nil {
return
}
vals.Set(key, *v)
}
}