Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Homewizard: add cache #9158

Merged
merged 4 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 12 additions & 33 deletions charger/homewizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package charger

import (
"errors"
"fmt"
"net/http"
"time"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/homewizard"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
)

// HomeWizard project homepage
Expand All @@ -26,22 +24,25 @@ func init() {

// NewHomeWizardFromConfig creates a HomeWizard charger from generic config
func NewHomeWizardFromConfig(other map[string]interface{}) (api.Charger, error) {
var cc = struct {
cc := struct {
embed `mapstructure:",squash"`
URI string
StandbyPower float64
}{}
Cache time.Duration
}{
Cache: time.Second,
}

if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}

return NewHomeWizard(cc.embed, cc.URI, cc.StandbyPower)
return NewHomeWizard(cc.embed, cc.URI, cc.StandbyPower, cc.Cache)
}

// NewHomeWizard creates HomeWizard charger
func NewHomeWizard(embed embed, uri string, standbypower float64) (*HomeWizard, error) {
conn, err := homewizard.NewConnection(uri)
func NewHomeWizard(embed embed, uri string, standbypower float64, cache time.Duration) (*HomeWizard, error) {
conn, err := homewizard.NewConnection(uri, cache)
if err != nil {
return nil, err
}
Expand All @@ -52,7 +53,7 @@ func NewHomeWizard(embed embed, uri string, standbypower float64) (*HomeWizard,

// Check compatible product type
if c.conn.ProductType != "HWE-SKT" {
return nil, errors.New("not supported product type: " + c.conn.ProductType)
return nil, errors.New("unsupported product type: " + c.conn.ProductType)
}

c.switchSocket = NewSwitchSocket(&embed, c.Enabled, c.conn.CurrentPower, standbypower)
Expand All @@ -62,34 +63,12 @@ func NewHomeWizard(embed embed, uri string, standbypower float64) (*HomeWizard,

// Enabled implements the api.Charger interface
func (c *HomeWizard) Enabled() (bool, error) {
var res homewizard.StateResponse
err := c.conn.GetJSON(fmt.Sprintf("%s/data", c.conn.URI), &res)
return res.PowerOn, err
return c.conn.Enabled()
}

// Enable implements the api.Charger interface
func (c *HomeWizard) Enable(enable bool) error {
var res homewizard.StateResponse
data := map[string]interface{}{
"power_on": enable,
}

req, err := request.New(http.MethodPut, fmt.Sprintf("%s/state", c.conn.URI), request.MarshalJSON(data), request.JSONEncoding)
if err != nil {
return err
}
if err := c.conn.DoJSON(req, &res); err != nil {
return err
}

switch {
case enable && !res.PowerOn:
return errors.New("switchOn failed")
case !enable && res.PowerOn:
return errors.New("switchOff failed")
default:
return nil
}
return c.conn.Enable(enable)
}

var _ api.MeterEnergy = (*HomeWizard)(nil)
Expand Down
17 changes: 11 additions & 6 deletions meter/homewizard.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package meter

import (
"time"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/homewizard"
"github.com/evcc-io/evcc/util"
Expand All @@ -19,19 +21,22 @@ func init() {
// NewHomeWizardFromConfig creates a HomeWizard meter from generic config
func NewHomeWizardFromConfig(other map[string]interface{}) (api.Meter, error) {
cc := struct {
URI string
}{}
URI string
Cache time.Duration
}{
Cache: time.Second,
}

if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}

return NewHomeWizard(cc.URI)
return NewHomeWizard(cc.URI, cc.Cache)
}

// NewHomeWizard creates HomeWizard meter
func NewHomeWizard(uri string) (*HomeWizard, error) {
conn, err := homewizard.NewConnection(uri)
func NewHomeWizard(uri string, cache time.Duration) (*HomeWizard, error) {
conn, err := homewizard.NewConnection(uri, cache)
if err != nil {
return nil, err
}
Expand All @@ -40,7 +45,7 @@ func NewHomeWizard(uri string) (*HomeWizard, error) {
conn: conn,
}

return c, err
return c, nil
}

var _ api.Meter = (*HomeWizard)(nil)
Expand Down
77 changes: 64 additions & 13 deletions meter/homewizard/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package homewizard
import (
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
Expand All @@ -13,49 +16,97 @@ import (
// Connection is the homewizard connection
type Connection struct {
*request.Helper
URI string
uri string
ProductType string
dataCache provider.Cacheable[DataResponse]
stateCache provider.Cacheable[StateResponse]
}

// NewConnection creates a homewizard connection
func NewConnection(uri string) (*Connection, error) {
func NewConnection(uri string, cache time.Duration) (*Connection, error) {
if uri == "" {
return nil, errors.New("missing uri")
}

log := util.NewLogger("homewizard")
c := &Connection{
Helper: request.NewHelper(log),
URI: fmt.Sprintf("%s/api", util.DefaultScheme(strings.TrimRight(uri, "/"), "http")),
uri: fmt.Sprintf("%s/api", util.DefaultScheme(strings.TrimRight(uri, "/"), "http")),
}

c.Client.Transport = request.NewTripper(log, transport.Insecure())

// Check and set API version + product type
// check and set API version + product type
var res ApiResponse
if err := c.GetJSON(c.URI, &res); err != nil {
if err := c.GetJSON(c.uri, &res); err != nil {
return c, err
}
if res.ApiVersion != "v1" {
return nil, errors.New("not supported api version: " + res.ApiVersion)
return nil, errors.New("unsupported api version: " + res.ApiVersion)
}

c.URI = c.URI + "/" + res.ApiVersion
c.uri = c.uri + "/" + res.ApiVersion
c.ProductType = res.ProductType

c.dataCache = provider.ResettableCached(func() (DataResponse, error) {
var res DataResponse
err := c.GetJSON(fmt.Sprintf("%s/data", c.uri), &res)
return res, err
}, cache)

c.stateCache = provider.ResettableCached(func() (StateResponse, error) {
var res StateResponse
err := c.GetJSON(fmt.Sprintf("%s/state", c.uri), &res)
return res, err
}, cache)

return c, nil
}

// Enable implements the api.Charger interface
func (c *Connection) Enable(enable bool) error {
var res StateResponse
data := map[string]interface{}{
"power_on": enable,
}

req, err := request.New(http.MethodPut, fmt.Sprintf("%s/state", c.uri), request.MarshalJSON(data), request.JSONEncoding)
if err != nil {
return err
}
if err := c.DoJSON(req, &res); err != nil {
return err
}

if err == nil {
c.stateCache.Reset()
c.dataCache.Reset()
}

switch {
case enable && !res.PowerOn:
return errors.New("switchOn failed")
case !enable && res.PowerOn:
return errors.New("switchOff failed")
default:
return nil
}
}

// Enabled reads the homewizard switch state true=on/false=off
func (c *Connection) Enabled() (bool, error) {
res, err := c.stateCache.Get()
andig marked this conversation as resolved.
Show resolved Hide resolved
return res.PowerOn, err
}

// CurrentPower implements the api.Meter interface
func (d *Connection) CurrentPower() (float64, error) {
var res DataResponse
err := d.GetJSON(fmt.Sprintf("%s/data", d.URI), &res)
func (c *Connection) CurrentPower() (float64, error) {
res, err := c.dataCache.Get()
return res.ActivePowerW, err
}

// TotalEnergy implements the api.MeterEnergy interface
func (d *Connection) TotalEnergy() (float64, error) {
var res DataResponse
err := d.GetJSON(fmt.Sprintf("%s/data", d.URI), &res)
func (c *Connection) TotalEnergy() (float64, error) {
res, err := c.dataCache.Get()
return res.TotalPowerImportT1kWh + res.TotalPowerImportT2kWh + res.TotalPowerImportT3kWh + res.TotalPowerImportT4kWh, err
}