Add solar usage reporting

This commit is contained in:
2025-04-12 16:57:19 +05:30
parent 3cd448d9ee
commit 9ed6c8cd99
7 changed files with 193 additions and 5 deletions

View File

@@ -178,7 +178,7 @@ func (client *Client) eventHandler(evt interface{}) {
time.Sleep(2 * time.Duration(rand.IntN(3)) * time.Second)
msg := proto.String(client.message + "\n\nIgnore this random number: `" + strconv.FormatInt(time.Now().UnixMilli(), 10) + "`")
msg := proto.String(client.message + "\n\nIgnore this random number: `" + strconv.FormatInt(time.Now().Unix(), 10) + "`")
imageResp, err := client.WAClient.Upload(context.Background(), waautoresponder.Bernie, whatsmeow.MediaImage)
@@ -216,3 +216,26 @@ func (client *Client) eventHandler(evt interface{}) {
client.updateAutoResponseTime(chatUserId)
}
}
func (client *Client) SendTextMessage(chatId string, message string) {
chatJID, err := types.ParseJID(chatId)
if err != nil {
fmt.Println("Error parsing JID:", err)
return
}
time.Sleep(2 * time.Duration(rand.IntN(3)) * time.Second)
client.WAClient.SendChatPresence(chatJID, types.ChatPresenceComposing, types.ChatPresenceMediaText)
time.Sleep(2 * time.Duration(rand.IntN(3)) * time.Second)
client.WAClient.SendMessage(
context.Background(),
chatJID,
&waE2E.Message{
Conversation: proto.String(message),
},
)
client.WAClient.SendChatPresence(chatJID, types.ChatPresencePaused, types.ChatPresenceMediaText)
}

144
internal/solar.go Normal file
View File

@@ -0,0 +1,144 @@
package internal
import (
"fmt"
"io"
"net/http"
"os"
"regexp"
"strconv"
"time"
)
const adminStatusEndpoint = "http://deye-solar-inverter/status.html"
const expectedDailyOutput float64 = 20
var yieldTodayPattern = regexp.MustCompile(`var webdata_today_e = "([^"]+)";`)
var yieldTotalPattern = regexp.MustCompile(`var webdata_total_e = "([^"]+)";`)
type Solar struct {
client *Client
channelId string
adminUser string
adminPassword string
}
func NewSolar(client *Client, adminUser, adminPassword string) *Solar {
channelId := os.Getenv("SOLAR_REPORT_CHANNEL_ID")
if channelId == "" {
panic("SOLAR_REPORT_CHANNEL_ID must be set")
}
return &Solar{
client: client,
adminUser: adminUser,
adminPassword: adminPassword,
channelId: channelId,
}
}
func (this *Solar) SendDailyReport() {
metrics, err := this.querySolarMetrics()
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")
message := fmt.Sprintf(
"*🔆 Solar Production Report, %s*\n\n"+
summary+
"\n\n"+
"Today: %.2f kWh\n"+
"Total: %.2f kWh\n\n"+
"`DO NOT CLICK: %d`",
todayFormatted,
metrics.today,
metrics.total,
time.Now().Unix(),
)
fmt.Printf("[SOLAR] Message to be sent:\n%s", message)
this.client.SendTextMessage(
this.channelId,
message,
)
}
func (this *Solar) querySolarMetrics() (*struct {
today float64
total float64
}, error) {
req, err := http.NewRequest("GET", adminStatusEndpoint, nil)
if err != nil {
panic(err)
}
req.SetBasicAuth(this.adminUser, this.adminPassword)
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Bad status %d querying status page", res.StatusCode)
}
defer res.Body.Close()
rawBody, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
html := string(rawBody)
matches := yieldTodayPattern.FindStringSubmatch(html)
if len(matches) != 2 {
return nil, fmt.Errorf("Regex failed to match today's yield")
}
yieldToday, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return nil, fmt.Errorf("Failed to convert %s to float64: %w", matches[1], err)
}
matches = yieldTotalPattern.FindStringSubmatch(html)
if len(matches) != 2 {
return nil, fmt.Errorf("Regex failed to match total yield")
}
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,
}, nil
}