Allow giving home battery charge priority up to defined soc (#369)
Add batteryPriority setting Add battery soc reading to PowerWall Add battery soc reading to generic meter Publish batterySoC vaue when read
This commit is contained in:
parent
8a5bd8e119
commit
d3ee35272d
7 changed files with 189 additions and 24 deletions
|
|
@ -52,6 +52,11 @@ type MeterCurrent interface {
|
|||
Currents() (float64, float64, float64, error)
|
||||
}
|
||||
|
||||
// Battery is able to provide battery SoC in %
|
||||
type Battery interface {
|
||||
SoC() (float64, error)
|
||||
}
|
||||
|
||||
// Charger is able to provide current charging status and to enable/disabler charging
|
||||
type Charger interface {
|
||||
Status() (ChargeStatus, error)
|
||||
|
|
|
|||
19
core/site.go
19
core/site.go
|
|
@ -31,6 +31,7 @@ type Site struct {
|
|||
Voltage float64 `mapstructure:"voltage"` // Operating voltage. 230V for Germany.
|
||||
ResidualPower float64 `mapstructure:"residualPower"` // PV meter only: household usage. Grid meter: household safety margin
|
||||
Meters MetersConfig // Meter references
|
||||
PrioritySoC float64 `mapstructure:"prioritySoC"` // prefer battery up to this SoC
|
||||
|
||||
// meters
|
||||
gridMeter api.Meter // Grid usage meter
|
||||
|
|
@ -311,7 +312,23 @@ func (site *Site) sitePower() (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
sitePower := sitePower(site.gridPower, site.batteryPower, site.ResidualPower)
|
||||
// honour battery priority
|
||||
batteryPower := site.batteryPower
|
||||
if battery, ok := site.batteryMeter.(api.Battery); ok && site.PrioritySoC > 0 {
|
||||
soc, err := battery.SoC()
|
||||
if err != nil {
|
||||
site.log.ERROR.Printf("updating battery soc: %v", err)
|
||||
} else {
|
||||
site.publish("batterySoC", soc)
|
||||
|
||||
if soc < site.PrioritySoC {
|
||||
site.log.DEBUG.Printf("giving priority to battery at soc: %.0f", soc)
|
||||
batteryPower = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sitePower := sitePower(site.gridPower, batteryPower, site.ResidualPower)
|
||||
site.log.DEBUG.Printf("site power: %.0fW", sitePower)
|
||||
|
||||
return sitePower, nil
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ site:
|
|||
grid: grid # grid meter
|
||||
pv: pv # pv meter
|
||||
battery: battery # battery meter
|
||||
prioritySoC: 60 # give home battery priority up to this soc (0 to disable)
|
||||
|
||||
# loadpoint describes the charger, charge meter and connected vehicle
|
||||
loadpoints:
|
||||
|
|
|
|||
|
|
@ -13,13 +13,14 @@ 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)"
|
||||
//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)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
|
||||
// NewConfigurableFromConfig creates api.Meter from config
|
||||
func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
cc := struct {
|
||||
Power provider.Config
|
||||
Energy *provider.Config // optional
|
||||
SoC *provider.Config // optional
|
||||
Currents []provider.Config // optional
|
||||
}{}
|
||||
|
||||
|
|
@ -69,7 +70,17 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error)
|
|||
currents = m.currents
|
||||
}
|
||||
|
||||
res := decorateMeter(m, totalEnergy, currents)
|
||||
// decorate Meter with BatterySoC
|
||||
var batterySoC func() (float64, error)
|
||||
if cc.SoC != nil {
|
||||
m.batterySoCG, err = provider.NewFloatGetterFromConfig(*cc.SoC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
batterySoC = m.batterySoC
|
||||
}
|
||||
|
||||
res := decorateMeter(m, totalEnergy, currents, batterySoC)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
@ -87,6 +98,7 @@ type Meter struct {
|
|||
currentPowerG func() (float64, error)
|
||||
totalEnergyG func() (float64, error)
|
||||
currentsG []func() (float64, error)
|
||||
batterySoCG func() (float64, error)
|
||||
}
|
||||
|
||||
// CurrentPower implements the Meter.CurrentPower interface
|
||||
|
|
@ -113,3 +125,8 @@ func (m *Meter) currents() (float64, float64, float64, error) {
|
|||
|
||||
return currents[0], currents[1], currents[2], nil
|
||||
}
|
||||
|
||||
// batterySoC implements the Battery.SoC interface
|
||||
func (m *Meter) batterySoC() (float64, error) {
|
||||
return m.batterySoCG()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import (
|
|||
"github.com/andig/evcc/api"
|
||||
)
|
||||
|
||||
func decorateMeter(base api.Meter, meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error)) api.Meter {
|
||||
func decorateMeter(base api.Meter, meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error), battery func() (float64, error)) api.Meter {
|
||||
switch {
|
||||
case meterCurrent == nil && meterEnergy == nil:
|
||||
case battery == nil && meterCurrent == nil && meterEnergy == nil:
|
||||
return base
|
||||
|
||||
case meterCurrent == nil && meterEnergy != nil:
|
||||
case battery == nil && meterCurrent == nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
|
|
@ -22,7 +22,7 @@ func decorateMeter(base api.Meter, meterEnergy func() (float64, error), meterCur
|
|||
},
|
||||
}
|
||||
|
||||
case meterCurrent != nil && meterEnergy == nil:
|
||||
case battery == nil && meterCurrent != nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.MeterCurrent
|
||||
|
|
@ -33,7 +33,7 @@ func decorateMeter(base api.Meter, meterEnergy func() (float64, error), meterCur
|
|||
},
|
||||
}
|
||||
|
||||
case meterCurrent != nil && meterEnergy != nil:
|
||||
case battery == nil && meterCurrent != nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.MeterCurrent
|
||||
|
|
@ -47,11 +47,79 @@ func decorateMeter(base api.Meter, meterEnergy func() (float64, error), meterCur
|
|||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meterCurrent == nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.Battery
|
||||
}{
|
||||
Meter: base,
|
||||
Battery: &decorateMeterBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meterCurrent == nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.Battery
|
||||
api.MeterEnergy
|
||||
}{
|
||||
Meter: base,
|
||||
Battery: &decorateMeterBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
MeterEnergy: &decorateMeterMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meterCurrent != nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.Battery
|
||||
api.MeterCurrent
|
||||
}{
|
||||
Meter: base,
|
||||
Battery: &decorateMeterBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
MeterCurrent: &decorateMeterMeterCurrentImpl{
|
||||
meterCurrent: meterCurrent,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meterCurrent != nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.Battery
|
||||
api.MeterCurrent
|
||||
api.MeterEnergy
|
||||
}{
|
||||
Meter: base,
|
||||
Battery: &decorateMeterBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
MeterCurrent: &decorateMeterMeterCurrentImpl{
|
||||
meterCurrent: meterCurrent,
|
||||
},
|
||||
MeterEnergy: &decorateMeterMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type decorateMeterBatteryImpl struct {
|
||||
battery func() (float64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateMeterBatteryImpl) SoC() (float64, error) {
|
||||
return impl.battery()
|
||||
}
|
||||
|
||||
type decorateMeterMeterCurrentImpl struct {
|
||||
meterCurrent func() (float64, float64, float64, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,12 @@ import (
|
|||
|
||||
// credits to https://github.com/vloschiavo/powerwall2
|
||||
|
||||
type teslaResponse map[string]struct {
|
||||
const (
|
||||
teslaMeterURI = "/api/meters/aggregates"
|
||||
teslaBatteryURI = "/api/system_status/soe"
|
||||
)
|
||||
|
||||
type teslaMeterResponse map[string]struct {
|
||||
LastCommunicationTime string `json:"last_communication_time"`
|
||||
InstantPower float64 `json:"instant_power"`
|
||||
InstantReactivePower float64 `json:"instant_reactive_power"`
|
||||
|
|
@ -29,6 +34,10 @@ type teslaResponse map[string]struct {
|
|||
ICCurrent float64 `json:"i_c_current"`
|
||||
}
|
||||
|
||||
type teslaBatteryResponse struct {
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
// Tesla is the tesla powerwall meter
|
||||
type Tesla struct {
|
||||
*request.Helper
|
||||
|
|
@ -39,7 +48,7 @@ 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)"
|
||||
//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)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
|
||||
// NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config
|
||||
func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
|
|
@ -60,12 +69,12 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) {
|
|||
return nil, fmt.Errorf("invalid uri %s", cc.URI)
|
||||
}
|
||||
|
||||
if url.Path == "" {
|
||||
url.Path = "api/meters/aggregates"
|
||||
cc.URI = url.String()
|
||||
uri := "https://" + url.Hostname()
|
||||
if url.Port() != "" {
|
||||
uri += ":" + url.Port()
|
||||
}
|
||||
|
||||
return NewTesla(cc.URI, cc.Usage)
|
||||
return NewTesla(uri, cc.Usage)
|
||||
}
|
||||
|
||||
// NewTesla creates a Tesla Meter
|
||||
|
|
@ -85,16 +94,22 @@ func NewTesla(uri, usage string) (api.Meter, error) {
|
|||
totalEnergy = m.totalEnergy
|
||||
}
|
||||
|
||||
return decorateTesla(m, totalEnergy), nil
|
||||
// decorate api.BatterySoC
|
||||
var batterySoC func() (float64, error)
|
||||
if usage == "battery" {
|
||||
batterySoC = m.batterySoC
|
||||
}
|
||||
|
||||
return decorateTesla(m, totalEnergy, batterySoC), nil
|
||||
}
|
||||
|
||||
// CurrentPower implements the Meter.CurrentPower interface
|
||||
func (m *Tesla) CurrentPower() (float64, error) {
|
||||
var tr teslaResponse
|
||||
err := m.GetJSON(m.uri, &tr)
|
||||
var res teslaMeterResponse
|
||||
err := m.GetJSON(m.uri+teslaMeterURI, &res)
|
||||
|
||||
if err == nil {
|
||||
if o, ok := tr[m.usage]; ok {
|
||||
if o, ok := res[m.usage]; ok {
|
||||
return o.InstantPower, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -104,11 +119,11 @@ func (m *Tesla) CurrentPower() (float64, error) {
|
|||
|
||||
// totalEnergy implements the api.MeterEnergy interface
|
||||
func (m *Tesla) totalEnergy() (float64, error) {
|
||||
var tr teslaResponse
|
||||
err := m.GetJSON(m.uri, &tr)
|
||||
var res teslaMeterResponse
|
||||
err := m.GetJSON(m.uri+teslaMeterURI, &res)
|
||||
|
||||
if err == nil {
|
||||
if o, ok := tr[m.usage]; ok {
|
||||
if o, ok := res[m.usage]; ok {
|
||||
if m.usage == "load" {
|
||||
return o.EnergyImported, nil
|
||||
}
|
||||
|
|
@ -120,3 +135,11 @@ func (m *Tesla) totalEnergy() (float64, error) {
|
|||
|
||||
return 0, fmt.Errorf("invalid usage: %s", m.usage)
|
||||
}
|
||||
|
||||
// batterySoC implements the api.Battery interface
|
||||
func (m *Tesla) batterySoC() (float64, error) {
|
||||
var res teslaBatteryResponse
|
||||
err := m.GetJSON(m.uri+teslaBatteryURI, &res)
|
||||
|
||||
return res.Percentage, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import (
|
|||
"github.com/andig/evcc/api"
|
||||
)
|
||||
|
||||
func decorateTesla(base api.Meter, meterEnergy func() (float64, error)) api.Meter {
|
||||
func decorateTesla(base api.Meter, meterEnergy func() (float64, error), battery func() (float64, error)) api.Meter {
|
||||
switch {
|
||||
case meterEnergy == nil:
|
||||
case battery == nil && meterEnergy == nil:
|
||||
return base
|
||||
|
||||
case meterEnergy != nil:
|
||||
case battery == nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.MeterEnergy
|
||||
|
|
@ -21,11 +21,45 @@ func decorateTesla(base api.Meter, meterEnergy func() (float64, error)) api.Mete
|
|||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meterEnergy == nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.Battery
|
||||
}{
|
||||
Meter: base,
|
||||
Battery: &decorateTeslaBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
}
|
||||
|
||||
case battery != nil && meterEnergy != nil:
|
||||
return &struct {
|
||||
api.Meter
|
||||
api.Battery
|
||||
api.MeterEnergy
|
||||
}{
|
||||
Meter: base,
|
||||
Battery: &decorateTeslaBatteryImpl{
|
||||
battery: battery,
|
||||
},
|
||||
MeterEnergy: &decorateTeslaMeterEnergyImpl{
|
||||
meterEnergy: meterEnergy,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type decorateTeslaBatteryImpl struct {
|
||||
battery func() (float64, error)
|
||||
}
|
||||
|
||||
func (impl *decorateTeslaBatteryImpl) SoC() (float64, error) {
|
||||
return impl.battery()
|
||||
}
|
||||
|
||||
type decorateTeslaMeterEnergyImpl struct {
|
||||
meterEnergy func() (float64, error)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue