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)
|
solar := internal.NewSolar(client, deyeUser, deyePassword)
|
||||||
|
|
||||||
myCron := cron.New()
|
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()
|
myCron.Start()
|
||||||
|
|
||||||
// Listen to Ctrl+C (you can also do something else that prevents the program from exiting)
|
// 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 adminStatusEndpoint = "http://deye-solar-inverter/status.html"
|
||||||
const expectedDailyOutput float64 = 20
|
const expectedDailyOutput float64 = 20
|
||||||
|
const criticalBelowDailyOutput float64 = 10
|
||||||
|
|
||||||
var yieldTodayPattern = regexp.MustCompile(`var webdata_today_e = "([^"]+)";`)
|
var yieldTodayPattern = regexp.MustCompile(`var webdata_today_e = "([^"]+)";`)
|
||||||
var yieldTotalPattern = regexp.MustCompile(`var webdata_total_e = "([^"]+)";`)
|
var yieldTotalPattern = regexp.MustCompile(`var webdata_total_e = "([^"]+)";`)
|
||||||
|
|
||||||
|
type SolarMetrics struct {
|
||||||
|
lastUpdatedAt time.Time
|
||||||
|
today float64
|
||||||
|
total float64
|
||||||
|
}
|
||||||
|
|
||||||
type Solar struct {
|
type Solar struct {
|
||||||
client *Client
|
client *Client
|
||||||
channelId string
|
channelId string
|
||||||
adminUser string
|
adminUser string
|
||||||
adminPassword string
|
adminPassword string
|
||||||
|
metrics SolarMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSolar(client *Client, adminUser, adminPassword string) *Solar {
|
func NewSolar(client *Client, adminUser, adminPassword string) *Solar {
|
||||||
@ -38,39 +46,36 @@ func NewSolar(client *Client, adminUser, adminPassword string) *Solar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Solar) SendDailyReport() {
|
func (this *Solar) CronSendDailyReport() {
|
||||||
metrics, err := this.querySolarMetrics()
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[SOLAR] Metrics: %v\n", metrics)
|
fmt.Printf("[SOLAR] Metrics: %v\n", this.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")
|
|
||||||
|
|
||||||
message := fmt.Sprintf(
|
message := fmt.Sprintf(
|
||||||
"*🔆 Solar Production Report — %s*\n\n"+
|
messageHeaderLine()+
|
||||||
summary+
|
this.getSummary()+
|
||||||
"\n\n"+
|
"\n\n"+
|
||||||
"*Today: %.2f kWh*\n"+
|
"*Today: %.2f kWh*\n"+
|
||||||
"Total: %.2f kWh\n\n"+
|
"Total: %.2f kWh\n\n"+
|
||||||
"`DO NOT CLICK: %d`",
|
"_Last updated: %s_\n\n"+
|
||||||
todayFormatted,
|
messageDoNotClickLine(),
|
||||||
metrics.today,
|
this.metrics.today,
|
||||||
metrics.total,
|
this.metrics.total,
|
||||||
time.Now().Unix(),
|
time.Now().Format("03:04 PM, 2006-01-02"),
|
||||||
)
|
)
|
||||||
|
|
||||||
fmt.Printf("[SOLAR] Message to be sent:\n%s", message)
|
fmt.Printf("[SOLAR] Message to be sent:\n%s", message)
|
||||||
@ -81,10 +86,40 @@ func (this *Solar) SendDailyReport() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Solar) querySolarMetrics() (*struct {
|
func (this *Solar) getSummary() string {
|
||||||
today float64
|
deltaYieldToday := this.metrics.today - expectedDailyOutput
|
||||||
total float64
|
|
||||||
}, error) {
|
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)
|
req, err := http.NewRequest("GET", adminStatusEndpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -92,7 +127,6 @@ func (this *Solar) querySolarMetrics() (*struct {
|
|||||||
|
|
||||||
req.SetBasicAuth(this.adminUser, this.adminPassword)
|
req.SetBasicAuth(this.adminUser, this.adminPassword)
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -104,7 +138,6 @@ func (this *Solar) querySolarMetrics() (*struct {
|
|||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
rawBody, err := io.ReadAll(res.Body)
|
rawBody, err := io.ReadAll(res.Body)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -117,7 +150,6 @@ func (this *Solar) querySolarMetrics() (*struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
yieldToday, err := strconv.ParseFloat(matches[1], 64)
|
yieldToday, err := strconv.ParseFloat(matches[1], 64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to convert %s to float64: %w", matches[1], err)
|
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)
|
yieldTotal, err := strconv.ParseFloat(matches[1], 64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to convert %s to float64: %w", matches[1], err)
|
return nil, fmt.Errorf("Failed to convert %s to float64: %w", matches[1], err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &struct {
|
return &SolarMetrics{
|
||||||
today float64
|
lastUpdatedAt: time.Now(),
|
||||||
total float64
|
today: yieldToday,
|
||||||
}{
|
total: yieldTotal,
|
||||||
today: yieldToday,
|
|
||||||
total: yieldTotal,
|
|
||||||
}, nil
|
}, 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