evcc-io/meter/bosch/api.go
2023-12-06 18:29:32 +01:00

144 lines
3 KiB
Go

package bosch
import (
"fmt"
"net/http"
"net/http/cookiejar"
"strconv"
"strings"
"sync"
"time"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
)
type API struct {
*request.Helper
uri string
status StatusResponse
login LoginResponse
updated time.Time
cache time.Duration
}
var Instances = new(sync.Map)
func NewLocal(log *util.Logger, uri string, cache time.Duration) *API {
api := &API{
Helper: request.NewHelper(log),
uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"),
cache: cache,
}
// ignore the self signed certificate
api.Client.Transport = request.NewTripper(log, transport.Insecure())
// create cookie jar to save login tokens
api.Client.Jar, _ = cookiejar.New(nil)
return api
}
func (c *API) Login() (err error) {
req, err := request.New(http.MethodGet, c.uri, nil, nil)
if err != nil {
return err
}
body, err := c.DoBody(req)
if err != nil {
return err
}
return c.extractWuiSidFromBody(string(body))
}
func (c *API) Status() (StatusResponse, error) {
var err error
if time.Since(c.updated) > c.cache {
if err = c.updateValues(); err == nil {
c.updated = time.Now()
}
}
return c.status, err
}
func (c *API) extractWuiSidFromBody(body string) error {
index := strings.Index(body, "WUI_SID=")
if index < 0 || len(body) < index+9+15 {
c.login.wuSid = ""
return fmt.Errorf("error while extracting wui sid. body was= %s", body)
}
c.login.wuSid = body[index+9 : index+9+15]
return nil
}
func (c *API) updateValues() error {
data := "action=get.hyb.overview&flow=1"
uri := c.uri + "/cgi-bin/ipcclient.fcgi?" + c.login.wuSid
req, err := request.New(http.MethodPost, uri, strings.NewReader(data), map[string]string{
"Content-Type": "text/plain",
})
if err != nil {
return err
}
body, err := c.DoBody(req)
if err != nil {
return err
}
return c.extractValues(string(body))
}
func (c *API) extractValues(body string) error {
if strings.Contains(body, "session invalid") {
return c.Login()
}
values := strings.Split(body, "|")
if len(values) < 14 {
return fmt.Errorf("extractValues: response has not enough values")
}
soc, err := strconv.Atoi(values[3])
if err == nil {
c.status.CurrentBatterySoc = float64(soc)
c.status.SellToGrid, err = parseWattValue(values[11])
}
if err == nil {
c.status.BuyFromGrid, err = parseWattValue(values[14])
}
if err == nil {
c.status.PvPower, err = parseWattValue(values[2])
}
if err == nil {
c.status.BatteryChargePower, err = parseWattValue(values[10])
}
if err == nil {
c.status.BatteryDischargePower, err = parseWattValue(values[13])
}
return err
}
func parseWattValue(inputString string) (float64, error) {
if len(strings.TrimSpace(inputString)) == 0 || strings.Contains(inputString, "nbsp;") {
return 0.0, nil
}
num := strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(inputString, "kW", " "), "von", " "))
res, err := strconv.ParseFloat(num, 64)
return res * 1000.0, err
}