Allow to define supported phases at vehicle level (#2209)

This commit is contained in:
andig 2022-01-16 09:31:52 +01:00 • committed by GitHub
parent 4a78afe9f3
commit cd42f20a51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 5 deletions

View file

@ -9,7 +9,7 @@ import (
"github.com/fatih/structs"
)
//go:generate mockgen -package mock -destination ../mock/mock_api.go github.com/evcc-io/evcc/api Charger,ChargeState,ChargePhases,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery
//go:generate mockgen -package mock -destination ../mock/mock_api.go github.com/evcc-io/evcc/api Charger,ChargeState,ChargePhases,Identifier,Meter,MeterEnergy,Vehicle,VehiclePhases,ChargeRater,Battery
// ChargeMode are charge modes modeled after OpenWB
type ChargeMode string
@ -170,6 +170,11 @@ type VehiclePosition interface {
Position() (float64, float64, error)
}
// VehiclePhases returns the number of supported phases
type VehiclePhases interface {
Phases() int
}
// VehicleStartCharge starts the charging session on the vehicle side
type VehicleStartCharge interface {
StartCharge() error

View file

@ -773,6 +773,8 @@ func (lp *LoadPoint) setActiveVehicle(vehicle api.Vehicle) {
lp.publish("vehicleCapacity", lp.vehicle.Capacity())
lp.applyAction(vehicle.OnIdentified())
lp.setVehiclePhases()
} else {
lp.socEstimator = nil
@ -909,6 +911,25 @@ func (lp *LoadPoint) resetPVTimerIfRunning(typ ...string) {
lp.publishTimer(pvTimer, 0, timerInactive)
}
// setVehiclePhases sets the expected active phases by the vehicle
func (lp *LoadPoint) setVehiclePhases() {
if v, ok := lp.vehicle.(api.VehiclePhases); ok {
if phases := v.Phases(); phases > 0 {
lp.log.DEBUG.Printf("vehicle phases: %dp", phases)
lp.Lock()
defer lp.Unlock()
if phases > lp.Phases {
phases = lp.Phases
}
lp.activePhases = phases
lp.publish("activePhases", lp.activePhases)
}
}
}
// scalePhasesIfAvailable scales if api.ChargePhases is available
func (lp *LoadPoint) scalePhasesIfAvailable(phases int) error {
err := lp.scalePhases(phases)
@ -927,12 +948,16 @@ func (lp *LoadPoint) setPhases(phases int) {
lp.Phases = phases
lp.publish("phases", lp.Phases)
// When scaling down, charger will temporarily disable. During this time, activePhases will not be updated
// since all currents are zero. This will lead to inconsistent state when scaling is triggered again
// (1p configured vs 3p active). Update activePhases to reflect the current state of the charger.
if phases < lp.activePhases {
// When scaling down, charger will temporarily disable. During this time, activePhases will not be updated
// since all currents are zero. This will lead to inconsistent state when scaling is triggered again
// (1p configured vs 3p active). Update activePhases to reflect the current state of the charger.
lp.activePhases = phases
lp.publish("activePhases", lp.activePhases)
} else {
// When scaling up, charger will offer more phases than vehicle can use.
// Adjust to enable subsequent PV restart at lower powers than full 3p.
lp.setVehiclePhases()
}
}
}

View file

@ -932,3 +932,50 @@ func TestScalePhases(t *testing.T) {
}
}
}
func TestVehiclePhases(t *testing.T) {
ctrl := gomock.NewController(t)
vehicle := &struct {
*mock.MockVehicle
*mock.MockVehiclePhases
}{
mock.NewMockVehicle(ctrl),
mock.NewMockVehiclePhases(ctrl),
}
tc := []struct {
phases, activePhases, vehiclePhases int
res int
}{
{1, 1, 0, 1}, // leave as-is
{3, 1, 0, 1}, // leave as-is
{3, 3, 0, 3}, // leave as-is
{1, 1, 1, 1}, // leave as-is
{3, 1, 1, 1}, // leave as-is
{3, 3, 1, 1}, // limit to 1p
{1, 1, 2, 1}, // leave as-is
{3, 1, 2, 2}, // limit to 2p
{3, 3, 2, 2}, // limit to 2p
{1, 1, 3, 1}, // leave as-is
{3, 1, 3, 3}, // limit to 3p
{3, 3, 3, 3}, // leave as-is
}
for _, tc := range tc {
t.Logf("%+v", tc)
lp := &LoadPoint{
log: util.NewLogger("foo"),
vehicle: vehicle,
Phases: tc.phases,
activePhases: tc.activePhases,
}
vehicle.MockVehiclePhases.EXPECT().Phases().Return(tc.vehiclePhases)
lp.setVehiclePhases()
if lp.activePhases != tc.res {
t.Errorf("expected %v, got %v", tc.res, lp.activePhases)
}
}
}

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,ChargePhases,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery)
// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,ChargePhases,Identifier,Meter,MeterEnergy,Vehicle,VehiclePhases,ChargeRater,Battery)
// Package mock is a generated GoMock package.
package mock
@ -375,6 +375,43 @@ func (mr *MockVehicleMockRecorder) Title() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Title", reflect.TypeOf((*MockVehicle)(nil).Title))
}
// MockVehiclePhases is a mock of VehiclePhases interface.
type MockVehiclePhases struct {
ctrl *gomock.Controller
recorder *MockVehiclePhasesMockRecorder
}
// MockVehiclePhasesMockRecorder is the mock recorder for MockVehiclePhases.
type MockVehiclePhasesMockRecorder struct {
mock *MockVehiclePhases
}
// NewMockVehiclePhases creates a new mock instance.
func NewMockVehiclePhases(ctrl *gomock.Controller) *MockVehiclePhases {
mock := &MockVehiclePhases{ctrl: ctrl}
mock.recorder = &MockVehiclePhasesMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockVehiclePhases) EXPECT() *MockVehiclePhasesMockRecorder {
return m.recorder
}
// Phases mocks base method.
func (m *MockVehiclePhases) Phases() int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Phases")
ret0, _ := ret[0].(int)
return ret0
}
// Phases indicates an expected call of Phases.
func (mr *MockVehiclePhasesMockRecorder) Phases() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Phases", reflect.TypeOf((*MockVehiclePhases)(nil).Phases))
}
// MockChargeRater is a mock of ChargeRater interface.
type MockChargeRater struct {
ctrl *gomock.Controller

View file

@ -5,6 +5,7 @@ import "github.com/evcc-io/evcc/api"
type embed struct {
Title_ string `mapstructure:"title"`
Capacity_ int64 `mapstructure:"capacity"`
Phases_ int `mapstructure:"phases"`
Identifiers_ []string `mapstructure:"identifiers"`
OnIdentify api.ActionConfig `mapstructure:"onIdentify"`
}
@ -19,6 +20,11 @@ func (v *embed) Capacity() int64 {
return v.Capacity_
}
// Phases returns the phases used by the vehicle
func (v *embed) Phases() int {
return v.Phases_
}
// Identifiers implements the api.Identifier interface
func (v *embed) Identifiers() []string {
return v.Identifiers_