From e7ee87b19a23566b0eaaeafcd03a301d346bce71 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 26 Aug 2020 19:25:07 +0200 Subject: [PATCH] Implement composition of optional interfaces using generator (#310) --- Makefile | 1 - charger/evsewifi.go | 59 ++++++--- charger/evsewifi_decorators.go | 69 ++++++++++ charger/evsewifi_test.go | 29 +++++ cmd/tools/decorate.go | 227 +++++++++++++++++++++++++++++++++ go.mod | 3 +- go.sum | 2 + meter/meter.go | 122 +++++------------- meter/meter_decorators.go | 69 ++++++++++ meter/modbus.go | 16 +-- meter/modbus_decorators.go | 35 +++++ meter/sma.go | 20 ++- meter/sma_decorators.go | 35 +++++ meter/tesla.go | 16 +-- meter/tesla_decorators.go | 35 +++++ tools.go | 1 - 16 files changed, 596 insertions(+), 143 deletions(-) create mode 100644 charger/evsewifi_decorators.go create mode 100644 charger/evsewifi_test.go create mode 100644 cmd/tools/decorate.go create mode 100644 meter/meter_decorators.go create mode 100644 meter/modbus_decorators.go create mode 100644 meter/sma_decorators.go create mode 100644 meter/tesla_decorators.go diff --git a/Makefile b/Makefile index af606ecac..4e9508947 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,6 @@ clean: rm -rf dist/ install: - go install golang.org/x/tools/cmd/stringer go install github.com/mjibson/esc go install github.com/golang/mock/mockgen diff --git a/charger/evsewifi.go b/charger/evsewifi.go index ecaef40bb..b7ca3bed8 100644 --- a/charger/evsewifi.go +++ b/charger/evsewifi.go @@ -46,7 +46,7 @@ type EVSEListEntry struct { // EVSEWifi charger implementation type EVSEWifi struct { *util.HTTPHelper - uri string + uri string alwaysActive bool } @@ -54,22 +54,45 @@ func init() { registry.Add("evsewifi", NewEVSEWifiFromConfig) } +//go:generate go run ../cmd/tools/decorate.go -p charger -f decorateEVSE -b api.Charger -o evsewifi_decorators -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" + // NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) { - cc := struct{ URI string }{} + cc := struct { + URI string + Meter struct { + Power, Energy, Currents bool + } + }{} if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - return NewEVSEWifi(cc.URI) + evse, err := NewEVSEWifi(cc.URI) + if err != nil { + return evse, err + } + + // decorate Charger with MeterEnergy + var totalEnergy func() (float64, error) + if cc.Meter.Energy { + totalEnergy = evse.totalEnergy + } + + // decorate Charger with MeterCurrent + var currents func() (float64, float64, float64, error) + if cc.Meter.Currents { + currents = evse.currents + } + + return decorateEVSE(evse, totalEnergy, currents), nil } // NewEVSEWifi creates EVSEWifi charger -func NewEVSEWifi(uri string) (api.Charger, error) { +func NewEVSEWifi(uri string) (*EVSEWifi, error) { evse := &EVSEWifi{ - HTTPHelper: util.NewHTTPHelper(util.NewLogger("wifi")), - uri: strings.TrimRight(uri, "/"), - alwaysActive: true, + HTTPHelper: util.NewHTTPHelper(util.NewLogger("wifi")), + uri: strings.TrimRight(uri, "/"), } return evse, nil @@ -164,20 +187,20 @@ func (evse *EVSEWifi) ChargingTime() (time.Duration, error) { return time.Duration(params.Duration) * time.Millisecond, err } -// // TotalEnergy implements the MeterEnergy interface -// func (evse *EVSEWifi) TotalEnergy() (float64, error) { -// params, err := evse.getParameters() -// return params.MeterReading, err -// } +// TotalEnergy implements the MeterEnergy interface +func (evse *EVSEWifi) totalEnergy() (float64, error) { + params, err := evse.getParameters() + return params.MeterReading, err +} + +// Currents implements the MeterCurrents interface +func (evse *EVSEWifi) currents() (float64, float64, float64, error) { + params, err := evse.getParameters() + return float64(params.CurrentP1), float64(params.CurrentP2), float64(params.CurrentP3), err +} // // ChargedEnergy implements the ChargeRater interface // func (evse *EVSEWifi) ChargedEnergy() (float64, error) { // params, err := evse.getParameters() // return params.Energy, err // } - -// // Currents implements the MeterCurrents interface -// func (evse *EVSEWifi) Currents() (float64, float64, float64, error) { -// params, err := evse.getParameters() -// return float64(params.CurrentP1), float64(params.CurrentP2), float64(params.CurrentP3), err -// } diff --git a/charger/evsewifi_decorators.go b/charger/evsewifi_decorators.go new file mode 100644 index 000000000..d7b0c7eda --- /dev/null +++ b/charger/evsewifi_decorators.go @@ -0,0 +1,69 @@ +package charger + +// This file has been generated - do not modify + +import ( + "github.com/andig/evcc/api" +) + +func decorateEVSE(base api.Charger, meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error)) api.Charger { + switch { + case meterCurrent == nil && meterEnergy == nil: + return base + + case meterCurrent == nil && meterEnergy != nil: + return &struct{ + api.Charger + api.MeterEnergy + }{ + Charger: base, + MeterEnergy: &decorateEVSEMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meterCurrent != nil && meterEnergy == nil: + return &struct{ + api.Charger + api.MeterCurrent + }{ + Charger: base, + MeterCurrent: &decorateEVSEMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case meterCurrent != nil && meterEnergy != nil: + return &struct{ + api.Charger + api.MeterCurrent + api.MeterEnergy + }{ + Charger: base, + MeterCurrent: &decorateEVSEMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decorateEVSEMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decorateEVSEMeterCurrentImpl struct { + meterCurrent func() (float64, float64, float64, error) +} + +func (impl *decorateEVSEMeterCurrentImpl) Currents() (float64, float64, float64, error) { + return impl.meterCurrent() +} + +type decorateEVSEMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decorateEVSEMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +} diff --git a/charger/evsewifi_test.go b/charger/evsewifi_test.go new file mode 100644 index 000000000..e72e425d5 --- /dev/null +++ b/charger/evsewifi_test.go @@ -0,0 +1,29 @@ +package charger + +import ( + "testing" + + "github.com/andig/evcc/api" +) + +func TestEvseWifi(t *testing.T) { + evse, err := NewEVSEWifiFromConfig(map[string]interface{}{ + "meter": map[string]interface{}{ + "power": true, + "energy": true, + "currents": true, + }, + }) + + if err != nil { + t.Error(err) + } + + if _, ok := evse.(api.MeterEnergy); !ok { + t.Error("missing api.MeterEnergy") + } + + if _, ok := evse.(api.MeterCurrent); !ok { + t.Error("missing api.MeterCurrent") + } +} diff --git a/cmd/tools/decorate.go b/cmd/tools/decorate.go new file mode 100644 index 000000000..e54b62f47 --- /dev/null +++ b/cmd/tools/decorate.go @@ -0,0 +1,227 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strings" + "text/template" + + combinations "github.com/mxschmitt/golang-combinations" + "github.com/spf13/pflag" +) + +var srcTmpl = ` +package {{.Package}} + +// This file has been generated - do not modify + +import ( + "{{.API}}" +) + +{{define "case"}} + {{- $combo := .Combo}} + {{- $prefix := .Prefix}} + {{- $idx := 0}} + + {{- range $typ, $def := .Types}} + {{- if gt $idx 0}} &&{{else}}{{$idx = 1}}{{end}} {{$def.VarName}} {{if contains $combo $typ}}!={{else}}=={{end}} nil + {{- end}}: + return &struct{ + {{.BaseType}} +{{- range $typ, $def := .Types}} + {{- if contains $combo $typ}} + {{$typ}} + {{- end}} +{{- end}} + }{ + {{.ShortBase}}: base, +{{- range $typ, $def := .Types}} + {{- if contains $combo $typ}} + {{$def.ShortType}}: &{{$prefix}}{{$def.ShortType}}Impl{ + {{$def.VarName}}: {{$def.VarName}}, + }, + {{- end}} +{{- end}} + } +{{- end -}} + +func {{.Function}}(base {{.BaseType}}{{range ordered}}, {{.VarName}} func() {{slice .Signature 7}}{{end}}) {{.BaseType}} { +{{- $basetype := .BaseType}} +{{- $shortbase := .ShortBase}} +{{- $prefix := .Function}} +{{- $types := .Types}} +{{- $idx := 0}} + switch { + case {{- range $typ, $def := .Types}} + {{- if gt $idx 0}} &&{{else}}{{$idx = 1}}{{end}} {{$def.VarName}} == nil + {{- end}}: + return base +{{range $combo := .Combinations}} + case {{- template "case" dict "BaseType" $basetype "Prefix" $prefix "ShortBase" $shortbase "Types" $types "Combo" $combo}} +{{end}} } + + return nil +} + +{{range .Types -}} +type {{$prefix}}{{.ShortType}}Impl struct { + {{.VarName}} {{.Signature}} +} + +func (impl *{{$prefix}}{{.ShortType}}Impl) {{.Function}}{{slice .Signature 4}} { + return impl.{{.VarName}}() +} + +{{end}} +` + +type dynamicType struct { + typ, function, signature string +} + +type typeStruct struct { + Type, ShortType, Signature, Function, VarName string +} + +func generate(out io.Writer, packageName, functionName, baseType string, dynamicTypes ...dynamicType) { + types := make(map[string]typeStruct, len(dynamicTypes)) + combos := make([]string, 0) + + tmpl, err := template.New("gen").Funcs(template.FuncMap{ + // dict combines key value pairs for passing structs into templates + "dict": func(values ...interface{}) (map[string]interface{}, error) { + if len(values)%2 != 0 { + return nil, errors.New("invalid dict call") + } + dict := make(map[string]interface{}, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].(string) + if !ok { + return nil, errors.New("dict keys must be strings") + } + dict[key] = values[i+1] + } + return dict, nil + }, + // contains checks if slice contains string + "contains": func(combo []string, typ string) bool { + for _, v := range combo { + if v == typ { + return true + } + } + return false + }, + // ordered checks if slice ordered string + "ordered": func() []typeStruct { + ordered := make([]typeStruct, 0) + for _, k := range dynamicTypes { + ordered = append(ordered, types[k.typ]) + } + + return ordered + }, + }).Parse(srcTmpl) + if err != nil { + panic(err) + } + + for _, dt := range dynamicTypes { + parts := strings.SplitN(dt.typ, ".", 2) + + types[dt.typ] = typeStruct{ + Type: dt.typ, + ShortType: parts[1], + VarName: strings.ToLower(parts[1][:1]) + parts[1][1:], + Signature: dt.signature, + Function: dt.function, + } + + combos = append(combos, dt.typ) + } + + baseTypeParts := strings.SplitN(baseType, ".", 2) + vars := struct { + API string + Package, Function string + BaseType, ShortBase string + Types map[string]typeStruct + Combinations [][]string + }{ + API: "github.com/andig/evcc/api", + Package: packageName, + Function: functionName, + BaseType: baseType, + ShortBase: baseTypeParts[1], + Types: types, + Combinations: combinations.All(combos), + } + + if err := tmpl.Execute(out, vars); err != nil { + println(err) + os.Exit(2) + } +} + +var ( + target = pflag.StringP("out", "o", "", "output file") + pkg = pflag.StringP("package", "p", "", "package name") + function = pflag.StringP("function", "f", "decorate", "function name") + base = pflag.StringP("base", "b", "", "base type") + types = pflag.StringArrayP("type", "t", nil, "comma-separated list of type definitions") +) + +// Usage prints flags usage +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of decorate:\n") + fmt.Fprintf(os.Stderr, "\ndecorate [flags] -type interface,interface function,function signature\n") + fmt.Fprintf(os.Stderr, "\nFlags:\n") + pflag.PrintDefaults() +} + +func main() { + pflag.Usage = Usage + pflag.Parse() + + if *base == "" || *pkg == "" || len(*types) == 0 { + Usage() + os.Exit(2) + } + + var dynamicTypes []dynamicType + for _, v := range *types { + split := strings.SplitN(v, ",", 3) + dt := dynamicType{split[0], split[1], split[2]} + dynamicTypes = append(dynamicTypes, dt) + } + + var buf bytes.Buffer + generate(&buf, *pkg, *function, *base, dynamicTypes...) + generated := strings.TrimSpace(buf.String()) + "\n" + + var out io.Writer = os.Stdout + if *target != "" { + name := *target + if !strings.HasSuffix(name, ".go") { + name += ".go" + } + + dst, err := os.Create(name) + if err != nil { + println(err) + os.Exit(2) + } + + defer dst.Close() + out = dst + } + + if _, err := out.Write([]byte(generated)); err != nil { + println(err) + os.Exit(2) + } +} diff --git a/go.mod b/go.mod index 30b3076aa..5503e9ac5 100644 --- a/go.mod +++ b/go.mod @@ -29,14 +29,15 @@ require ( github.com/mitchellh/mapstructure v1.3.2 github.com/mjibson/esc v0.2.0 github.com/muka/go-bluetooth v0.0.0-20200619025933-f6113f7141c5 + github.com/mxschmitt/golang-combinations v1.0.0 github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da github.com/spf13/cobra v1.0.0 github.com/spf13/jwalterweatherman v1.1.0 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/volkszaehler/mbmd v0.0.0-20200804054214-122815825570 golang.org/x/net v0.0.0-20200707034311-ab3426394381 - golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6 gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c ) diff --git a/go.sum b/go.sum index 1b5b9c30e..b53443c8b 100644 --- a/go.sum +++ b/go.sum @@ -266,6 +266,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/muka/go-bluetooth v0.0.0-20200619025933-f6113f7141c5 h1:xnTS/7y0g28W2SJeWNLMYTiTOmfW2P/YdPByoQnPvVo= github.com/muka/go-bluetooth v0.0.0-20200619025933-f6113f7141c5/go.mod h1:yV39+EVOWdnoTe75NyKdo9iuyI3Slyh4t7eQvElUbWE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxschmitt/golang-combinations v1.0.0 h1:NFoO7CSP8MUcFlHpe1YdewKwMa15dgDbaqkVLC5DUPI= +github.com/mxschmitt/golang-combinations v1.0.0/go.mod h1:RbMhWvfCelHR6WROvT2bVfxJvZHoEvBj71SKe+H0MYU= github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da h1:qiPWuGGr+1GQE6s9NPSK8iggR/6x/V+0snIoOPYsBgc= github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da/go.mod h1:DvuJJ/w1Y59rG8UTDxsMk5U+UJXJwuvUgbiJSm9yhX8= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= diff --git a/meter/meter.go b/meter/meter.go index 8e324599e..b3f32b12c 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -13,6 +13,8 @@ func init() { registry.Add("default", NewConfigurableFromConfig) } +//go:generate go run ../cmd/tools/decorate.go -p meter -f decorateMeter -b api.Meter -o meter_decorators -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" + // NewConfigurableFromConfig creates api.Meter from config func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { @@ -39,63 +41,42 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) m, _ := NewConfigurable(power) // decorate Meter with MeterEnergy + var totalEnergy func() (float64, error) if cc.Energy != nil { - energy, err := NewMeterEnergy(*cc.Energy) + m.totalEnergyG, err = provider.NewFloatGetterFromConfig(*cc.Energy) if err != nil { return nil, err } - - type EnergyDecorator struct { - api.Meter - api.MeterEnergy - } - - m = &EnergyDecorator{ - Meter: m, - MeterEnergy: energy, - } + totalEnergy = m.totalEnergy } // decorate Meter with MeterCurrent + var currents func() (float64, float64, float64, error) if len(cc.Currents) > 0 { - currents, err := NewCurrents(cc.Currents) - if err != nil { - return nil, err + if len(cc.Currents) != 3 { + return nil, errors.New("need 3 currents") } - type PowerEnergy interface { - api.Meter - api.MeterEnergy + var currentsG []func() (float64, error) + for _, cc := range cc.Currents { + c, err := provider.NewFloatGetterFromConfig(cc) + if err != nil { + return nil, err + } + + m.currentsG = append(currentsG, c) } - if pe, ok := m.(PowerEnergy); ok { - type CurrentDecorator struct { - PowerEnergy - api.MeterCurrent - } - - m = &CurrentDecorator{ - PowerEnergy: pe, - MeterCurrent: currents, - } - } else { - type CurrentDecorator struct { - api.Meter - api.MeterCurrent - } - - m = &CurrentDecorator{ - Meter: m, - MeterCurrent: currents, - } - } + currents = m.currents } - return m, nil + res := decorateMeter(m, totalEnergy, currents) + + return res, nil } -// NewConfigurable creates a new charger -func NewConfigurable(currentPowerG func() (float64, error)) (api.Meter, error) { +// NewConfigurable creates a new meter +func NewConfigurable(currentPowerG func() (float64, error)) (*Meter, error) { m := &Meter{ currentPowerG: currentPowerG, } @@ -105,6 +86,8 @@ func NewConfigurable(currentPowerG func() (float64, error)) (api.Meter, error) { // Meter is an api.Meter implementation with configurable getters and setters. type Meter struct { currentPowerG func() (float64, error) + totalEnergyG func() (float64, error) + currentsG []func() (float64, error) } // CurrentPower implements the Meter.CurrentPower interface @@ -112,62 +95,15 @@ func (m *Meter) CurrentPower() (float64, error) { return m.currentPowerG() } -// MeterEnergy is an api.MeterEnergy implementation with configurable getters and setters. -type MeterEnergy struct { - totalEnergyG func() (float64, error) -} - -// NewMeterEnergy creates a new api.MeterEnergy -func NewMeterEnergy(ccEnergy provider.Config) (api.MeterEnergy, error) { - totalEnergyG, err := provider.NewFloatGetterFromConfig(ccEnergy) - if err != nil { - return nil, err - } - - e := &MeterEnergy{ - totalEnergyG: totalEnergyG, - } - - return e, nil -} - -// TotalEnergy implements the Meter.TotalEnergy interface -func (m *MeterEnergy) TotalEnergy() (float64, error) { +// totalEnergy implements the Meter.TotalEnergy interface +func (m *Meter) totalEnergy() (float64, error) { return m.totalEnergyG() } -// Currents is an api.MeterCurrent implementation -type Currents struct { - currentsG []func() (float64, error) -} - -// NewCurrents creates a new api.MeterCurrent -func NewCurrents(ccCurrents []provider.Config) (api.MeterCurrent, error) { - if len(ccCurrents) != 3 { - return nil, errors.New("need 3 currents") - } - - var currentsG []func() (float64, error) - for _, cc := range ccCurrents { - c, err := provider.NewFloatGetterFromConfig(cc) - if err != nil { - return nil, err - } - - currentsG = append(currentsG, c) - } - - c := &Currents{ - currentsG: currentsG, - } - - return c, nil -} - -// Currents implements the api.Currents interface -func (c *Currents) Currents() (float64, float64, float64, error) { +// currents implements the Meter.Currents interface +func (m *Meter) currents() (float64, float64, float64, error) { var currents []float64 - for _, currentG := range c.currentsG { + for _, currentG := range m.currentsG { c, err := currentG() if err != nil { return 0, 0, 0, err diff --git a/meter/meter_decorators.go b/meter/meter_decorators.go new file mode 100644 index 000000000..42f29ad60 --- /dev/null +++ b/meter/meter_decorators.go @@ -0,0 +1,69 @@ +package meter + +// This file has been generated - do not modify + +import ( + "github.com/andig/evcc/api" +) + +func decorateMeter(base api.Meter, meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error)) api.Meter { + switch { + case meterCurrent == nil && meterEnergy == nil: + return base + + case meterCurrent == nil && meterEnergy != nil: + return &struct{ + api.Meter + api.MeterEnergy + }{ + Meter: base, + MeterEnergy: &decorateMeterMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case meterCurrent != nil && meterEnergy == nil: + return &struct{ + api.Meter + api.MeterCurrent + }{ + Meter: base, + MeterCurrent: &decorateMeterMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case meterCurrent != nil && meterEnergy != nil: + return &struct{ + api.Meter + api.MeterCurrent + api.MeterEnergy + }{ + Meter: base, + MeterCurrent: &decorateMeterMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decorateMeterMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decorateMeterMeterCurrentImpl struct { + meterCurrent func() (float64, float64, float64, error) +} + +func (impl *decorateMeterMeterCurrentImpl) Currents() (float64, float64, float64, error) { + return impl.meterCurrent() +} + +type decorateMeterMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decorateMeterMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +} diff --git a/meter/modbus.go b/meter/modbus.go index 23fcd0f9b..cb2959edf 100644 --- a/meter/modbus.go +++ b/meter/modbus.go @@ -25,6 +25,8 @@ func init() { registry.Add("modbus", NewModbusFromConfig) } +//go:generate go run ../cmd/tools/decorate.go -p meter -f decorateModbus -b api.Meter -o modbus_decorators -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" + // NewModbusFromConfig creates api.Meter from config func NewModbusFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { @@ -88,15 +90,16 @@ func NewModbusFromConfig(other map[string]interface{}) (api.Meter, error) { } // decorate energy reading + var totalEnergy func() (float64, error) if cc.Energy != "" { if err := modbus.ParseOperation(device, cc.Energy, &m.opEnergy); err != nil { log.FATAL.Fatalf("invalid measurement for energy: %s", cc.Power) } - return &ModbusEnergy{m}, nil + totalEnergy = m.totalEnergy } - return m, nil + return decorateModbus(m, totalEnergy), nil } // floatGetter executes configured modbus read operation and implements func() (float64, error) @@ -143,12 +146,7 @@ func (m *Modbus) CurrentPower() (float64, error) { return m.floatGetter(m.opPower) } -// ModbusEnergy decorates Modbus with api.MeterEnergy interface -type ModbusEnergy struct { - *Modbus -} - -// TotalEnergy implements the Meter.TotalEnergy interface -func (m *ModbusEnergy) TotalEnergy() (float64, error) { +// totalEnergy implements the Meter.TotalEnergy interface +func (m *Modbus) totalEnergy() (float64, error) { return m.floatGetter(m.opEnergy) } diff --git a/meter/modbus_decorators.go b/meter/modbus_decorators.go new file mode 100644 index 000000000..12b92870d --- /dev/null +++ b/meter/modbus_decorators.go @@ -0,0 +1,35 @@ +package meter + +// This file has been generated - do not modify + +import ( + "github.com/andig/evcc/api" +) + +func decorateModbus(base api.Meter, meterEnergy func() (float64, error)) api.Meter { + switch { + case meterEnergy == nil: + return base + + case meterEnergy != nil: + return &struct{ + api.Meter + api.MeterEnergy + }{ + Meter: base, + MeterEnergy: &decorateModbusMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decorateModbusMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decorateModbusMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +} diff --git a/meter/sma.go b/meter/sma.go index f2dddc371..ae7ec1970 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -37,6 +37,8 @@ func init() { registry.Add("sma", NewSMAFromConfig) } +//go:generate go run ../cmd/tools/decorate.go -p meter -f decorateSMA -b api.Meter -o sma_decorators -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" + // NewSMAFromConfig creates a SMA Meter from generic config func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { @@ -77,14 +79,15 @@ func NewSMA(uri, serial, power, energy string) (api.Meter, error) { return nil, errors.New("missing uri or serial") } - go sm.receive() - // decorate api.MeterEnergy + var totalEnergy func() (float64, error) if energy != "" { - return &SMAEnergy{SMA: sm}, nil + totalEnergy = sm.totalEnergy } - return sm, nil + go sm.receive() + + return decorateSMA(sm, totalEnergy), nil } // update the actual meter data @@ -168,13 +171,8 @@ func (sm *SMA) Currents() (float64, float64, float64, error) { return values.currentL1, sm.values.currentL2, sm.values.currentL3, err } -// SMAEnergy decorates SMA with api.MeterEnergy interface -type SMAEnergy struct { - *SMA -} - -// TotalEnergy implements the api.MeterEnergy interface -func (sm *SMAEnergy) TotalEnergy() (float64, error) { +// totalEnergy implements the api.MeterEnergy interface +func (sm *SMA) totalEnergy() (float64, error) { values, err := sm.hasValue() return values.energy, err } diff --git a/meter/sma_decorators.go b/meter/sma_decorators.go new file mode 100644 index 000000000..cfdfc382b --- /dev/null +++ b/meter/sma_decorators.go @@ -0,0 +1,35 @@ +package meter + +// This file has been generated - do not modify + +import ( + "github.com/andig/evcc/api" +) + +func decorateSMA(base api.Meter, meterEnergy func() (float64, error)) api.Meter { + switch { + case meterEnergy == nil: + return base + + case meterEnergy != nil: + return &struct{ + api.Meter + api.MeterEnergy + }{ + Meter: base, + MeterEnergy: &decorateSMAMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decorateSMAMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decorateSMAMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +} diff --git a/meter/tesla.go b/meter/tesla.go index 64104e228..d773bc49b 100644 --- a/meter/tesla.go +++ b/meter/tesla.go @@ -39,6 +39,8 @@ func init() { registry.Add("tesla", NewTeslaFromConfig) } +//go:generate go run ../cmd/tools/decorate.go -p meter -f decorateTesla -b api.Meter -o tesla_decorators -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" + // NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) { cc := struct { @@ -80,11 +82,12 @@ func NewTesla(uri, usage string) (api.Meter, error) { m.HTTPHelper.Client.Transport = customTransport // decorate api.MeterEnergy + var totalEnergy func() (float64, error) if m.usage == "load" || m.usage == "solar" { - return &TeslaEnergy{Tesla: m}, nil + totalEnergy = m.totalEnergy } - return m, nil + return decorateTesla(m, totalEnergy), nil } // CurrentPower implements the Meter.CurrentPower interface @@ -101,13 +104,8 @@ func (m *Tesla) CurrentPower() (float64, error) { return 0, fmt.Errorf("invalid usage: %s", m.usage) } -// TeslaEnergy decorates Tesla with api.MeterEnergy interface -type TeslaEnergy struct { - *Tesla -} - -// TotalEnergy implements the api.MeterEnergy interface -func (m *TeslaEnergy) TotalEnergy() (float64, error) { +// totalEnergy implements the api.MeterEnergy interface +func (m *Tesla) totalEnergy() (float64, error) { var tr teslaResponse _, err := m.GetJSON(m.uri, &tr) diff --git a/meter/tesla_decorators.go b/meter/tesla_decorators.go new file mode 100644 index 000000000..2d2f5cdff --- /dev/null +++ b/meter/tesla_decorators.go @@ -0,0 +1,35 @@ +package meter + +// This file has been generated - do not modify + +import ( + "github.com/andig/evcc/api" +) + +func decorateTesla(base api.Meter, meterEnergy func() (float64, error)) api.Meter { + switch { + case meterEnergy == nil: + return base + + case meterEnergy != nil: + return &struct{ + api.Meter + api.MeterEnergy + }{ + Meter: base, + MeterEnergy: &decorateTeslaMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decorateTeslaMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decorateTeslaMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +} diff --git a/tools.go b/tools.go index 87eeda029..283a69b72 100644 --- a/tools.go +++ b/tools.go @@ -5,5 +5,4 @@ package main import ( _ "github.com/golang/mock/mockgen" _ "github.com/mjibson/esc" - _ "golang.org/x/tools/cmd/stringer" )