add support for barometric pressure

This commit is contained in:
2026-01-29 14:04:18 +11:00
parent 7a0081b2ed
commit 5d07c5d54b
9 changed files with 401 additions and 51 deletions

View File

@@ -6,6 +6,7 @@ import (
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
@@ -70,59 +71,113 @@ func main() {
go runWundergroundUploader(ctx, latest, cfg.Wunderground.StationID, cfg.Wunderground.StationKey, cfg.Wunderground.Interval)
}
bindings := make([]mqttTopicBinding, 0, len(cfg.MQTT.Topics))
subscriptions := make([]mqttingest.Subscription, 0, len(cfg.MQTT.Topics))
for _, t := range cfg.MQTT.Topics {
qos := cfg.MQTT.QoS
if t.QoS != nil {
qos = *t.QoS
}
topicType := strings.ToLower(t.Type)
bindings = append(bindings, mqttTopicBinding{
Name: t.Name,
Topic: t.Topic,
Type: topicType,
QoS: qos,
})
subscriptions = append(subscriptions, mqttingest.Subscription{
Topic: t.Topic,
QoS: qos,
})
}
// MQTT subscriber (blocks until ctx done)
err = mqttingest.RunSubscriber(ctx, mqttingest.MQTTConfig{
Broker: cfg.MQTT.Broker,
ClientID: cfg.MQTT.ClientID,
Username: cfg.MQTT.Username,
Password: cfg.MQTT.Password,
Topic: cfg.MQTT.Topic,
QoS: cfg.MQTT.QoS,
Topics: subscriptions,
}, func(ctx context.Context, topic string, payload []byte) error {
log.Printf("mqtt message topic=%s bytes=%d payload=%s", topic, len(payload), string(payload))
p, raw, err := mqttingest.ParseWS90(payload)
if err != nil {
log.Printf("ws90 parse error topic=%s err=%v payload=%s", topic, err, string(payload))
binding := matchMQTTBinding(bindings, topic)
if binding == nil {
log.Printf("mqtt message ignored topic=%s reason=unmatched", topic)
return nil
}
// Use receive time as observation ts (WS90 payload doesn't include a timestamp).
ts := time.Now().UTC()
switch binding.Type {
case "ws90":
p, raw, err := mqttingest.ParseWS90(payload)
if err != nil {
log.Printf("ws90 parse error topic=%s err=%v payload=%s", topic, err, string(payload))
return nil
}
latest.Update(ts, p)
// Use receive time as observation ts (WS90 payload doesn't include a timestamp).
ts := time.Now().UTC()
if snap, ok := latest.Snapshot(); ok {
logForecastDeviation(forecastCache, snap)
}
latest.Update(ts, p)
if err := d.InsertWS90(ctx, db.InsertWS90Params{
TS: ts,
Site: cfg.Site.Name,
StationID: p.ID,
Model: p.Model,
BatteryOK: p.BatteryOK,
BatteryMV: p.BatteryMV,
TempC: p.TemperatureC,
Humidity: p.Humidity,
WindDirDeg: p.WindDirDeg,
WindAvgMS: p.WindAvgMS,
WindMaxMS: p.WindMaxMS,
UVI: p.UVI,
LightLux: p.LightLux,
Flags: p.Flags,
RainMM: p.RainMM,
RainStart: p.RainStart,
SupercapV: p.SupercapV,
Firmware: p.Firmware,
RawData: p.Data,
MIC: p.MIC,
Protocol: p.Protocol,
RSSI: p.RSSI,
Duration: p.Duration,
Payload: raw,
}); err != nil {
log.Printf("db insert ws90 error: %v", err)
if snap, ok := latest.Snapshot(); ok {
logForecastDeviation(forecastCache, snap)
}
if err := d.InsertWS90(ctx, db.InsertWS90Params{
TS: ts,
Site: cfg.Site.Name,
StationID: p.ID,
Model: p.Model,
BatteryOK: p.BatteryOK,
BatteryMV: p.BatteryMV,
TempC: p.TemperatureC,
Humidity: p.Humidity,
WindDirDeg: p.WindDirDeg,
WindAvgMS: p.WindAvgMS,
WindMaxMS: p.WindMaxMS,
UVI: p.UVI,
LightLux: p.LightLux,
Flags: p.Flags,
RainMM: p.RainMM,
RainStart: p.RainStart,
SupercapV: p.SupercapV,
Firmware: p.Firmware,
RawData: p.Data,
MIC: p.MIC,
Protocol: p.Protocol,
RSSI: p.RSSI,
Duration: p.Duration,
Payload: raw,
}); err != nil {
log.Printf("db insert ws90 error: %v", err)
}
case "baro", "barometer", "pressure":
p, raw, err := mqttingest.ParseBarometer(payload)
if err != nil {
log.Printf("barometer parse error topic=%s err=%v payload=%s", topic, err, string(payload))
return nil
}
ts := time.Now().UTC()
source := binding.Name
if source == "" {
source = binding.Topic
}
if err := d.InsertBarometer(ctx, db.InsertBarometerParams{
TS: ts,
Site: cfg.Site.Name,
Source: source,
PressureHPA: p.PressureHPA,
Payload: raw,
}); err != nil {
log.Printf("db insert barometer error: %v", err)
} else {
log.Printf("barometer stored source=%s pressure_hpa=%.2f", source, p.PressureHPA)
}
default:
log.Printf("mqtt message ignored topic=%s reason=unknown_type type=%s", topic, binding.Type)
}
return nil
@@ -132,6 +187,22 @@ func main() {
}
}
type mqttTopicBinding struct {
Name string
Topic string
Type string
QoS byte
}
func matchMQTTBinding(bindings []mqttTopicBinding, topic string) *mqttTopicBinding {
for i := range bindings {
if mqttingest.TopicMatches(bindings[i].Topic, topic) {
return &bindings[i]
}
}
return nil
}
func runOpenMeteoPoller(ctx context.Context, d *db.DB, cache *ForecastCache, site providers.Site, model string, interval time.Duration) {
p := &providers.OpenMeteo{}
t := time.NewTicker(interval)