Custom charger: add typical meter measurements (#14546)
This commit is contained in:
parent
0049674440
commit
a367a1e0a1
6 changed files with 2335 additions and 133 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/meter"
|
||||
"github.com/evcc-io/evcc/provider"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
|
@ -22,7 +23,7 @@ func init() {
|
|||
registry.Add(api.Custom, NewConfigurableFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateCustom -b *Charger -r api.Charger -t "api.ChargerEx,MaxCurrentMillis,func(float64) error" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Resurrector,WakeUp,func() error" -t "api.Battery,Soc,func() (float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateCustom -b *Charger -r api.Charger -t "api.ChargerEx,MaxCurrentMillis,func(float64) error" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Resurrector,WakeUp,func() error" -t "api.Battery,Soc,func() (float64, error)" -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
||||
|
||||
// NewConfigurableFromConfig creates a new configurable charger
|
||||
func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
|
|
@ -34,6 +35,13 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error
|
|||
Wakeup *provider.Config
|
||||
Soc *provider.Config
|
||||
Tos bool
|
||||
|
||||
// optional measurements
|
||||
Power *provider.Config
|
||||
Energy *provider.Config
|
||||
|
||||
// phase values, currently not supported (https://github.com/evcc-io/evcc/pull/14546)
|
||||
// Currents, Voltages, Powers []provider.Config
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -123,7 +131,13 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error
|
|||
}
|
||||
}
|
||||
|
||||
return decorateCustom(c, maxcurrentmillis, identify, phases1p3p, wakeup, soc), nil
|
||||
// decorate measurements
|
||||
powerG, energyG, _, _, _, err := meter.BuildMeasurements(cc.Power, cc.Energy, nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decorateCustom(c, maxcurrentmillis, identify, phases1p3p, wakeup, soc, powerG, energyG), nil
|
||||
}
|
||||
|
||||
// NewConfigurable creates a new charger
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
99
meter/helper.go
Normal file
99
meter/helper.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package meter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/provider"
|
||||
)
|
||||
|
||||
// BuildMeasurements returns typical meter measurement getters from config
|
||||
func BuildMeasurements(
|
||||
power *provider.Config,
|
||||
energy *provider.Config,
|
||||
currents []provider.Config,
|
||||
voltages []provider.Config,
|
||||
powers []provider.Config,
|
||||
) (
|
||||
func() (float64, error),
|
||||
func() (float64, error),
|
||||
func() (float64, float64, float64, error),
|
||||
func() (float64, float64, float64, error),
|
||||
func() (float64, float64, float64, error),
|
||||
error,
|
||||
) {
|
||||
var powerG func() (float64, error)
|
||||
if power != nil {
|
||||
var err error
|
||||
powerG, err = provider.NewFloatGetterFromConfig(*power)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("power: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var energyG func() (float64, error)
|
||||
if energy != nil {
|
||||
var err error
|
||||
energyG, err = provider.NewFloatGetterFromConfig(*energy)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("energy: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
currentsG, err := buildPhaseProviders(currents)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("currents: %w", err)
|
||||
}
|
||||
|
||||
voltagesG, err := buildPhaseProviders(voltages)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("voltages: %w", err)
|
||||
}
|
||||
|
||||
powersG, err := buildPhaseProviders(powers)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("powers: %w", err)
|
||||
}
|
||||
|
||||
return powerG, energyG, currentsG, voltagesG, powersG, nil
|
||||
}
|
||||
|
||||
// buildPhaseProviders returns phases getter for given config
|
||||
func buildPhaseProviders(providers []provider.Config) (func() (float64, float64, float64, error), error) {
|
||||
if len(providers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(providers) != 3 {
|
||||
return nil, errors.New("need one per phase, total three")
|
||||
}
|
||||
|
||||
var phases [3]func() (float64, error)
|
||||
for idx, prov := range providers {
|
||||
c, err := provider.NewFloatGetterFromConfig(prov)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%d] %w", idx, err)
|
||||
}
|
||||
|
||||
phases[idx] = c
|
||||
}
|
||||
|
||||
return collectPhaseProviders(phases), nil
|
||||
}
|
||||
|
||||
// collectPhaseProviders combines phase getters into combined api function
|
||||
func collectPhaseProviders(g [3]func() (float64, error)) func() (float64, float64, float64, error) {
|
||||
return func() (float64, float64, float64, error) {
|
||||
var res [3]float64
|
||||
for idx, currentG := range g {
|
||||
c, err := currentG()
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
res[idx] = c
|
||||
}
|
||||
|
||||
return res[0], res[1], res[2], nil
|
||||
}
|
||||
}
|
||||
|
|
@ -145,30 +145,27 @@ func NewModbusMbmdFromConfig(other map[string]interface{}) (api.Meter, error) {
|
|||
}
|
||||
|
||||
func (m *ModbusMbmd) buildPhaseProviders(readings []string) (func() (float64, float64, float64, error), error) {
|
||||
var res func() (float64, float64, float64, error)
|
||||
if len(readings) > 0 {
|
||||
if len(readings) != 3 {
|
||||
return nil, errors.New("need one per phase, total three")
|
||||
}
|
||||
|
||||
phases := make([]func() (float64, error), 0, 3)
|
||||
for idx, reading := range readings {
|
||||
opCurrent, err := modbus.ParseOperation(m.device, reading)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid measurement [%d]: %s", idx, reading)
|
||||
}
|
||||
|
||||
c := func() (float64, error) {
|
||||
return m.floatGetter(opCurrent)
|
||||
}
|
||||
|
||||
phases = append(phases, c)
|
||||
}
|
||||
|
||||
res = collectPhaseProviders(phases)
|
||||
if len(readings) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
if len(readings) != 3 {
|
||||
return nil, errors.New("need one per phase, total three")
|
||||
}
|
||||
|
||||
var phases [3]func() (float64, error)
|
||||
for idx, reading := range readings {
|
||||
opCurrent, err := modbus.ParseOperation(m.device, reading)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid measurement [%d]: %s", idx, reading)
|
||||
}
|
||||
|
||||
phases[idx] = func() (float64, error) {
|
||||
return m.floatGetter(opCurrent)
|
||||
}
|
||||
}
|
||||
|
||||
return collectPhaseProviders(phases), nil
|
||||
}
|
||||
|
||||
// floatGetter executes configured modbus read operation and implements func() (float64, error)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package meter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
|
|
@ -41,39 +40,12 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
power, err := provider.NewFloatGetterFromConfig(cc.Power)
|
||||
powerG, energyG, currentsG, voltagesG, powersG, err := BuildMeasurements(&cc.Power, cc.Energy, cc.Currents, cc.Voltages, cc.Powers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("power: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m, _ := NewConfigurable(power)
|
||||
|
||||
// decorate energy
|
||||
var totalEnergyG func() (float64, error)
|
||||
if cc.Energy != nil {
|
||||
totalEnergyG, err = provider.NewFloatGetterFromConfig(*cc.Energy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("energy: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// decorate currents
|
||||
currentsG, err := buildPhaseProviders(cc.Currents)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("currents: %w", err)
|
||||
}
|
||||
|
||||
// decorate voltages
|
||||
voltagesG, err := buildPhaseProviders(cc.Voltages)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("voltages: %w", err)
|
||||
}
|
||||
|
||||
// decorate powers
|
||||
powersG, err := buildPhaseProviders(cc.Powers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("powers: %w", err)
|
||||
}
|
||||
m, _ := NewConfigurable(powerG)
|
||||
|
||||
// decorate soc
|
||||
var socG func() (float64, error)
|
||||
|
|
@ -104,51 +76,11 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error)
|
|||
batModeS = cc.battery.ModeController(modeS)
|
||||
}
|
||||
|
||||
res := m.Decorate(totalEnergyG, currentsG, voltagesG, powersG, socG, cc.capacity.Decorator(), batModeS)
|
||||
res := m.Decorate(energyG, currentsG, voltagesG, powersG, socG, cc.capacity.Decorator(), batModeS)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func buildPhaseProviders(providers []provider.Config) (func() (float64, float64, float64, error), error) {
|
||||
var res func() (float64, float64, float64, error)
|
||||
if len(providers) > 0 {
|
||||
if len(providers) != 3 {
|
||||
return nil, errors.New("need one per phase, total three")
|
||||
}
|
||||
|
||||
phases := make([]func() (float64, error), 0, 3)
|
||||
for idx, prov := range providers {
|
||||
c, err := provider.NewFloatGetterFromConfig(prov)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%d] %w", idx, err)
|
||||
}
|
||||
|
||||
phases = append(phases, c)
|
||||
}
|
||||
|
||||
res = collectPhaseProviders(phases)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// collectPhaseProviders combines phase getters into currents api function
|
||||
func collectPhaseProviders(g []func() (float64, error)) func() (float64, float64, float64, error) {
|
||||
return func() (float64, float64, float64, error) {
|
||||
var res []float64
|
||||
for _, currentG := range g {
|
||||
c, err := currentG()
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
res = append(res, c)
|
||||
}
|
||||
|
||||
return res[0], res[1], res[2], nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewConfigurable creates a new meter
|
||||
func NewConfigurable(currentPowerG func() (float64, error)) (*Meter, error) {
|
||||
m := &Meter{
|
||||
|
|
|
|||
|
|
@ -64,13 +64,13 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var curr []func() (float64, error)
|
||||
for i := 1; i <= 3; i++ {
|
||||
current, err := to.FloatGetter(mq("%s/evu/%s%d", cc.Topic, openwb.CurrentTopic, i))
|
||||
var curr [3]func() (float64, error)
|
||||
for i := 0; i < 3; i++ {
|
||||
current, err := to.FloatGetter(mq("%s/evu/%s%d", cc.Topic, openwb.CurrentTopic, i+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
curr = append(curr, current)
|
||||
curr[i] = current
|
||||
}
|
||||
|
||||
currents = collectPhaseProviders(curr)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue