Skip to content

Commit 7d05dcf

Browse files
authored
Merge pull request #431 from jeremypoulter/divert_updates
Divert updates
2 parents 15f74d8 + f42df1f commit 7d05dcf

File tree

17 files changed

+3265
-3074
lines changed

17 files changed

+3265
-3074
lines changed

gui

models/Status.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ properties:
130130
charge_rate:
131131
type: integer
132132
description: The divert charge rate
133+
trigger_current:
134+
type: integer
135+
description: The current at which the divert module will start the charge
133136
available_current:
134137
type: integer
135138
description: The computed avalible current for divert
@@ -141,7 +144,7 @@ properties:
141144
description: The time since the last update of the divert feed
142145
divert_active:
143146
type: boolean
144-
description: '`true` if divert is active, `false` if not'
147+
description: '`true` if divert is actively enabling charging, `false` if not'
145148
ota_update:
146149
type: integer
147150
description: '`1`, if there is an OTA update active, `0` if normal operation'

platformio.ini

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ debug_flags =
4949
-D ENABLE_DEBUG
5050
#-D ENABLE_DEBUG_WEB
5151
#-D ENABLE_DEBUG_WEB_REQUEST
52-
-D ENABLE_DEBUG_SCHEDULER
53-
# -D ENABLE_DEBUG_TIME
52+
#-D ENABLE_DEBUG_SCHEDULER
53+
#-D ENABLE_DEBUG_TIME
5454
#-D ENABLE_DEBUG_EVSE_MAN
55-
-D ENABLE_DEBUG_EVSE_MONITOR
55+
#-D ENABLE_DEBUG_EVSE_MONITOR
5656
#-D ENABLE_DEBUG_DIVERT
5757
#-D ENABLE_DEBUG_LED
5858
#-D ENABLE_DEBUG_LCD
@@ -266,7 +266,6 @@ build_flags =
266266
-D RANDOM_SEED_CHANNEL=1
267267
-D RX2=16
268268
-D TX2=32
269-
-D ETH_PHY_POWER=5
270269
-D RESET_ETH_PHY_ON_BOOT=1
271270
board_build.extra_flags = "-DARDUINO_ESP32_GATEWAY=\'E\'"
272271
upload_speed = 921600

src/app_config.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ ConfigOpt *opts[] =
115115

116116
// Language String
117117
new ConfigOptDefenition<String>(lang, "", "lang", "lan"),
118-
118+
119119
// Web server authentication (leave blank for none)
120120
new ConfigOptDefenition<String>(www_username, "", "www_username", "au"),
121121
new ConfigOptSecret(www_password, "", "www_password", "ap"),
@@ -245,7 +245,7 @@ void config_changed(String name)
245245
if(name == "time_zone") {
246246
config_set_timezone(time_zone);
247247
} else if(name == "flags") {
248-
divertmode_update((config_divert_enabled() && 1 == config_charge_mode()) ? DIVERT_MODE_ECO : DIVERT_MODE_NORMAL);
248+
divert.setMode((config_divert_enabled() && 1 == config_charge_mode()) ? DivertMode::Eco : DivertMode::Normal);
249249
if(mqtt_connected() != config_mqtt_enabled()) {
250250
mqtt_restart();
251251
}
@@ -265,7 +265,7 @@ void config_changed(String name)
265265
} else if(name == "divert_enabled" || name == "charge_mode") {
266266
DBUGVAR(config_divert_enabled());
267267
DBUGVAR(config_charge_mode());
268-
divertmode_update((config_divert_enabled() && 1 == config_charge_mode()) ? DIVERT_MODE_ECO : DIVERT_MODE_NORMAL);
268+
divert.setMode((config_divert_enabled() && 1 == config_charge_mode()) ? DivertMode::Eco : DivertMode::Normal);
269269
} else if(name.startsWith("current_shaper_")) {
270270
shaper.notifyConfigChanged(config_current_shaper_enabled()?1:0,current_shaper_max_pwr);
271271
} else if(name == "tesla_vehicle_id") {

src/divert.cpp

Lines changed: 131 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
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),
@@ -40,21 +40,10 @@
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+
4545
int solar = 0;
4646
int 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
6049
time_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

Comments
 (0)