Implement composition of optional interfaces using generator (#310)
This commit is contained in:
parent
6d89835c2c
commit
e7ee87b19a
16 changed files with 596 additions and 143 deletions
1
Makefile
1
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// }
|
||||
|
|
|
|||
69
charger/evsewifi_decorators.go
Normal file
69
charger/evsewifi_decorators.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
29
charger/evsewifi_test.go
Normal file
29
charger/evsewifi_test.go
Normal file
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
227
cmd/tools/decorate.go
Normal file
227
cmd/tools/decorate.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
3
go.mod
3
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
|
||||
)
|
||||
|
||||
|
|
|
|||
2
go.sum
2
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=
|
||||
|
|
|
|||
122
meter/meter.go
122
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
|
||||
|
|
|
|||
69
meter/meter_decorators.go
Normal file
69
meter/meter_decorators.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
35
meter/modbus_decorators.go
Normal file
35
meter/modbus_decorators.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
20
meter/sma.go
20
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
|
||||
}
|
||||
|
|
|
|||
35
meter/sma_decorators.go
Normal file
35
meter/sma_decorators.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
35
meter/tesla_decorators.go
Normal file
35
meter/tesla_decorators.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
1
tools.go
1
tools.go
|
|
@ -5,5 +5,4 @@ package main
|
|||
import (
|
||||
_ "github.com/golang/mock/mockgen"
|
||||
_ "github.com/mjibson/esc"
|
||||
_ "golang.org/x/tools/cmd/stringer"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue