solar: Collect metrics periodically
This commit is contained in:
parent
7627d48aaf
commit
ae1c064e52
@ -52,7 +52,8 @@ func main() {
|
||||
solar := internal.NewSolar(client, deyeUser, deyePassword)
|
||||
|
||||
myCron := cron.New()
|
||||
myCron.AddFunc("0 0 18 * * *", solar.SendDailyReport)
|
||||
myCron.AddFunc("0 0 18 * * *", solar.CronSendDailyReport)
|
||||
myCron.AddFunc("0 */15 * * * *", solar.CronCollectMetrics)
|
||||
myCron.Start()
|
||||
|
||||
// Listen to Ctrl+C (you can also do something else that prevents the program from exiting)
|
||||
|
@ -12,15 +12,23 @@ import (
|
||||
|
||||
const adminStatusEndpoint = "http://deye-solar-inverter/status.html"
|
||||
const expectedDailyOutput float64 = 20
|
||||
const criticalBelowDailyOutput float64 = 10
|
||||
|
||||
var yieldTodayPattern = regexp.MustCompile(`var webdata_today_e = "([^"]+)";`)
|
||||
var yieldTotalPattern = regexp.MustCompile(`var webdata_total_e = "([^"]+)";`)
|
||||
|
||||
type SolarMetrics struct {
|
||||
lastUpdatedAt time.Time
|
||||
today float64
|
||||
total float64
|
||||
}
|
||||
|
||||
type Solar struct {
|
||||
client *Client
|
||||
channelId string
|
||||
adminUser string
|
||||
adminPassword string
|
||||
metrics SolarMetrics
|
||||
}
|
||||
|
||||
func NewSolar(client *Client, adminUser, adminPassword string) *Solar {
|
||||
@ -38,39 +46,36 @@ func NewSolar(client *Client, adminUser, adminPassword string) *Solar {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Solar) SendDailyReport() {
|
||||
metrics, err := this.querySolarMetrics()
|
||||
func (this *Solar) CronSendDailyReport() {
|
||||
// TODO: Add retries?
|
||||
fmt.Println("[SOLAR] Cron job invoked to send daily report")
|
||||
defer fmt.Println("[SOLAR] Cron job completed to send daily report")
|
||||
|
||||
if this.metrics.lastUpdatedAt.IsZero() {
|
||||
fmt.Println("[SOLAR] Got zero value for lastUpdatedAt, means either inverter is not working properly or there was a long power outage or network connectivity problems")
|
||||
|
||||
this.client.SendTextMessage(
|
||||
this.channelId,
|
||||
messageHeaderLine()+
|
||||
"🔴 Couldn't collect any data today. Check logs, inverter and connectivity.",
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error querying soalr metrics:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[SOLAR] Metrics: %v\n", metrics)
|
||||
|
||||
deltaYieldToday := metrics.today - expectedDailyOutput
|
||||
|
||||
var summary string
|
||||
|
||||
if deltaYieldToday < 0 {
|
||||
summary = fmt.Sprintf("☹️ Below expected daily yield of %.2f kWh", expectedDailyOutput)
|
||||
} else {
|
||||
summary = fmt.Sprintf("😁 Above expected daily yield by %.2f kWh", deltaYieldToday)
|
||||
}
|
||||
|
||||
var todayFormatted = time.Now().Format("Monday, 2 Jan 2006")
|
||||
fmt.Printf("[SOLAR] Metrics: %v\n", this.metrics)
|
||||
|
||||
message := fmt.Sprintf(
|
||||
"*🔆 Solar Production Report — %s*\n\n"+
|
||||
summary+
|
||||
messageHeaderLine()+
|
||||
this.getSummary()+
|
||||
"\n\n"+
|
||||
"*Today: %.2f kWh*\n"+
|
||||
"Total: %.2f kWh\n\n"+
|
||||
"`DO NOT CLICK: %d`",
|
||||
todayFormatted,
|
||||
metrics.today,
|
||||
metrics.total,
|
||||
time.Now().Unix(),
|
||||
"_Last updated: %s_\n\n"+
|
||||
messageDoNotClickLine(),
|
||||
this.metrics.today,
|
||||
this.metrics.total,
|
||||
time.Now().Format("03:04 PM, 2006-01-02"),
|
||||
)
|
||||
|
||||
fmt.Printf("[SOLAR] Message to be sent:\n%s", message)
|
||||
@ -81,10 +86,40 @@ func (this *Solar) SendDailyReport() {
|
||||
)
|
||||
}
|
||||
|
||||
func (this *Solar) querySolarMetrics() (*struct {
|
||||
today float64
|
||||
total float64
|
||||
}, error) {
|
||||
func (this *Solar) getSummary() string {
|
||||
deltaYieldToday := this.metrics.today - expectedDailyOutput
|
||||
|
||||
if this.metrics.today < criticalBelowDailyOutput {
|
||||
return fmt.Sprintf("⚠️ Yield was only %.2f kWh today, well below expected %.2f kWh. Check if this was due to bad weather/powercut or if panels need maintenance.", this.metrics.today, expectedDailyOutput)
|
||||
} else if deltaYieldToday < 0 {
|
||||
return fmt.Sprintf("☹️ Below expected daily yield of %.2f kWh", expectedDailyOutput)
|
||||
} else {
|
||||
return fmt.Sprintf("😁 Above expected daily yield by %.2f kWh", deltaYieldToday)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Solar) CronCollectMetrics() {
|
||||
fmt.Println("[SOLAR] Cron job invoked to collect fresh metrics")
|
||||
defer fmt.Println("[SOLAR] Cron job completed to collect fresh metrics")
|
||||
|
||||
// Reset if we rolled over to next day
|
||||
if !AreSameDay(time.Now(), this.metrics.lastUpdatedAt) {
|
||||
this.metrics = SolarMetrics{}
|
||||
}
|
||||
|
||||
metrics, err := this.querySolarMetrics()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("[SOLAR] Error querying solar metrics:", err)
|
||||
return
|
||||
}
|
||||
|
||||
this.metrics = *metrics
|
||||
|
||||
fmt.Printf("[SOLAR] Updated metrics: %+v\n", this.metrics)
|
||||
}
|
||||
|
||||
func (this *Solar) querySolarMetrics() (*SolarMetrics, error) {
|
||||
req, err := http.NewRequest("GET", adminStatusEndpoint, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -92,7 +127,6 @@ func (this *Solar) querySolarMetrics() (*struct {
|
||||
|
||||
req.SetBasicAuth(this.adminUser, this.adminPassword)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -104,7 +138,6 @@ func (this *Solar) querySolarMetrics() (*struct {
|
||||
defer res.Body.Close()
|
||||
|
||||
rawBody, err := io.ReadAll(res.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -117,7 +150,6 @@ func (this *Solar) querySolarMetrics() (*struct {
|
||||
}
|
||||
|
||||
yieldToday, err := strconv.ParseFloat(matches[1], 64)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to convert %s to float64: %w", matches[1], err)
|
||||
}
|
||||
@ -129,16 +161,21 @@ func (this *Solar) querySolarMetrics() (*struct {
|
||||
}
|
||||
|
||||
yieldTotal, err := strconv.ParseFloat(matches[1], 64)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to convert %s to float64: %w", matches[1], err)
|
||||
}
|
||||
|
||||
return &struct {
|
||||
today float64
|
||||
total float64
|
||||
}{
|
||||
today: yieldToday,
|
||||
total: yieldTotal,
|
||||
return &SolarMetrics{
|
||||
lastUpdatedAt: time.Now(),
|
||||
today: yieldToday,
|
||||
total: yieldTotal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func messageDoNotClickLine() string {
|
||||
return fmt.Sprintf("`DO NOT CLICK: %d`", time.Now().Unix())
|
||||
}
|
||||
|
||||
func messageHeaderLine() string {
|
||||
return fmt.Sprintf("*🔆 Solar Production Report — %s*\n\n", time.Now().Format("2 Jan 2006"))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user