Files
go-weatherstation/internal/providers/wunderground.go
2026-01-26 13:08:34 +11:00

113 lines
2.9 KiB
Go

package providers
import (
"context"
"fmt"
"log"
"net/http"
"net/url"
"strconv"
"time"
)
// WU PWS upload protocol endpoint. :contentReference[oaicite:1]{index=1}
const wuEndpoint = "https://rtupdate.wunderground.com/weatherstation/updateweatherstation.php"
type WundergroundClient struct {
HTTP *http.Client
}
type WUUpload struct {
StationID string
StationKey string
// Units required by WU:
// tempf (F), wind mph, baromin (inHg), rain inches.
TempC float64
Humidity float64
WindDirDeg float64
WindAvgMS float64
WindMaxMS float64
UVI float64
SolarWm2 *float64 // your payload is lux; leave nil unless you add W/m^2 later
RainLastHourMM float64
DailyRainMM float64
DateUTC string // "now" recommended
}
func (c *WundergroundClient) Upload(ctx context.Context, u WUUpload) (string, error) {
if c.HTTP == nil {
c.HTTP = &http.Client{Timeout: 10 * time.Second}
}
q := url.Values{}
q.Set("ID", u.StationID)
q.Set("PASSWORD", u.StationKey)
q.Set("action", "updateraw")
q.Set("dateutc", u.DateUTC) // "now" ok :contentReference[oaicite:2]{index=2}
// Required-ish observation fields (only set what we have)
q.Set("tempf", fmt.Sprintf("%.2f", cToF(u.TempC)))
q.Set("humidity", fmt.Sprintf("%.0f", u.Humidity))
q.Set("winddir", fmt.Sprintf("%.0f", u.WindDirDeg))
q.Set("windspeedmph", fmt.Sprintf("%.2f", msToMph(u.WindAvgMS)))
q.Set("windgustmph", fmt.Sprintf("%.2f", msToMph(u.WindMaxMS)))
// Rain (inches)
q.Set("rainin", fmt.Sprintf("%.4f", mmToIn(u.RainLastHourMM)))
q.Set("dailyrainin", fmt.Sprintf("%.4f", mmToIn(u.DailyRainMM)))
// UV index
q.Set("UV", fmt.Sprintf("%.2f", u.UVI))
// NOTE: your WS90 payload provides light in lux, not W/m^2.
// WU expects solarradiation in W/m^2, so we omit it unless you add a conversion/actual sensor field.
if u.SolarWm2 != nil {
q.Set("solarradiation", fmt.Sprintf("%.2f", *u.SolarWm2))
}
// Optional: a software identifier
q.Set("softwaretype", "go-weatherstation-go")
// Log sanitized params so it's clear what we're sending.
safe := url.Values{}
for k, vals := range q {
if k == "PASSWORD" {
continue
}
for _, v := range vals {
safe.Add(k, v)
}
}
log.Printf("wunderground upload params %s", safe.Encode())
reqURL := wuEndpoint + "?" + q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
if err != nil {
return "", err
}
resp, err := c.HTTP.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// WU typically returns plain text like "success"
if resp.StatusCode/100 != 2 {
return "", fmt.Errorf("wunderground upload HTTP %d", resp.StatusCode)
}
// Read small body
buf := make([]byte, 2048)
n, _ := resp.Body.Read(buf)
return strconv.Quote(string(buf[:n])), nil
}
func cToF(c float64) float64 { return (c * 9.0 / 5.0) + 32.0 }
func msToMph(ms float64) float64 { return ms * 2.2369362920544 }
func mmToIn(mm float64) float64 { return mm / 25.4 }