Completely rearrange main.go

This commit is contained in:
Josh Deprez 2023-01-21 16:59:12 +11:00
parent d198f61ea9
commit 40b56ee7ec
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw

135
main.go
View file

@ -41,24 +41,28 @@ var (
inverterAddrs = flag.String("inverter-addrs", "rakmodule_00DBC1:502,192.168.86.6:502", "Comma-separated list of inverter addresses (modbus-tcp with 'encryption')") inverterAddrs = flag.String("inverter-addrs", "rakmodule_00DBC1:502,192.168.86.6:502", "Comma-separated list of inverter addresses (modbus-tcp with 'encryption')")
scrapeInterval = flag.Duration("scrape-interval", 15*time.Second, "Period of modbus scraping loop") scrapeInterval = flag.Duration("scrape-interval", 15*time.Second, "Period of modbus scraping loop")
scrapeMu sync.Mutex promHandler = promhttp.Handler()
lastScrape time.Time scrapeMu sync.Mutex
lastValues = make(map[uint16]float64) lastScrape time.Time
lastValues = make(map[uint16]float64)
scrapeCounter = promauto.NewCounter(prometheus.CounterOpts{ scrapeCounter = promauto.NewCounter(prometheus.CounterOpts{
Namespace: "sungrow", Namespace: "sungrow",
Subsystem: "scraper", Subsystem: "scraper",
Name: "scrapes_total", Name: "scrapes_total",
Help: "Number of successful scrapes of the inverter input registers",
}) })
scrapeStart = promauto.NewGauge(prometheus.GaugeOpts{ scrapeStart = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "sungrow", Namespace: "sungrow",
Subsystem: "scraper", Subsystem: "scraper",
Name: "scrape_start", Name: "scrape_start",
Help: "Start time of the most recent scrape attempt",
}) })
scrapeEnd = promauto.NewGauge(prometheus.GaugeOpts{ scrapeEnd = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "sungrow", Namespace: "sungrow",
Subsystem: "scraper", Subsystem: "scraper",
Name: "scrape_end", Name: "scrape_end",
Help: "End time of the most recent successful scrape",
}) })
scrapeDuration = promauto.NewGauge(prometheus.GaugeOpts{ scrapeDuration = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "sungrow", Namespace: "sungrow",
@ -83,7 +87,7 @@ func init() {
Namespace: "sungrow", Namespace: "sungrow",
Subsystem: "tariff", Subsystem: "tariff",
Name: "daily_charge", Name: "daily_charge",
Help: "unit:$", Help: "units:$",
}, },
func() float64 { return dailySupplyCharge }, func() float64 { return dailySupplyCharge },
) )
@ -92,7 +96,7 @@ func init() {
Namespace: "sungrow", Namespace: "sungrow",
Subsystem: "tariff", Subsystem: "tariff",
Name: "import_tariff", Name: "import_tariff",
Help: "unit:$", Help: "units:$",
}, },
func() float64 { return tariff93.pricePerKWh(time.Now()) }, func() float64 { return tariff93.pricePerKWh(time.Now()) },
) )
@ -101,7 +105,7 @@ func init() {
Namespace: "sungrow", Namespace: "sungrow",
Subsystem: "tariff", Subsystem: "tariff",
Name: "export_tariff", Name: "export_tariff",
Help: "unit:$", Help: "units:$",
}, },
func() float64 { return solarFeedInTariff.pricePerKWh(time.Now()) }, func() float64 { return solarFeedInTariff.pricePerKWh(time.Now()) },
) )
@ -111,14 +115,26 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "current time: %v\n", time.Now()) fmt.Fprintf(w, "current time: %v\n", time.Now())
} }
func dialInverter() (*sungrowConn, error) {
for _, addr := range strings.Split(*inverterAddrs, ",") {
conn, err := dialSungrow(addr)
if err != nil {
log.Printf("Couldn't dial inverter: %v", err)
continue
}
return conn, nil
}
return nil, fmt.Errorf("all addresses unreachable")
}
// Called under scrapeMu. // Called under scrapeMu.
func readRegs(client modbus.Client, start, qty uint16) { func readRegs(client modbus.Client, start, qty uint16) error {
data, err := client.ReadInputRegisters(start, qty) data, err := client.ReadInputRegisters(start, qty)
if err != nil { if err != nil {
log.Fatalf("Couldn't read input registers %d-%d: %v", start+1, start+qty, err) return fmt.Errorf("read input registers %d-%d: %v", start+1, start+qty, err)
} }
if len(data) != int(2*qty) { if len(data) != int(2*qty) {
log.Fatalf("Couldn't read input registers %d-%d: len(data) = %d != %d = 2*qty", start+1, start+qty, len(data), 2*qty) return fmt.Errorf("reading input registers %d-%d: len(data) = %d != %d = 2*qty", start+1, start+qty, len(data), 2*qty)
} }
for addr, reg := range sungrowInputRegs { for addr, reg := range sungrowInputRegs {
if addr <= start || addr > start+qty { if addr <= start || addr > start+qty {
@ -130,56 +146,72 @@ func readRegs(client modbus.Client, start, qty uint16) {
log.Printf("Couldn't parse input register data at %d, skipping: %v", addr, err) log.Printf("Couldn't parse input register data at %d, skipping: %v", addr, err)
continue continue
} }
log.Fatalf("Couldn't parse input register data at %d: %v", addr, err) return fmt.Errorf("parsing input register data at %d: %v", addr, err)
} }
//fmt.Printf("%s: %v %s\n", reg.name, val, reg.unit) //fmt.Printf("%s: %v %s\n", reg.name, val, reg.unit)
lastValues[addr] = val lastValues[addr] = val
} }
return nil
} }
// Called under scrapeMu. // Called under scrapeMu.
func scrape(client modbus.Client) { func scrape() error {
sgc, err := dialInverter()
if err != nil {
return err
}
defer sgc.Close()
handler := modbus.TCPHandlerFromConnection(sgc)
handler.SlaveId = 0x01
//handler.Connect()
client := modbus.NewClient(handler)
start := time.Now() start := time.Now()
scrapeStart.SetToCurrentTime() scrapeStart.SetToCurrentTime()
readRegs(client, 5000, 50) if err := readRegs(client, 5000, 50); err != nil {
readRegs(client, 5050, 50) return err
readRegs(client, 5100, 50) }
if err := readRegs(client, 5050, 50); err != nil {
return err
}
if err := readRegs(client, 5100, 50); err != nil {
return err
}
scrapeEnd.SetToCurrentTime() scrapeEnd.SetToCurrentTime()
lastScrape = time.Now() lastScrape = time.Now()
scrapeDuration.Set(time.Since(start).Seconds()) scrapeDuration.Set(time.Since(start).Seconds())
scrapeCounter.Inc() scrapeCounter.Inc()
return nil
}
func metricsHandler(w http.ResponseWriter, r *http.Request) {
// In normal mode, always serve metrics
defer promHandler.ServeHTTP(w, r)
scrapeMu.Lock()
defer scrapeMu.Unlock()
if time.Since(lastScrape) <= maxScrapeAge {
return
}
var lastErr error
for i := 0; i < 3; i++ {
if err := scrape(); err != nil {
log.Printf("Retrying scrape; error: %v", err)
lastErr = err
continue
}
return
}
log.Fatalf("Scrape failed, bailing entirely: %v", lastErr)
} }
func main() { func main() {
flag.Parse() flag.Parse()
// Is the inverter reachable?
var sgc *sungrowConn
for _, addr := range strings.Split(*inverterAddrs, ",") {
conn, err := dialSungrow(addr)
if err != nil {
log.Printf("Couldn't dial inverter: %v", err)
continue
}
sgc = conn
defer conn.Close()
break
}
if sgc == nil {
log.Fatal("Couldn't dial any addresses, aborting")
}
// HTTP setup
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", statusHandler)
handler := modbus.TCPHandlerFromConnection(sgc)
handler.SlaveId = 0x01
//handler.Connect()
defer handler.Close()
client := modbus.NewClient(handler)
// Finally, create all the register gauges.
// These are GaugeFuncs to more closely align Prometheus scrape time with // These are GaugeFuncs to more closely align Prometheus scrape time with
// the modbus scrape time. // the modbus scrape time.
for addr, reg := range sungrowInputRegs { for addr, reg := range sungrowInputRegs {
@ -191,17 +223,20 @@ func main() {
Name: reg.name, Name: reg.name,
Help: fmt.Sprintf("addr: %d, unit: %s", addr, reg.unit), Help: fmt.Sprintf("addr: %d, unit: %s", addr, reg.unit),
}, },
func() float64 { func() float64 { return lastValues[addr] },
scrapeMu.Lock()
defer scrapeMu.Unlock()
if time.Since(lastScrape) <= maxScrapeAge {
return lastValues[addr]
}
scrape(client)
return lastValues[addr]
},
) )
} }
// Startup paranoia check: Is the inverter reachable?
sgc, err := dialInverter()
if err != nil {
log.Fatal("Couldn't dial any addresses, aborting")
}
sgc.Close()
// HTTP setup
http.HandleFunc("/metrics", metricsHandler)
http.HandleFunc("/", statusHandler)
log.Fatalf("http.ListenAndServe: %v", http.ListenAndServe(*httpAddr, nil)) log.Fatalf("http.ListenAndServe: %v", http.ListenAndServe(*httpAddr, nil))
} }