patched restart continuity so daily/hour rain totals are rebuilt from DB on startup
This commit is contained in:
@@ -62,6 +62,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
|
// Rebuild recent rain increments from DB so wunderground rain rates/totals
|
||||||
|
// remain stable across container restarts.
|
||||||
|
hydrateCtx, hydrateCancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
hydrateLatestFromDB(hydrateCtx, d, latest, cfg.Site.Name, rainDayLoc)
|
||||||
|
hydrateCancel()
|
||||||
|
|
||||||
site := providers.Site{
|
site := providers.Site{
|
||||||
Name: cfg.Site.Name,
|
Name: cfg.Site.Name,
|
||||||
Latitude: cfg.Site.Latitude,
|
Latitude: cfg.Site.Latitude,
|
||||||
@@ -219,6 +225,159 @@ func matchMQTTBinding(bindings []mqttTopicBinding, topic string) *mqttTopicBindi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hydrateLatestFromDB(ctx context.Context, d *db.DB, latest *mqttingest.Latest, site string, rainDayLoc *time.Location) {
|
||||||
|
if d == nil || d.Pool == nil || latest == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rainDayLoc == nil {
|
||||||
|
rainDayLoc = time.UTC
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
localNow := now.In(rainDayLoc)
|
||||||
|
localMidnight := time.Date(localNow.Year(), localNow.Month(), localNow.Day(), 0, 0, 0, 0, rainDayLoc)
|
||||||
|
start := localMidnight.Add(-1 * time.Hour).UTC()
|
||||||
|
|
||||||
|
rows, err := d.Pool.Query(ctx, `
|
||||||
|
SELECT
|
||||||
|
ts,
|
||||||
|
station_id,
|
||||||
|
coalesce(model, ''),
|
||||||
|
coalesce(battery_ok, 0),
|
||||||
|
coalesce(battery_mv, 0),
|
||||||
|
coalesce(temperature_c, 0),
|
||||||
|
coalesce(humidity, 0),
|
||||||
|
coalesce(wind_dir_deg, 0),
|
||||||
|
coalesce(wind_avg_m_s, 0),
|
||||||
|
coalesce(wind_max_m_s, 0),
|
||||||
|
coalesce(uvi, 0),
|
||||||
|
coalesce(light_lux, 0),
|
||||||
|
coalesce(flags, 0),
|
||||||
|
coalesce(rain_mm, 0),
|
||||||
|
coalesce(rain_start, 0),
|
||||||
|
coalesce(supercap_v, 0),
|
||||||
|
coalesce(firmware, 0),
|
||||||
|
coalesce(raw_data, ''),
|
||||||
|
coalesce(mic, ''),
|
||||||
|
coalesce(protocol, ''),
|
||||||
|
coalesce(rssi, 0),
|
||||||
|
coalesce(duration, 0)
|
||||||
|
FROM observations_ws90
|
||||||
|
WHERE site = $1
|
||||||
|
AND ts >= $2
|
||||||
|
ORDER BY ts ASC
|
||||||
|
`, site, start)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("startup hydrate ws90 query error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
hydrated := 0
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
ts time.Time
|
||||||
|
stationID int64
|
||||||
|
model string
|
||||||
|
batteryOK, batteryMV int32
|
||||||
|
tempC, humidity float64
|
||||||
|
windDirDeg, windAvgMS, windMaxMS float64
|
||||||
|
uvi, lightLux float64
|
||||||
|
flags int32
|
||||||
|
rainMM, supercapV float64
|
||||||
|
rainStart, duration int64
|
||||||
|
firmware, rssi int32
|
||||||
|
rawData, mic, protocol string
|
||||||
|
)
|
||||||
|
if err := rows.Scan(
|
||||||
|
&ts,
|
||||||
|
&stationID,
|
||||||
|
&model,
|
||||||
|
&batteryOK,
|
||||||
|
&batteryMV,
|
||||||
|
&tempC,
|
||||||
|
&humidity,
|
||||||
|
&windDirDeg,
|
||||||
|
&windAvgMS,
|
||||||
|
&windMaxMS,
|
||||||
|
&uvi,
|
||||||
|
&lightLux,
|
||||||
|
&flags,
|
||||||
|
&rainMM,
|
||||||
|
&rainStart,
|
||||||
|
&supercapV,
|
||||||
|
&firmware,
|
||||||
|
&rawData,
|
||||||
|
&mic,
|
||||||
|
&protocol,
|
||||||
|
&rssi,
|
||||||
|
&duration,
|
||||||
|
); err != nil {
|
||||||
|
log.Printf("startup hydrate ws90 scan error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
latest.Update(ts, &mqttingest.WS90Payload{
|
||||||
|
Model: model,
|
||||||
|
ID: stationID,
|
||||||
|
BatteryOK: int(batteryOK),
|
||||||
|
BatteryMV: int(batteryMV),
|
||||||
|
TemperatureC: tempC,
|
||||||
|
Humidity: humidity,
|
||||||
|
WindDirDeg: windDirDeg,
|
||||||
|
WindAvgMS: windAvgMS,
|
||||||
|
WindMaxMS: windMaxMS,
|
||||||
|
UVI: uvi,
|
||||||
|
LightLux: lightLux,
|
||||||
|
Flags: int(flags),
|
||||||
|
RainMM: rainMM,
|
||||||
|
RainStart: rainStart,
|
||||||
|
SupercapV: supercapV,
|
||||||
|
Firmware: int(firmware),
|
||||||
|
Data: rawData,
|
||||||
|
MIC: mic,
|
||||||
|
Protocol: protocol,
|
||||||
|
RSSI: int(rssi),
|
||||||
|
Duration: duration,
|
||||||
|
})
|
||||||
|
hydrated++
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Printf("startup hydrate ws90 rows error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hydrated > 0 {
|
||||||
|
log.Printf(
|
||||||
|
"startup hydrate ws90 rows=%d window_start=%s window_end=%s",
|
||||||
|
hydrated,
|
||||||
|
start.Format(time.RFC3339),
|
||||||
|
now.Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Printf("startup hydrate ws90 rows=0 window_start=%s", start.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
baroTS time.Time
|
||||||
|
pressureHPA float64
|
||||||
|
)
|
||||||
|
err = d.Pool.QueryRow(ctx, `
|
||||||
|
SELECT ts, pressure_hpa
|
||||||
|
FROM observations_baro
|
||||||
|
WHERE site = $1
|
||||||
|
AND pressure_hpa IS NOT NULL
|
||||||
|
ORDER BY ts DESC
|
||||||
|
LIMIT 1
|
||||||
|
`, site).Scan(&baroTS, &pressureHPA)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("startup hydrate barometer skipped: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
latest.UpdateBarometer(baroTS, pressureHPA)
|
||||||
|
log.Printf("startup hydrate barometer ts=%s pressure_hpa=%.2f", baroTS.UTC().Format(time.RFC3339), pressureHPA)
|
||||||
|
}
|
||||||
|
|
||||||
func runOpenMeteoPoller(ctx context.Context, d *db.DB, cache *ForecastCache, site providers.Site, model string, interval time.Duration) {
|
func runOpenMeteoPoller(ctx context.Context, d *db.DB, cache *ForecastCache, site providers.Site, model string, interval time.Duration) {
|
||||||
p := &providers.OpenMeteo{}
|
p := &providers.OpenMeteo{}
|
||||||
t := time.NewTicker(interval)
|
t := time.NewTicker(interval)
|
||||||
|
|||||||
Reference in New Issue
Block a user