Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove guard timer but keep charger sync logic #12084

Merged
merged 22 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions assets/js/components/Loadpoint.vue
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,6 @@ export default {
phaseRemaining: Number,
pvRemaining: Number,
pvAction: String,
guardRemaining: Number,
guardAction: String,
smartCostLimit: Number,
smartCostType: String,
smartCostActive: Boolean,
Expand All @@ -196,7 +194,6 @@ export default {
tickerHandler: null,
phaseRemainingInterpolated: this.phaseRemaining,
pvRemainingInterpolated: this.pvRemaining,
guardRemainingInterpolated: this.guardRemaining,
chargeDurationInterpolated: this.chargeDuration,
chargeRemainingDurationInterpolated: this.chargeRemainingDuration,
};
Expand Down Expand Up @@ -252,9 +249,6 @@ export default {
pvRemaining() {
this.pvRemainingInterpolated = this.pvRemaining;
},
guardRemaining() {
this.guardRemainingInterpolated = this.guardRemaining;
},
chargeDuration() {
this.chargeDurationInterpolated = this.chargeDuration;
},
Expand All @@ -276,9 +270,6 @@ export default {
if (this.pvRemainingInterpolated > 0) {
this.pvRemainingInterpolated--;
}
if (this.guardRemainingInterpolated > 0) {
this.guardRemainingInterpolated--;
}
if (this.chargeDurationInterpolated > 0 && this.charging) {
this.chargeDurationInterpolated++;
}
Expand Down
3 changes: 0 additions & 3 deletions assets/js/components/Vehicle.story.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ const hoursFromNow = function (hours) {
:pvRemainingInterpolated="155"
/>
</Variant>
<Variant title="guard timer">
<Vehicle v-bind="state" guardAction="enable" :guardRemainingInterpolated="123" />
</Variant>
<Variant title="vehicle switch">
<Vehicle
v-bind="state"
Expand Down
2 changes: 0 additions & 2 deletions assets/js/components/Vehicle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ export default {
effectivePlanSoc: Number,
effectivePlanTime: String,
enabled: Boolean,
guardAction: String,
guardRemainingInterpolated: Number,
heating: Boolean,
id: [String, Number],
integratedDevice: Boolean,
Expand Down
37 changes: 0 additions & 37 deletions assets/js/components/VehicleStatus.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,43 +144,6 @@ describe("timer", () => {
{ remaining: "1:30\u202Fm" }
);
});
test("show guard timer if it exists", () => {
expectStatus(
{
connected: true,
guardAction: "enable",
guardRemainingInterpolated: 90,
},
"guard",
{ remaining: "1:30\u202Fm" }
);
});
test("don't show guard timer if another timer exists", () => {
expectStatus(
{
connected: true,
charging: true,
pvAction: "disable",
pvRemainingInterpolated: 30,
guardAction: "enable",
guardRemainingInterpolated: 90,
},
"pvDisable",
{ remaining: "30\u202Fs" }
);
});
test("show guard timer if charging and no other timer exists", () => {
expectStatus(
{
connected: true,
charging: true,
guardAction: "enable",
guardRemainingInterpolated: 90,
},
"guard",
{ remaining: "1:30\u202Fm" }
);
});
});

describe("vehicle target soc", () => {
Expand Down
11 changes: 0 additions & 11 deletions assets/js/components/VehicleStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ export default {
phaseRemainingInterpolated: Number,
pvAction: String,
pvRemainingInterpolated: Number,
guardAction: String,
guardRemainingInterpolated: Number,
targetChargeDisabled: Boolean,
vehicleClimaterActive: Boolean,
smartCostLimit: Number,
Expand All @@ -48,9 +46,6 @@ export default {
this.pvRemainingInterpolated > 0 && ["enable", "disable"].includes(this.pvAction)
);
},
guardTimerActive() {
return this.guardRemainingInterpolated > 0 && this.guardAction === "enable";
},
message: function () {
const t = (key, data) => {
if (this.heating) {
Expand Down Expand Up @@ -128,12 +123,6 @@ export default {
});
}
if (this.guardTimerActive) {
return t("guard", {
remaining: this.fmtDuration(this.guardRemainingInterpolated),
});
}
if (this.charging) {
return t("charging");
}
Expand Down
75 changes: 19 additions & 56 deletions core/loadpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ const (
pvEnable = "enable"
pvDisable = "disable"

guardTimer = "guard"
guardEnable = "enable"

phaseTimer = "phase"
phaseScale1p = "scale1p"
phaseScale3p = "scale3p"
Expand All @@ -55,9 +52,8 @@ const (
minActiveCurrent = 1.0 // minimum current at which a phase is treated as active
minActiveVoltage = 207 // minimum voltage at which a phase is treated as active

guardGracePeriod = 60 * time.Second // allow out of sync during this timespan
phaseSwitchCommandTimeout = 30 * time.Second // do not sync charger enabled/disabled state during this timespan
phaseSwitchDuration = 60 * time.Second // do not measure phases during this timespan
chargerSwitchDuration = 60 * time.Second // allow out of sync during this timespan
phaseSwitchDuration = 60 * time.Second // allow out of sync and do not measure phases during this timespan
)

// elapsed is the time an expired timer will be set to
Expand Down Expand Up @@ -116,12 +112,12 @@ type Loadpoint struct {
MeterRef string `mapstructure:"meter"` // Charge meter reference
Soc SocConfig
Enable, Disable ThresholdConfig
GuardDuration time.Duration // charger enable/disable minimum holding time
MarkusGH marked this conversation as resolved.
Show resolved Hide resolved

// TODO deprecated
ConfiguredPhases_ int `mapstructure:"phases"`
MinCurrent_ float64 `mapstructure:"minCurrent"`
MaxCurrent_ float64 `mapstructure:"maxCurrent"`
GuardDuration_ time.Duration `mapstructure:"guardduration"` // charger enable/disable minimum holding time
ConfiguredPhases_ int `mapstructure:"phases"`
MinCurrent_ float64 `mapstructure:"minCurrent"`
MaxCurrent_ float64 `mapstructure:"maxCurrent"`

minCurrent float64 // PV mode: start current Min+PV mode: min current
maxCurrent float64 // Max allowed current. Physically ensured by the charger
Expand All @@ -134,9 +130,9 @@ type Loadpoint struct {
phases int // Charger enabled phases, guarded by mutex
measuredPhases int // Charger physically measured phases
chargeCurrent float64 // Charger current limit
guardUpdated time.Time // Charger enabled/disabled timestamp
socUpdated time.Time // Soc updated timestamp (poll: connected)
vehicleDetect time.Time // Vehicle connected timestamp
chargerSwitched time.Time // Charger enabled/disabled timestamp
phasesSwitched time.Time // Phase switch timestamp
vehicleDetectTicker *clock.Ticker
vehicleIdentifier string
Expand Down Expand Up @@ -295,7 +291,6 @@ func NewLoadpoint(log *util.Logger, settings *Settings) *Loadpoint {
},
Enable: ThresholdConfig{Delay: time.Minute, Threshold: 0}, // t, W
Disable: ThresholdConfig{Delay: 3 * time.Minute, Threshold: 0}, // t, W
GuardDuration: 5 * time.Minute,
sessionEnergy: NewEnergyMetrics(),
progress: NewProgress(0, 10), // soc progress indicator
coordinator: coordinator.NewDummy(), // dummy vehicle coordinator
Expand Down Expand Up @@ -600,7 +595,6 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
lp.publish(keys.PhasesActive, lp.ActivePhases())
lp.publishTimer(phaseTimer, 0, timerInactive)
lp.publishTimer(pvTimer, 0, timerInactive)
lp.publishTimer(guardTimer, 0, timerInactive)

if phases := lp.getChargerPhysicalPhases(); phases != 0 {
lp.publish(keys.ChargerPhysicalPhases, phases)
Expand Down Expand Up @@ -641,7 +635,6 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
// read initial charger state to prevent immediately disabling charger
if enabled, err := lp.charger.Enabled(); err == nil {
if lp.enabled = enabled; enabled {
lp.guardUpdated = lp.clock.Now()
// set defined current for use by pv mode
_ = lp.setLimit(lp.effectiveMinCurrent(), false)
}
Expand All @@ -662,7 +655,7 @@ func (lp *Loadpoint) syncCharger() error {
return err
}

if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
defer func() {
lp.enabled = enabled
lp.publish(keys.Enabled, lp.enabled)
Expand All @@ -672,11 +665,10 @@ func (lp *Loadpoint) syncCharger() error {
if !enabled && lp.charging() {
lp.log.WARN.Println("charger logic error: disabled but charging")
enabled = true // treat as enabled when charging
if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
if err := lp.charger.Enable(true); err != nil { // also enable charger to correct internal state
return err
}
lp.elapseGuard()
lp.elapsePVTimer()
return nil
}
Expand All @@ -693,10 +685,9 @@ func (lp *Loadpoint) syncCharger() error {

// smallest adjustment most PWM-Controllers can do is: 100%÷256×0,6A = 0.234A
if math.Abs(lp.chargeCurrent-current) > 0.23 {
if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
lp.log.WARN.Printf("charger logic error: current mismatch (got %.3gA, expected %.3gA)", current, lp.chargeCurrent)
}

lp.chargeCurrent = current
lp.bus.Publish(evChargeCurrent, lp.chargeCurrent)
}
Expand All @@ -705,13 +696,9 @@ func (lp *Loadpoint) syncCharger() error {
return nil
}

if enabled || lp.phaseSwitchCommandTimeoutElapsed() {
// ignore disabled state if vehicle was disconnected ^(lp.enabled && ^lp.connected)
if lp.guardGracePeriodElapsed() && lp.phaseSwitchCompleted() && (enabled || lp.connected()) {
lp.log.WARN.Printf("charger out of sync: expected %vd, got %vd", status[lp.enabled], status[enabled])
lp.elapseGuard()
}
return nil
// ignore disabled state if vehicle was disconnected ^(lp.enabled && ^lp.connected)
MarkusGH marked this conversation as resolved.
Show resolved Hide resolved
if lp.chargerUpdateCompleted() && lp.phaseSwitchCompleted() && (enabled || lp.connected()) {
lp.log.WARN.Printf("charger out of sync: expected %vd, got %vd", status[lp.enabled], status[enabled])
}

return nil
Expand Down Expand Up @@ -752,12 +739,6 @@ func (lp *Loadpoint) setLimit(chargeCurrent float64, force bool) error {
lp.bus.Publish(evChargeCurrent, chargeCurrent)
}

if lp.clock.Since(lp.guardUpdated).Truncate(time.Second) < lp.GuardDuration && !force {
lp.publishTimer(guardTimer, lp.GuardDuration, guardEnable)
return nil
}
lp.elapseGuard()

// set enabled/disabled
if enabled := chargeCurrent >= lp.effectiveMinCurrent(); enabled != lp.enabled {
if err := lp.charger.Enable(enabled); err != nil {
Expand All @@ -777,7 +758,7 @@ func (lp *Loadpoint) setLimit(chargeCurrent float64, force bool) error {
lp.log.DEBUG.Printf("charger %s", status[enabled])
lp.enabled = enabled
lp.publish(keys.Enabled, lp.enabled)
lp.guardUpdated = lp.clock.Now()
lp.chargerSwitched = lp.clock.Now()

lp.bus.Publish(evChargeCurrent, chargeCurrent)

Expand Down Expand Up @@ -968,8 +949,6 @@ func (lp *Loadpoint) elapsePVTimer() {

lp.pvTimer = elapsed
lp.publishTimer(pvTimer, 0, timerInactive)

lp.elapseGuard()
}

// resetPVTimer resets the pv enable/disable timer to disabled state
Expand Down Expand Up @@ -1061,7 +1040,7 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) bo
// - https://github.com/evcc-io/evcc/issues/2613
measuredPhases := lp.getMeasuredPhases()
if phases > 0 && phases < measuredPhases {
if lp.guardGracePeriodElapsed() {
if lp.chargerUpdateCompleted() {
lp.log.WARN.Printf("ignoring inconsistent phases: %dp < %dp observed active", phases, measuredPhases)
}
lp.resetMeasuredPhases()
Expand Down Expand Up @@ -1140,9 +1119,6 @@ func (lp *Loadpoint) publishTimer(name string, delay time.Duration, action strin
if name == phaseTimer {
timer = lp.phaseTimer
}
if name == guardTimer {
timer = lp.guardUpdated
}

remaining := delay - lp.clock.Since(timer)
if remaining < 0 {
Expand Down Expand Up @@ -1472,14 +1448,6 @@ func (lp *Loadpoint) publishSocAndRange() {
}
}

func (lp *Loadpoint) elapseGuard() {
if lp.guardUpdated != elapsed {
lp.log.DEBUG.Print("charger: guard elapse")
lp.guardUpdated = elapsed
lp.publishTimer(guardTimer, 0, timerInactive)
}
}

// addTask adds a single task to the queue
func (lp *Loadpoint) addTask(task func()) {
// test guard
Expand Down Expand Up @@ -1515,17 +1483,12 @@ func (lp *Loadpoint) stopWakeUpTimer() {
lp.wakeUpTimer.Stop()
}

// guardGracePeriodElapsed checks if last guard update is within guard grace period
func (lp *Loadpoint) guardGracePeriodElapsed() bool {
return time.Since(lp.guardUpdated) > guardGracePeriod
}

// phaseSwitchCommandTimeoutElapsed returns true if phase switch command should be already processed by the charger
func (lp *Loadpoint) phaseSwitchCommandTimeoutElapsed() bool {
return time.Since(lp.phasesSwitched) > phaseSwitchCommandTimeout
// chargerUpdateCompleted returns true if enable command should be already processed by the charger (so we can try to sync charger and loadpoint)
func (lp *Loadpoint) chargerUpdateCompleted() bool {
return time.Since(lp.chargerSwitched) > chargerSwitchDuration
}

// phaseSwitchCompleted returns true if phase switch has completed
// phaseSwitchCompleted returns true if phase switch command should be already processed by the charger (so we can try to sync charger and loadpoint and are able to measure currents)
func (lp *Loadpoint) phaseSwitchCompleted() bool {
return time.Since(lp.phasesSwitched) > phaseSwitchDuration
}
Expand Down
Loading
Loading