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 PressureHPA *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)) // Barometric pressure (inHg) if u.PressureHPA != nil { q.Set("baromin", fmt.Sprintf("%.4f", hPaToInHg(*u.PressureHPA))) } // 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 } func hPaToInHg(hpa float64) float64 { return hpa * 0.0295299830714 }