Skip to content

Commit

Permalink
Allow specifying battery capacity (#5598)
Browse files Browse the repository at this point in the history
  • Loading branch information
kscholty authored Dec 29, 2022
1 parent 68f28cb commit 1ef2590
Show file tree
Hide file tree
Showing 18 changed files with 1,784 additions and 121 deletions.
7 changes: 6 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ type Battery interface {
Soc() (float64, error)
}

// BatteryCapacity provides a capacity in Wh
type BatteryCapacity interface {
Capacity() float64
}

// ChargeState provides current charging status
type ChargeState interface {
Status() (ChargeStatus, error)
Expand Down Expand Up @@ -178,9 +183,9 @@ type Authorizer interface {
// Vehicle represents the EV and it's battery
type Vehicle interface {
Battery
BatteryCapacity
Title() string
Icon() string
Capacity() float64
Phases() int
Identifiers() []string
OnIdentified() ActionConfig
Expand Down
5 changes: 4 additions & 1 deletion cmd/dumper.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func (d *dumper) Dump(name string, v interface{}) {
}
}

if v, ok := v.(api.BatteryCapacity); ok {
fmt.Fprintf(w, "Capacity:\t%.1fkWh\n", v.Capacity())
}

// charger

if v, ok := v.(api.ChargeState); ok {
Expand Down Expand Up @@ -194,7 +198,6 @@ func (d *dumper) Dump(name string, v interface{}) {
}

if v, ok := v.(api.Vehicle); ok {
fmt.Fprintf(w, "Capacity:\t%.1fkWh\n", v.Capacity())
if len(v.Identifiers()) > 0 {
fmt.Fprintf(w, "Identifiers:\t%v\n", v.Identifiers())
}
Expand Down
40 changes: 31 additions & 9 deletions core/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ type meterMeasurement struct {

// batteryMeasurement is used as slice element for publishing structured data
type batteryMeasurement struct {
Power float64 `json:"power"`
Soc float64 `json:"soc"`
Power float64 `json:"power"`
Soc float64 `json:"soc"`
Capacity float64 `json:"capacity"`
}

// Site is the main configuration container. A site can host multiple loadpoints.
Expand Down Expand Up @@ -270,9 +271,11 @@ func (site *Site) DumpConfig() {
if len(site.batteryMeters) > 0 {
for i, battery := range site.batteryMeters {
_, ok := battery.(api.Battery)
_, hasCapacity := battery.(api.BatteryCapacity)

site.log.INFO.Println(
meterCapabilities(fmt.Sprintf("battery %d", i+1), battery),
fmt.Sprintf("soc %s", presence[ok]),
fmt.Sprintf("soc %s capacity %s", presence[ok], presence[hasCapacity]),
)
}
}
Expand Down Expand Up @@ -393,13 +396,16 @@ func (site *Site) updateMeters() error {
}

if len(site.batteryMeters) > 0 {
var totalCapacity float64
site.batteryPower = 0
site.batterySoc = 0

mm := make([]batteryMeasurement, len(site.batteryMeters))

for i, meter := range site.batteryMeters {
var power float64

// NOTE battery errors are logged but ignored as we don't consider them relevant
err := retry.Do(site.updateMeter(meter, &power), retryOptions...)

if err == nil {
Expand All @@ -409,23 +415,39 @@ func (site *Site) updateMeters() error {
site.log.ERROR.Printf("battery %d power: %v", i+1, err)
}

var capacity float64
soc, err := meter.(api.Battery).Soc()

if err == nil {
site.batterySoc += soc
// weigh soc by capacity and accumulate total capacity
weighedSoc := soc
if m, ok := meter.(api.BatteryCapacity); ok {
capacity = m.Capacity()
totalCapacity += capacity
weighedSoc *= capacity
}

site.batterySoc += weighedSoc
site.log.DEBUG.Printf("battery %d soc: %.0f%%", i+1, soc)
} else {
err = fmt.Errorf("battery %d soc: %v", i+1, err)
site.log.ERROR.Println(err)
site.log.ERROR.Printf("battery %d soc: %v", i+1, err)
}

mm[i] = batteryMeasurement{
Power: power,
Soc: soc,
Power: power,
Soc: soc,
Capacity: capacity,
}
}

site.batterySoc /= float64(len(site.batteryMeters))
site.publish("batteryCapacity", math.Round(totalCapacity))

// convert weighed socs to total soc
if totalCapacity == 0 {
totalCapacity = float64(len(site.batteryMeters))
}
site.batterySoc /= totalCapacity

site.log.DEBUG.Printf("battery soc: %.0f%%", math.Round(site.batterySoc))
site.publish("batterySoc", math.Round(site.batterySoc))

Expand Down
17 changes: 17 additions & 0 deletions meter/capacity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package meter

type capacity struct {
Capacity float64
}

// var _ api.BatteryCapacity = (*capacity)(nil)

// Decorator returns the capacity decorator if capacity is specified or nil otherwise
func (m *capacity) Decorator() func() float64 {
if m.Capacity == 0 {
return nil
}
return func() float64 {
return m.Capacity
}
}
9 changes: 5 additions & 4 deletions meter/lgess.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ func init() {
registry.Add("lgess", NewLgEssFromConfig)
}

//go:generate go run ../cmd/tools/decorate.go -f decorateLgEss -b *LgEss -r api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)"
//go:generate go run ../cmd/tools/decorate.go -f decorateLgEss -b *LgEss -r api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64"

// NewLgEssFromConfig creates an LgEss Meter from generic config
func NewLgEssFromConfig(other map[string]interface{}) (api.Meter, error) {
cc := struct {
capacity `mapstructure:",squash"`
URI, Usage, Password string
Cache time.Duration
}{
Expand All @@ -68,11 +69,11 @@ func NewLgEssFromConfig(other map[string]interface{}) (api.Meter, error) {
return nil, errors.New("missing usage")
}

return NewLgEss(cc.URI, cc.Usage, cc.Password, cc.Cache)
return NewLgEss(cc.URI, cc.Usage, cc.Password, cc.Cache, cc.capacity.Decorator())
}

// NewLgEss creates an LgEss Meter
func NewLgEss(uri, usage, password string, cache time.Duration) (api.Meter, error) {
func NewLgEss(uri, usage, password string, cache time.Duration, capacity func() float64) (api.Meter, error) {
lp, err := lgpcs.GetInstance(uri, password, cache)
if err != nil {
return nil, err
Expand All @@ -95,7 +96,7 @@ func NewLgEss(uri, usage, password string, cache time.Duration) (api.Meter, erro
batterySoc = m.batterySoc
}

return decorateLgEss(m, totalEnergy, batterySoc), nil
return decorateLgEss(m, totalEnergy, batterySoc, capacity), nil
}

// CurrentPower implements the api.Meter interface
Expand Down
78 changes: 73 additions & 5 deletions meter/lgess_decorators.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions meter/meter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ func init() {
registry.Add(api.Custom, NewConfigurableFromConfig)
}

//go:generate go run ../cmd/tools/decorate.go -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" -t "api.MeterVoltage,Voltages,func() (float64, float64, float64, error)" -t "api.MeterPower,Powers,func() (float64, float64, float64, error)" -t "api.Battery,Soc,func() (float64, error)"
//go:generate go run ../cmd/tools/decorate.go -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" -t "api.MeterVoltage,Voltages,func() (float64, float64, float64, error)" -t "api.MeterPower,Powers,func() (float64, float64, float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64"

// NewConfigurableFromConfig creates api.Meter from config
func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) {
var cc struct {
capacity `mapstructure:",squash"`
Power provider.Config
Energy *provider.Config // optional
Soc *provider.Config // optional
Expand Down Expand Up @@ -73,7 +74,7 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error)
}
}

res := m.Decorate(totalEnergyG, currentsG, voltagesG, powersG, batterySocG)
res := m.Decorate(totalEnergyG, currentsG, voltagesG, powersG, batterySocG, cc.capacity.Decorator())

return res, nil
}
Expand Down Expand Up @@ -138,8 +139,9 @@ func (m *Meter) Decorate(
voltages func() (float64, float64, float64, error),
powers func() (float64, float64, float64, error),
batterySoc func() (float64, error),
capacity func() float64,
) api.Meter {
return decorateMeter(m, totalEnergy, currents, voltages, powers, batterySoc)
return decorateMeter(m, totalEnergy, currents, voltages, powers, batterySoc, capacity)
}

// CurrentPower implements the api.Meter interface
Expand Down
9 changes: 4 additions & 5 deletions meter/meter_average.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ func NewMovingAverageFromConfig(other map[string]interface{}) (api.Meter, error)
cc := struct {
Decay float64
Meter struct {
Type string
Other map[string]interface{} `mapstructure:",remain"`
capacity `mapstructure:",squash"`
Type string
Other map[string]interface{} `mapstructure:",remain"`
}
}{
Decay: 0.1,
Expand Down Expand Up @@ -67,9 +68,7 @@ func NewMovingAverageFromConfig(other map[string]interface{}) (api.Meter, error)
powers = m.Powers
}

res := meter.Decorate(totalEnergy, currents, voltages, powers, batterySoc)

return res, nil
return meter.Decorate(totalEnergy, currents, voltages, powers, batterySoc, cc.Meter.capacity.Decorator()), nil
}

type MovingAverage struct {
Expand Down
Loading

0 comments on commit 1ef2590

Please sign in to comment.