diff --git a/api/api.go b/api/api.go index 6a4d85796..7e3df35ea 100644 --- a/api/api.go +++ b/api/api.go @@ -7,7 +7,7 @@ import ( "time" ) -//go:generate go tool mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Tariff +//go:generate go tool mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Tariff // Meter provides total active power in W type Meter interface { @@ -115,11 +115,6 @@ type ChargeTimer interface { ChargeDuration() (time.Duration, error) } -// ConnectionTimer provides current connection duration -type ConnectionTimer interface { - ConnectionDuration() (time.Duration, error) -} - // ChargeRater provides charged energy amount in kWh type ChargeRater interface { ChargedEnergy() (float64, error) diff --git a/api/mock.go b/api/mock.go index 69a741239..8c7270832 100644 --- a/api/mock.go +++ b/api/mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Tariff) +// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Tariff) // // Generated by this command: // -// mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ConnectionTimer,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Tariff +// mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,CurrentGetter,PhaseSwitcher,PhaseGetter,FeatureDescriber,Identifier,Meter,MeterEnergy,PhaseCurrents,Vehicle,ChargeRater,Battery,BatteryController,BatterySocLimiter,Circuit,Tariff // // Package api is a generated GoMock package. @@ -11,7 +11,6 @@ package api import ( reflect "reflect" - time "time" gomock "go.uber.org/mock/gomock" ) @@ -638,45 +637,6 @@ func (mr *MockVehicleMockRecorder) Soc() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Soc", reflect.TypeOf((*MockVehicle)(nil).Soc)) } -// MockConnectionTimer is a mock of ConnectionTimer interface. -type MockConnectionTimer struct { - ctrl *gomock.Controller - recorder *MockConnectionTimerMockRecorder - isgomock struct{} -} - -// MockConnectionTimerMockRecorder is the mock recorder for MockConnectionTimer. -type MockConnectionTimerMockRecorder struct { - mock *MockConnectionTimer -} - -// NewMockConnectionTimer creates a new mock instance. -func NewMockConnectionTimer(ctrl *gomock.Controller) *MockConnectionTimer { - mock := &MockConnectionTimer{ctrl: ctrl} - mock.recorder = &MockConnectionTimerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockConnectionTimer) EXPECT() *MockConnectionTimerMockRecorder { - return m.recorder -} - -// ConnectionDuration mocks base method. -func (m *MockConnectionTimer) ConnectionDuration() (time.Duration, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ConnectionDuration") - ret0, _ := ret[0].(time.Duration) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ConnectionDuration indicates an expected call of ConnectionDuration. -func (mr *MockConnectionTimerMockRecorder) ConnectionDuration() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnectionDuration", reflect.TypeOf((*MockConnectionTimer)(nil).ConnectionDuration)) -} - // MockChargeRater is a mock of ChargeRater interface. type MockChargeRater struct { ctrl *gomock.Controller diff --git a/charger/phoenix-charx.go b/charger/phoenix-charx.go index 6f404b89a..6545d3a43 100644 --- a/charger/phoenix-charx.go +++ b/charger/phoenix-charx.go @@ -20,20 +20,19 @@ const ( // per-unit registers charxOffset = 1000 - charxRegMeter = 112 - charxRegVoltages = 232 // mV - charxRegCurrents = 238 // mA - charxRegPower = 244 // mW - charxRegEnergy = 250 // Wh - charxRegSoc = 264 // % - charxRegEvid = 265 // 10 - charxRegRfid = 275 // 10 - charxRegConnectionTime = 285 // s - charxRegChargeTime = 287 // s - charxRegChargeEnergy = 289 // Wh - charxRegStatus = 299 // IEC 61851-1 - charxRegEnable = 300 - charxRegMaxCurrent = 301 // A + charxRegMeter = 112 + charxRegVoltages = 232 // mV + charxRegCurrents = 238 // mA + charxRegPower = 244 // mW + charxRegEnergy = 250 // Wh + charxRegSoc = 264 // % + charxRegEvid = 265 // 10 + charxRegRfid = 275 // 10 + charxRegChargeTime = 287 // s + charxRegChargeEnergy = 289 // Wh + charxRegStatus = 299 // IEC 61851-1 + charxRegEnable = 300 + charxRegMaxCurrent = 301 // A ) // PhoenixCharx is an api.Charger implementation for Phoenix CHARX controller @@ -200,19 +199,7 @@ func (wb *PhoenixCharx) ChargeDuration() (time.Duration, error) { return 0, err } - return time.Duration(encoding.Uint32(b)) * time.Second, nil -} - -var _ api.ConnectionTimer = (*PhoenixCharx)(nil) - -// ConnectionDuration implements the api.ConnectionTimer interface -func (wb *PhoenixCharx) ConnectionDuration() (time.Duration, error) { - b, err := wb.conn.ReadHoldingRegisters(wb.register(charxRegConnectionTime), 2) - if err != nil { - return 0, err - } - - return time.Duration(encoding.Uint32(b)) * time.Second, nil + return time.Duration(encoding.Uint16(b)) * time.Second, nil } // currentPower implements the api.Meter interface diff --git a/core/loadpoint.go b/core/loadpoint.go index d48475d01..64eac0830 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -163,7 +163,6 @@ type Loadpoint struct { // charge progress vehicleSoc float64 // Vehicle or charger soc chargeDuration time.Duration // Charge duration - connectedDuration time.Duration // Connection duration energyMetrics EnergyMetrics // Stats for charged energy by session chargeRemainingDuration time.Duration // Remaining charge duration chargeRemainingEnergy float64 // Remaining charge energy in kWh @@ -1074,88 +1073,51 @@ func statusEvents(prevStatus, status api.ChargeStatus) []string { // updateChargerStatus updates charger status and detects car connected/disconnected events func (lp *Loadpoint) updateChargerStatus() (bool, error) { - statusChanges, err := lp.getStatusChanges() - if err != nil || len(statusChanges) == 0 { - return false, err + var welcomeCharge bool + + status, err := lp.charger.Status() + if err != nil { + return false, fmt.Errorf("charger status: %w", err) } - for _, status := range statusChanges { - prevStatus := lp.GetStatus() + lp.log.DEBUG.Printf("charger status: %s", status) + + if prevStatus := lp.GetStatus(); status != prevStatus { lp.setStatus(status) for _, ev := range statusEvents(prevStatus, status) { lp.bus.Publish(ev) // send connect/disconnect events except during startup - if prevStatus != api.StatusNone && (ev == evVehicleConnect || ev == evVehicleDisconnect) { - lp.pushEvent(ev) + if prevStatus != api.StatusNone { + switch ev { + case evVehicleConnect: + welcomeCharge = lp.chargerHasFeature(api.WelcomeCharge) || hasFeature(lp.defaultVehicle, api.WelcomeCharge) + + // Enable charging on connect if any available vehicle requires it. + // We're using the PV timer to disable after the welcome + if !welcomeCharge && !lp.chargerHasFeature(api.IntegratedDevice) { + for _, v := range lp.availableVehicles() { + if slices.Contains(v.Features(), api.WelcomeCharge) { + welcomeCharge = true + lp.log.DEBUG.Printf("welcome charge: %s", v.GetTitle()) + break + } + } + } + + lp.pushEvent(evVehicleConnect) + case evVehicleDisconnect: + lp.pushEvent(evVehicleDisconnect) + } } } + + // update whenever there is a state change + lp.bus.Publish(evChargeCurrent, lp.offeredCurrent) } - // update whenever there is a state change - lp.bus.Publish(evChargeCurrent, lp.offeredCurrent) - - return lp.needsWelcomeCharge(), nil -} - -// getStatusChanges checks charger status and returns a chronological list of status changes -func (lp *Loadpoint) getStatusChanges() ([]api.ChargeStatus, error) { - var res []api.ChargeStatus - - status, err := lp.charger.Status() - if err != nil { - return nil, fmt.Errorf("charger status: %w", err) - } - - lp.log.DEBUG.Printf("charger status: %s", status) - - // detect if charger status changed - if prevStatus := lp.GetStatus(); status != prevStatus { - res = append(res, status) - } - - // check charger connection duration - if ct, ok := lp.charger.(api.ConnectionTimer); ok { - d, err := ct.ConnectionDuration() - if err != nil { - return nil, fmt.Errorf("connection duration: %w", err) - } - - defer func() { lp.connectedDuration = d }() - - // connection duration dropped while status unchanged, indicates intermediate disconnect - if len(res) == 0 && d < lp.connectedDuration { - lp.log.DEBUG.Printf("connection duration drop detected (%s -> %v)", lp.connectedDuration.Round(time.Second), d.Round(time.Second)) - res = append(res, api.StatusA, status) - } - } - - return res, nil -} - -// needsWelcomeCharge checks if either the charger or a vehicle requires a welcome charge -func (lp *Loadpoint) needsWelcomeCharge() bool { - if !lp.connected() { - return false - } - - if lp.chargerHasFeature(api.WelcomeCharge) || hasFeature(lp.defaultVehicle, api.WelcomeCharge) { - return true - } - - // Enable charging on connect if any available vehicle requires it. - // We're using the PV timer to disable after the welcome - if !lp.chargerHasFeature(api.IntegratedDevice) { - for _, v := range lp.availableVehicles() { - if slices.Contains(v.Features(), api.WelcomeCharge) { - lp.log.DEBUG.Printf("welcome charge: %s", v.GetTitle()) - return true - } - } - } - - return false + return welcomeCharge, nil } // effectiveCurrent returns the currently effective charging current diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go index 284518305..38217abc5 100644 --- a/core/loadpoint_test.go +++ b/core/loadpoint_test.go @@ -764,49 +764,3 @@ func TestPVHysteresisAfterPhaseSwitch(t *testing.T) { ctrl.Finish() } } - -func TestConnectionDurationDropDetection(t *testing.T) { - clock := clock.NewMock() - ctrl := gomock.NewController(t) - ch := api.NewMockCharger(ctrl) - ct := api.NewMockConnectionTimer(ctrl) - - charger := struct { - api.Charger - api.ConnectionTimer - }{ - ch, ct, - } - - ch.EXPECT().Status().Return(api.StatusC, nil) - ch.EXPECT().Enabled().AnyTimes().Return(true, nil) - ch.EXPECT().MaxCurrent(int64(minA)).Return(nil) - - lp := &Loadpoint{ - log: util.NewLogger("foo"), - bus: evbus.New(), - clock: clock, - charger: charger, - minCurrent: minA, - maxCurrent: maxA, - chargeMeter: &Null{}, // silence nil panics - chargeRater: &Null{}, // silence nil panics - chargeTimer: &Null{}, // silence nil panics - wakeUpTimer: NewTimer(), // silence nil panics - } - - attachListeners(t, lp) - - connectedTime := clock.Now().Add(-10 * time.Minute) - - lp.enabled = true - lp.status = api.StatusC - lp.connectedDuration = 10 * time.Minute - lp.connectedTime = connectedTime - - ct.EXPECT().ConnectionDuration().Return(0*time.Second, nil) - lp.Update(500, 0, nil, nil, false, false, 0, nil, nil) - ctrl.Finish() - - assert.NotEqual(t, connectedTime, lp.connectedTime) -} diff --git a/templates/definition/charger/phoenix-charx.yaml b/templates/definition/charger/phoenix-charx.yaml index 9bea8cbe8..ffe63d7a7 100644 --- a/templates/definition/charger/phoenix-charx.yaml +++ b/templates/definition/charger/phoenix-charx.yaml @@ -9,15 +9,6 @@ products: - brand: LadeFoxx description: generic: Mikro 2.0 - - brand: Veton - description: - generic: One - - brand: Veton - description: - generic: Two - - brand: Veton - description: - generic: Wall params: - name: modbus choice: ["tcpip"]