@@ -29,6 +29,15 @@ const (
2929 minActiveCurrent = 1.0 // minimum current at which a phase is treated as active
3030)
3131
32+ // SoCConfig defines soc settings, estimation and update behaviour
33+ type SoCConfig struct {
34+ AlwaysUpdate bool `mapstructure:"alwaysUpdate"`
35+ Levels []int `mapstructure:"levels"`
36+ Estimate bool `mapstructure:"estimate"`
37+ Min int `mapstructure:"min"` // Default minimum SoC, guarded by mutex
38+ Target int `mapstructure:"target"` // Default target SoC, guarded by mutex
39+ }
40+
3241// ThresholdConfig defines enable/disable hysteresis parameters
3342type ThresholdConfig struct {
3443 Delay time.Duration
@@ -47,8 +56,7 @@ type LoadPoint struct {
4756
4857 // exposed public configuration
4958 sync.Mutex // guard status
50- Mode api.ChargeMode `mapstructure:"mode"` // Charge mode, guarded by mutex
51- TargetSoC int `mapstructure:"targetSoC"` // Target SoC, guarded by mutex
59+ Mode api.ChargeMode `mapstructure:"mode"` // Charge mode, guarded by mutex
5260
5361 Title string `mapstructure:"title"` // UI title
5462 Phases int64 `mapstructure:"phases"` // Phases- required for converting power and current
@@ -57,11 +65,7 @@ type LoadPoint struct {
5765 Meters struct {
5866 ChargeMeterRef string `mapstructure:"charge"` // Charge meter reference
5967 }
60- SoC struct {
61- AlwaysUpdate bool `mapstructure:"alwaysUpdate"`
62- Levels []int `mapstructure:"levels"`
63- Estimate bool `mapstructure:"estimate"`
64- }
68+ SoC SoCConfig
6569 OnDisconnect struct {
6670 Mode api.ChargeMode `mapstructure:"mode"` // Charge mode to apply when car disconnected
6771 TargetSoC int `mapstructure:"targetSoC"` // Target SoC to apply when car disconnected
@@ -102,11 +106,14 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin
102106 lp .OnDisconnect .Mode = api .ChargeModeString (string (lp .OnDisconnect .Mode ))
103107
104108 sort .Ints (lp .SoC .Levels )
105- if lp .TargetSoC == 0 {
106- lp .TargetSoC = 100
109+ if lp .SoC .Target == 0 {
110+ lp .SoC .Target = lp .OnDisconnect .TargetSoC // use disconnect value as default soc
111+ if lp .SoC .Target == 0 {
112+ lp .SoC .Target = 100
113+ }
107114
108115 if len (lp .SoC .Levels ) > 0 {
109- lp .TargetSoC = lp .SoC .Levels [len (lp .SoC .Levels )- 1 ]
116+ lp .SoC . Target = lp .SoC .Levels [len (lp .SoC .Levels )- 1 ]
110117 }
111118 }
112119
@@ -162,50 +169,6 @@ func NewLoadPoint(log *util.Logger) *LoadPoint {
162169 return lp
163170}
164171
165- // GetMode returns loadpoint charge mode
166- func (lp * LoadPoint ) GetMode () api.ChargeMode {
167- lp .Lock ()
168- defer lp .Unlock ()
169- return lp .Mode
170- }
171-
172- // SetMode sets loadpoint charge mode
173- func (lp * LoadPoint ) SetMode (mode api.ChargeMode ) {
174- lp .Lock ()
175- defer lp .Unlock ()
176-
177- lp .log .INFO .Printf ("set charge mode: %s" , string (mode ))
178-
179- // apply immediately
180- if lp .Mode != mode {
181- lp .Mode = mode
182- lp .publish ("mode" , mode )
183- lp .requestUpdate ()
184- }
185- }
186-
187- // GetTargetSoC returns loadpoint charge targetSoC
188- func (lp * LoadPoint ) GetTargetSoC () int {
189- lp .Lock ()
190- defer lp .Unlock ()
191- return lp .TargetSoC
192- }
193-
194- // SetTargetSoC sets loadpoint charge targetSoC
195- func (lp * LoadPoint ) SetTargetSoC (targetSoC int ) {
196- lp .Lock ()
197- defer lp .Unlock ()
198-
199- lp .log .INFO .Println ("set target soc:" , targetSoC )
200-
201- // apply immediately
202- if lp .TargetSoC != targetSoC {
203- lp .TargetSoC = targetSoC
204- lp .publish ("targetSoC" , targetSoC )
205- lp .requestUpdate ()
206- }
207- }
208-
209172// requestUpdate requests site to update this loadpoint
210173func (lp * LoadPoint ) requestUpdate () {
211174 select {
@@ -309,7 +272,7 @@ func (lp *LoadPoint) evVehicleDisconnectHandler() {
309272 lp .SetMode (lp .OnDisconnect .Mode )
310273 }
311274 if lp .OnDisconnect .TargetSoC != 0 {
312- lp .SetTargetSoC (lp .OnDisconnect .TargetSoC )
275+ _ = lp .SetTargetSoC (lp .OnDisconnect .TargetSoC )
313276 }
314277}
315278
@@ -364,7 +327,8 @@ func (lp *LoadPoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
364327 // publish initial values
365328 lp .Lock ()
366329 lp .publish ("mode" , lp .Mode )
367- lp .publish ("targetSoC" , lp .TargetSoC )
330+ lp .publish ("targetSoC" , lp .SoC .Target )
331+ lp .publish ("minSoC" , lp .SoC .Min )
368332 lp .Unlock ()
369333
370334 // prepare charger status
@@ -376,10 +340,20 @@ func (lp *LoadPoint) connected() bool {
376340 return lp .status == api .StatusB || lp .status == api .StatusC
377341}
378342
379- // targetSocReached checks if targetSoC configured and reached
380- func (lp * LoadPoint ) targetSocReached (socCharge , targetSoC float64 ) bool {
381- // check for vehicle != nil is not necessary as socCharge would be zero then
382- return targetSoC > 0 && targetSoC < 100 && socCharge >= targetSoC
343+ // targetSocReached checks if target is configured and reached.
344+ // If vehicle is not configured this will always return false
345+ func (lp * LoadPoint ) targetSocReached () bool {
346+ return lp .vehicle != nil &&
347+ lp .SoC .Target > 0 &&
348+ lp .socCharge >= float64 (lp .SoC .Target )
349+ }
350+
351+ // minSocNotReached checks if minimum is configured and not reached.
352+ // If vehicle is not configured this will always return true
353+ func (lp * LoadPoint ) minSocNotReached () bool {
354+ return lp .vehicle != nil &&
355+ lp .SoC .Min > 0 &&
356+ lp .socCharge < float64 (lp .SoC .Min )
383357}
384358
385359// climateActive checks if vehicle has active climate request
@@ -481,8 +455,13 @@ func (lp *LoadPoint) detectPhases() {
481455 }
482456}
483457
484- // maxCurrent calculates the maximum target current for PV mode
485- func (lp * LoadPoint ) maxCurrent (mode api.ChargeMode , sitePower float64 ) int64 {
458+ // pvDisableTimer puts the pv enable/disable timer into elapsed state
459+ func (lp * LoadPoint ) pvDisableTimer () {
460+ lp .pvTimer = time .Now ().Add (- lp .Disable .Delay )
461+ }
462+
463+ // pvMaxCurrent calculates the maximum target current for PV mode
464+ func (lp * LoadPoint ) pvMaxCurrent (mode api.ChargeMode , sitePower float64 ) int64 {
486465 // calculate target charge current from delta power and actual current
487466 effectiveCurrent := lp .handler .TargetCurrent ()
488467 if lp .status != api .StatusC {
@@ -624,11 +603,11 @@ func (lp *LoadPoint) publishSoC() {
624603
625604 chargeEstimate := time .Duration (- 1 )
626605 if lp .charging {
627- chargeEstimate = lp .socEstimator .RemainingChargeDuration (lp .chargePower , lp .TargetSoC )
606+ chargeEstimate = lp .socEstimator .RemainingChargeDuration (lp .chargePower , lp .SoC . Target )
628607 }
629608 lp .publish ("chargeEstimate" , chargeEstimate )
630609
631- chargeRemainingEnergy := 1e3 * lp .socEstimator .RemainingChargeEnergy (lp .TargetSoC )
610+ chargeRemainingEnergy := 1e3 * lp .socEstimator .RemainingChargeEnergy (lp .SoC . Target )
632611 lp .publish ("chargeRemainingEnergy" , chargeRemainingEnergy )
633612
634613 return
@@ -684,8 +663,8 @@ func (lp *LoadPoint) Update(sitePower float64) {
684663 // https://github.com/andig/evcc/issues/105
685664 err = lp .handler .Ramp (0 )
686665
687- case lp .targetSocReached (lp . socCharge , float64 ( lp . TargetSoC ) ):
688- var targetCurrent int64
666+ case lp .targetSocReached ():
667+ var targetCurrent int64 // zero disables
689668 if lp .climateActive () {
690669 targetCurrent = lp .MinCurrent
691670 }
@@ -694,11 +673,15 @@ func (lp *LoadPoint) Update(sitePower float64) {
694673 case mode == api .ModeOff :
695674 err = lp .handler .Ramp (0 , true )
696675
676+ case lp .minSocNotReached ():
677+ err = lp .handler .Ramp (lp .MaxCurrent , true )
678+ lp .pvDisableTimer () // let PV mode disable immediately afterwards
679+
697680 case mode == api .ModeNow :
698681 err = lp .handler .Ramp (lp .MaxCurrent , true )
699682
700683 case mode == api .ModeMinPV || mode == api .ModePV :
701- targetCurrent := lp .maxCurrent (mode , sitePower )
684+ targetCurrent := lp .pvMaxCurrent (mode , sitePower )
702685 lp .log .DEBUG .Printf ("target charge current: %dA" , targetCurrent )
703686
704687 var required bool // false
0 commit comments