2525// 2: Eco :
2626// Either modulate charge rate based solar PV generation (if only solar PV feed is available)
2727
28- // Or modulate charge rate based on on excess power (if grid feed (positive import / negative export) is available) i.e. power that would otherwise be exported to the grid is diverted to EVSE.
28+ // Or modulate charge rate based on on excess power (if grid feed (positive import / negative export) is available) i.e. power that would otherwise be exported to the grid is diverted to EVSE
2929// Note: it's Assumed EVSE power is included in grid feed e.g. (charge rate = gen - use - EVSE).
3030
3131// It's better to never import current from the grid but because of charging current quantification (min value of 6A and change in steps of 1A),
4040// Once charging begins it will not pause before a minimum amount of time has passed and this even if solar PV / excess power drops less then minimum charge rate.
4141// This avoids wear on the relay and the car.
4242
43- // Default to normal charging unless set. Divert mode always defaults back to 1 if unit is reset (divertmode not saved in EEPROM)
44- byte divertmode = DIVERT_MODE_NORMAL; // default normal mode
43+ // Default to normal charging unless set. Divert mode always defaults back to 1 if unit is reset (_mode not saved in EEPROM)
44+
4545int solar = 0 ;
4646int grid_ie = 0 ;
47- int charge_rate = 0 ;
48- int last_state = OPENEVSE_STATE_INVALID;
49- uint32_t lastUpdate = 0 ;
50-
51-
52- double available_current = 0 ;
53- double smoothed_available_current = 0 ;
54-
55- time_t min_charge_end = 0 ;
56-
57- bool divert_active = false ;
5847
5948// define as 'weak' so the simulator can override
6049time_t __attribute__ ((weak)) divertmode_get_time()
@@ -64,35 +53,85 @@ time_t __attribute__((weak)) divertmode_get_time()
6453 return now.tv_sec ;
6554}
6655
56+ DivertTask::DivertTask (EvseManager &evse) :
57+ _evse(&evse),
58+ _mode(DivertMode::Normal),
59+ _state(EvseState::None),
60+ _last_update(0 ),
61+ _charge_rate(0 ),
62+ _evseState(this ),
63+ _available_current(0 ),
64+ _smoothed_available_current(0 ),
65+ _min_charge_end(0 )
66+ {
67+
68+ }
69+
70+ DivertTask::~DivertTask ()
71+ {
72+
73+ }
74+
75+ void DivertTask::begin ()
76+ {
77+ MicroTask.startTask (this );
78+ }
79+
80+ void DivertTask::setup ()
81+ {
82+ _evse->onStateChange (&_evseState);
83+ }
84+
85+ unsigned long DivertTask::loop (MicroTasks::WakeReason reason)
86+ {
87+ DBUG (" Divert woke: " );
88+ DBUGLN (WakeReason_Scheduled == reason ? " WakeReason_Scheduled" :
89+ WakeReason_Event == reason ? " WakeReason_Event" :
90+ WakeReason_Message == reason ? " WakeReason_Message" :
91+ WakeReason_Manual == reason ? " WakeReason_Manual" :
92+ " UNKNOWN" );
93+
94+ if (_evseState.IsTriggered () && _evse_last_state != _evse->getEvseState ())
95+ {
96+ _evse_last_state = _evse->getEvseState ();
97+ if (_evse->isCharging ()) {
98+ _min_charge_end = divertmode_get_time () + divert_min_charge_time;
99+ }
100+ }
101+
102+ return MicroTask.Infinate ;
103+ }
104+
67105// Update divert mode e.g. Normal / Eco
68106// function called when divert mode is changed
69- void divertmode_update (byte newmode )
107+ void DivertTask::setMode (DivertMode mode )
70108{
71- DBUGF (" Set divertmode : %d" , newmode );
72- if (divertmode != newmode )
109+ DBUGF (" Set _mode : %d" , mode );
110+ if (_mode != mode )
73111 {
74- divertmode = newmode ;
112+ _mode = mode ;
75113
76114 StaticJsonDocument<128 > event;
77- event[" divertmode" ] = divertmode;
78- event[" divert_active" ] = divert_active = false ;
115+ event[" _mode" ] = (uint8_t )_mode;
116+ _state = EvseState::None;
117+ event[" divert_active" ] = false ;
79118
80- switch (divertmode )
119+ switch (_mode )
81120 {
82- case DIVERT_MODE_NORMAL :
83- evse. release (EvseClient_OpenEVSE_Divert);
121+ case DivertMode::Normal :
122+ _evse-> release (EvseClient_OpenEVSE_Divert);
84123 break ;
85124
86- case DIVERT_MODE_ECO :
125+ case DivertMode::Eco :
87126 {
88- min_charge_end = 0 ;
127+ _min_charge_end = 0 ;
89128
90- event[" charge_rate" ] = charge_rate = 0 ;
91- event[" available_current" ] = available_current = 0 ;
92- event[" smoothed_available_current" ] = smoothed_available_current = 0 ;
129+ event[" charge_rate" ] = _charge_rate = 0 ;
130+ event[" available_current" ] = _available_current = 0 ;
131+ event[" smoothed_available_current" ] = _smoothed_available_current = 0 ;
93132
94133 EvseProperties props (EvseState::Disabled);
95- evse. claim (EvseClient_OpenEVSE_Divert, EvseManager_Priority_Default, props);
134+ _evse-> claim (EvseClient_OpenEVSE_Divert, EvseManager_Priority_Default, props);
96135 } break ;
97136
98137 default :
@@ -103,19 +142,12 @@ void divertmode_update(byte newmode)
103142 }
104143}
105144
106- void divert_current_loop ()
107- {
108- Profile_Start (divert_current_loop);
109-
110- Profile_End (divert_current_loop, 5 );
111- } // end divert_current_loop
112-
113145// Set charge rate depending on divert mode and solar / grid_ie
114- void divert_update_state ()
146+ void DivertTask::update_state ()
115147{
116- Profile_Start (divert_update_state );
148+ Profile_Start (DivertTask::update_state );
117149
118- StaticJsonDocument<128 > event;
150+ StaticJsonDocument<256 > event;
119151 event[" divert_update" ] = 0 ;
120152
121153 if (mqtt_grid_ie != " " ) {
@@ -125,9 +157,9 @@ void divert_update_state()
125157 }
126158
127159 // If divert mode = Eco (2)
128- if (divertmode == DIVERT_MODE_ECO )
160+ if (_mode == DivertMode::Eco )
129161 {
130- double voltage = evse. getVoltage ();
162+ double voltage = _evse-> getVoltage ();
131163
132164 // Calculate current
133165 if (mqtt_grid_ie != " " )
@@ -142,7 +174,7 @@ void divert_update_state()
142174 DBUGVAR (Igrid_ie);
143175
144176 // Subtract the current charge the EV is using from the Grid IE
145- double amps = evse. getAmps ();
177+ double amps = _evse-> getAmps ();
146178 DBUGVAR (amps);
147179
148180 Igrid_ie -= amps;
@@ -153,87 +185,99 @@ void divert_update_state()
153185 // If excess power
154186 double reserve = (1000.0 * ((divert_PV_ratio > 1.0 ) ? (divert_PV_ratio - 1.0 ) : 0.0 )) / voltage;
155187 DBUGVAR (reserve);
156- available_current = (-Igrid_ie - reserve);
188+ _available_current = (-Igrid_ie - reserve);
157189 }
158190 else
159191 {
160192 // no excess, so use the min charge
161- available_current = 0 ;
193+ _available_current = 0 ;
162194 }
163195 }
164196 else if (mqtt_solar!=" " )
165197 {
166198 // if grid feed is not available: charge rate = solar generation
167199 DBUGVAR (voltage);
168- available_current = (double )solar / voltage;
200+ _available_current = (double )solar / voltage;
169201 }
170202
171- if (available_current < 0 ) {
172- available_current = 0 ;
203+ if (_available_current < 0 ) {
204+ _available_current = 0 ;
173205 }
174- DBUGVAR (available_current );
206+ DBUGVAR (_available_current );
175207
176- double scale = (available_current > smoothed_available_current ? divert_attack_smoothing_factor : divert_decay_smoothing_factor);
177- smoothed_available_current = (available_current * scale) + (smoothed_available_current * (1 - scale));
178- DBUGVAR (smoothed_available_current);
208+ double scale = (_available_current > _smoothed_available_current ?
209+ divert_attack_smoothing_factor :
210+ divert_decay_smoothing_factor);
211+ _smoothed_available_current = (_available_current * scale) + (_smoothed_available_current * (1 - scale));
212+ DBUGVAR (_smoothed_available_current);
179213
180- charge_rate = (int )floor (available_current );
214+ _charge_rate = (int )floor (_available_current );
181215 // if the remaining current can be used with a sufficient ratio of PV current in it, use it
182- if ((available_current - charge_rate ) > min (1.0 , divert_PV_ratio)) {
183- charge_rate += 1 ;
216+ if ((_available_current - _charge_rate ) > min (1.0 , divert_PV_ratio)) {
217+ _charge_rate += 1 ;
184218 }
185219
186- DBUGVAR (charge_rate);
220+ DBUGVAR (_charge_rate);
221+
222+ time_t min_charge_time_remaining = getMinChargeTimeRemaining ();
223+ DBUGVAR (min_charge_time_remaining);
187224
188- EvseState previousState = evse. getState ( );
225+ double trigger_current = _evse-> getMinCurrent () * min ( 1.0 , divert_PV_ratio );
189226
190227 // the smoothed current suffices to ensure a sufficient ratio of PV power
191- if (smoothed_available_current >= (evse. getMinCurrent () * min ( 1.0 , divert_PV_ratio)) )
228+ if (_smoothed_available_current >= trigger_current )
192229 {
193230 EvseProperties props (EvseState::Active);
194- props.setChargeCurrent (charge_rate);
195- evse.claim (EvseClient_OpenEVSE_Divert, EvseManager_Priority_Divert, props);
196-
197- if (previousState != evse.getState ())
231+ props.setChargeCurrent (_charge_rate);
232+ _evse->claim (EvseClient_OpenEVSE_Divert, EvseManager_Priority_Divert, props);
233+ }
234+ else
235+ {
236+ if ( EvseState::Active == _evse->getState (EvseClient_OpenEVSE_Divert) && 0 == min_charge_time_remaining)
198237 {
199- DBUGLN (F (" Charge Started" ));
200- min_charge_end = divertmode_get_time () + divert_min_charge_time;
201- event[" divert_active" ] = divert_active = true ;
238+ EvseProperties props (EvseState::Disabled);
239+ _evse->claim (EvseClient_OpenEVSE_Divert, EvseManager_Priority_Default, props);
202240 }
203241 }
204- else
242+
243+ EvseState current_evse_state = _evse->getState ();
244+ if (_evse->getState (EvseClient_OpenEVSE_Divert) == current_evse_state)
205245 {
206- if (EvseState::Active == evse.getState (EvseClient_OpenEVSE_Divert))
246+ // We are in the state we expect
247+ if (_state != current_evse_state)
207248 {
208- if (divert_active && divertmode_get_time () >= min_charge_end)
209- {
210- EvseProperties props (EvseState::Disabled);
211- evse.claim (EvseClient_OpenEVSE_Divert, EvseManager_Priority_Default, props);
212-
213- if (previousState != evse.getState ())
214- {
215- DBUGLN (F (" Charge Stopped" ));
216- min_charge_end = divertmode_get_time () + divert_min_charge_time;
217- event[" divert_active" ] = divert_active = false ;
218- }
219- }
249+ _state = current_evse_state;
250+ event[" divert_active" ] = isActive ();
220251 }
221252 }
222253
223- event[" charge_rate" ] = charge_rate;
254+ event[" charge_rate" ] = _charge_rate;
255+ event[" trigger_current" ] = trigger_current;
224256 event[" voltage" ] = voltage;
225- event[" available_current" ] = available_current;
226- event[" smoothed_available_current" ] = smoothed_available_current;
227- event[" pilot" ] = evse.getChargeCurrent ();
228- event[" min_charge_end" ] = (divert_active && divertmode_get_time () < min_charge_end) ?
229- min_charge_end - divertmode_get_time () :
230- 0 ;
257+ event[" available_current" ] = _available_current;
258+ event[" smoothed_available_current" ] = _smoothed_available_current;
259+ event[" pilot" ] = _evse->getChargeCurrent ();
260+ event[" min_charge_end" ] = min_charge_time_remaining;
231261 } // end ecomode
232262
233263 event_send (event);
234264 emoncms_publish (event);
235265
236- lastUpdate = millis ();
266+ _last_update = millis ();
237267
238- Profile_End (divert_update_state , 5 );
268+ Profile_End (DivertTask::update_state , 5 );
239269} // end divert_update_state
270+
271+ bool DivertTask::isActive ()
272+ {
273+ return EvseState::Active == _state;
274+ }
275+
276+ time_t DivertTask::getMinChargeTimeRemaining ()
277+ {
278+ return (isActive () &&
279+ _evse->isCharging () &&
280+ divertmode_get_time () < _min_charge_end) ?
281+ _min_charge_end - divertmode_get_time () :
282+ 0 ;
283+ }
0 commit comments