Allow to define supported phases at vehicle level (#2209)
This commit is contained in:
parent
4a78afe9f3
commit
cd42f20a51
5 changed files with 125 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue