forked from matth-x/MicroOcpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOcppEngine.cpp
More file actions
492 lines (419 loc) · 16.1 KB
/
Copy pathOcppEngine.cpp
File metadata and controls
492 lines (419 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
// matth-x/ArduinoOcpp
// Copyright Matthias Akstaller 2019 - 2021
// MIT License
#include <ArduinoOcpp/Core/OcppEngine.h>
#include <ArduinoOcpp/SimpleOcppOperationFactory.h>
#include <ArduinoOcpp/Core/OcppSocket.h>
#include <ArduinoOcpp/Core/OcppConnection.h>
#include <ArduinoOcpp/Core/OcppError.h>
#include <Variants.h>
namespace ArduinoOcpp {
namespace OcppEngine {
OcppSocket *ocppSocket;
SmartChargingService *ocppEngine_smartChargingService;
ChargePointStatusService *ocppEngine_chargePointStatusService;
MeteringService *ocppEngine_meteringService;
OcppTime *ocppEngine_ocppTime;
std::vector<OcppOperation*> initiatedOcppOperations;
std::vector<OcppOperation*> receivedOcppOperations;
int JSON_DOC_SIZE;
#define HEAP_GUARD 2000UL //will not accept JSON messages if it will result in less than HEAP_GUARD free bytes in heap
//size_t removePayload(const char *src, size_t src_size, char *dst, size_t dst_size);
OcppSocket *ocppSock;
OcppConnection *mConnection;
} //end namespace OcppEngine
using namespace ArduinoOcpp::OcppEngine;
void ocppEngine_initialize(OcppSocket *ocppSocket){
ocppSock = ocppSocket;
mConnection = new OcppConnection(ocppSock);
}
void initiateOcppOperation(OcppOperation *o) {
mConnection->initiateOcppOperation(o);
}
void ocppEngine_loop() {
ocppSock->loop();
mConnection->loop();
}
#if 0
/**
* Process raw application data coming from the websocket stream
*/
bool processWebSocketEvent(const char* payload, size_t length){
boolean deserializationSuccess = false;
DynamicJsonDocument *doc = NULL;
size_t capacity = length + 100;
DeserializationError err = DeserializationError::NoMemory;
while (capacity + HEAP_GUARD < ESP.getFreeHeap() && err == DeserializationError::NoMemory) {
if (doc){
delete doc;
doc = NULL;
}
doc = new DynamicJsonDocument(capacity);
err = deserializeJson(*doc, payload, length);
capacity /= 2;
capacity *= 3;
}
//TODO insert validateRpcHeader at suitable position
switch (err.code()) {
case DeserializationError::Ok:
if (doc != NULL){
int messageTypeId = (*doc)[0];
switch(messageTypeId) {
case MESSAGE_TYPE_CALL:
handleReqMessage(doc);
deserializationSuccess = true;
break;
case MESSAGE_TYPE_CALLRESULT:
handleConfMessage(doc);
deserializationSuccess = true;
break;
case MESSAGE_TYPE_CALLERROR:
handleErrMessage(doc);
deserializationSuccess = true;
break;
default:
Serial.print(F("[OcppEngine] Invalid OCPP message! (though JSON has successfully been deserialized)\n"));
break;
}
} else { //unlikely corner case
Serial.print(F("[OcppEngine] Deserialization is okay but doc is Null\n"));
}
break;
case DeserializationError::InvalidInput:
Serial.print(F("[OcppEngine] Invalid input! Not a JSON\n"));
break;
case DeserializationError::NoMemory:
{
if (DEBUG_OUT) Serial.print(F("[OcppEngine] Error: Not enough memory in heap! Input length = "));
if (DEBUG_OUT) Serial.print(length);
if (DEBUG_OUT) Serial.print(F(", free heap = "));
if (DEBUG_OUT) Serial.println(ESP.getFreeHeap());
/*
* If websocket input is of message type MESSAGE_TYPE_CALL, send back a message of type MESSAGE_TYPE_CALLERROR.
* Then the communication counterpart knows that this operation failed.
* If the input type is MESSAGE_TYPE_CALLRESULT, it can be ignored. This controller will automatically resend the corresponding requirement message.
*/
if (doc){
delete doc;
doc = NULL;
}
doc = new DynamicJsonDocument(200);
char onlyRpcHeader[200];
size_t onlyRpcHeader_len = removePayload(payload, length, onlyRpcHeader, 200);
DeserializationError err2 = deserializeJson(*doc, onlyRpcHeader, onlyRpcHeader_len);
if (err2.code() == DeserializationError::Ok) {
int messageTypeId2 = (*doc)[0] | -1;
if (messageTypeId2 == MESSAGE_TYPE_CALL) {
deserializationSuccess = true;
OcppOperation *op = makeOcppOperation(wsocket, new OutOfMemory(ESP.getFreeHeap(), length));
handleReqMessage(doc, op);
}
}
}
break;
default:
Serial.print(F("[OcppEngine] Deserialization failed: "));
Serial.println(err.c_str());
break;
}
if (doc) {
delete doc;
}
return deserializationSuccess;
}
/**
* Send back error code
*/
boolean processWebSocketUnsupportedEvent(const char* payload, size_t length){
bool deserializationSuccess = false;
DynamicJsonDocument *doc = new DynamicJsonDocument(200);
char onlyRpcHeader[200];
size_t onlyRpcHeader_len = removePayload(payload, length, onlyRpcHeader, 200);
DeserializationError err = deserializeJson(*doc, onlyRpcHeader, onlyRpcHeader_len);
if (err.code() == DeserializationError::Ok) {
int messageTypeId = (*doc)[0];
if (messageTypeId == MESSAGE_TYPE_CALL) {
deserializationSuccess = true;
OcppOperation *op = makeOcppOperation(wsocket, new WebSocketError("This controller does not support WebSocket fragments"));
handleReqMessage(doc, op);
}
} else {
Serial.print(F("[OcppEngine] Deserialization of unsupported WebSocket event failed: "));
Serial.println(err.c_str());
}
if (doc) delete doc;
return deserializationSuccess;
}
void handleReqMessage(JsonDocument *json){
OcppOperation *req = makeFromJson(wsocket, json);
if (req == NULL) {
Serial.print(F("[OcppEngine] Couldn't make OppOperation from Request. Ignore request.\n"));
return;
}
handleReqMessage(json, req);
}
void handleReqMessage(JsonDocument *json, OcppOperation *req) {
if (req == NULL) {
Serial.print(F("[OcppEngine] handleReqMessage: invalid argument\n"));
return;
}
receivedOcppOperations.push_back(req);; //enqueue so loop() plans conf sending
req->receiveReq(json); //"fire" the operation
}
/**
* call conf() on each element of the queue. Start with first element. On successful message delivery,
* delete the element from the list. Try all the pending OCPP Operations until the right one is found.
*
* This function could result in improper behavior in Charging Stations, because messages are not
* guaranteed to be received and therefore processed in the right order.
*/
void handleConfMessage(JsonDocument *json){
#if 0
for (int i = 0; i < initiatedOcppOperations.size(); i++){
OcppOperation *el = initiatedOcppOperations.at(i);
boolean success = el->receiveConf(json); //maybe rename to "consumed"?
if (success){
initiatedOcppOperations.(i);
//TODO Review: are all recources freed here?
delete el;
return;
}
}
#endif
for (auto operation = initiatedOcppOperations.begin(); operation != initiatedOcppOperations.end(); ++operation) {
boolean success = (*operation)->receiveConf(json); //maybe rename to "consumed"?
if (success) {
delete *operation;
initiatedOcppOperations.erase(operation);
return;
}
}
//didn't find matching OcppOperation
Serial.print(F("[OcppEngine] Received CALLRESULT doesn't match any pending operation!\n"));
}
void handleErrMessage(JsonDocument *json){
#if 0
for (int i = 0; i < initiatedOcppOperations.size(); i++){
OcppOperation *el = initiatedOcppOperations.get(i);
boolean discardOperation = el->receiveError(json); //maybe rename to "consumed"?
if (discardOperation){
initiatedOcppOperations.remove(i);
//TODO Review: are all recources freed here?
delete el;
return;
}
}
#endif
for (auto operation = initiatedOcppOperations.begin(); operation != initiatedOcppOperations.end(); ++operation){
boolean discardOperation = (*operation)->receiveError(json); //maybe rename to "consumed"?
if (discardOperation){
//TODO Review: are all recources freed here?
delete *operation;
initiatedOcppOperations.erase(operation);
return;
}
}
//No OcppOperation was aborted because of the error message
if (DEBUG_OUT) Serial.print(F("[OcppEngine] Received CALLERROR did not abort a pending operation\n"));
}
void initiateOcppOperation(OcppOperation *o){
if (!o->isFullyConfigured()){
Serial.printf("[OcppEngine] initiateOcppOperation(op) was called without the operation being configured and ready to send. Discard operation!\n");
delete o;
return;
}
initiatedOcppOperations.push_back(o);
}
void ocppEngine_loop(){
/**
* Work through the initiatedOcppOperations queue. Start with the first element by calling req() on it. If
* the operation is (still) pending, it will return false and therefore each other queued element must
* wait until this operation is over. If an ocppOperation is finished, it returns true on a req() call,
* can be dequeued and the following ocppOperation is processed.
*/
#if 0
while (initiatedOcppOperations.size() > 0){
OcppOperation *el = initiatedOcppOperations.get(0);
boolean timeout = el->sendReq(); //The only reason to dequeue elements here is when a timeout occurs. Normally
if (timeout){ //the Conf msg processing routine dequeues finished elements
initiatedOcppOperations.remove(0);
//TODO Review: are all recources freed here?
delete el;
//go on with the next element in the queue, which is now at initiatedOcppOperations[0]
} else {
//there is one operation pending right now, so quit this while-loop.
break;
}
}
#endif
auto operation = initiatedOcppOperations.begin();
while (operation != initiatedOcppOperations.end()){
boolean timeout = (*operation)->sendReq(); //The only reason to dequeue elements here is when a timeout occurs. Normally
if (timeout){ //the Conf msg processing routine dequeues finished elements
delete *operation;
operation = initiatedOcppOperations.erase(operation);
//TODO Review: are all recources freed here?
//go on with the next element in the queue, which is now at initiatedOcppOperations[0]
} else {
//there is one operation pending right now, so quit this while-loop.
break;
}
}
/*
* Activate timeout detection on the msgs other than the first in the queue.
*/
#if 0
for (int i = 1; i < initiatedOcppOperations.size(); i++) {
Timeout *timer = initiatedOcppOperations.get(i)->getTimeout();
if (timer)
timer->tick(false); //false: did not send a frame prior to calling tick
if (timer->isExceeded()) {
Serial.print(F("[OcppEngine] Discarding operation due to timeout: "));
initiatedOcppOperations.get(i)->print_debug();
initiatedOcppOperations.remove(i);
delete initiatedOcppOperations.get(i);
}
}
#endif
operation = initiatedOcppOperations.begin();
while (operation != initiatedOcppOperations.end()) {
if (operation == initiatedOcppOperations.begin()){ //jump over the first element
++operation;
continue;
}
Timeout *timer = (*operation)->getTimeout();
if (!timer) {
++operation; //no timeouts, nothing to do in this iteration
continue;
}
timer->tick(false); //false: did not send a frame prior to calling tick
if (timer->isExceeded()) {
Serial.print(F("[OcppEngine] Discarding operation due to timeout: "));
(*operation)->print_debug();
delete *operation;
operation = initiatedOcppOperations.erase(operation);
} else {
++operation;
}
}
/**
* Work through the receivedOcppOperations queue. Start with the first element by calling conf() on it.
* If an ocppOperation is finished, it returns true on a conf() call, and is dequeued.
*/
#if 0
int i = 0;
while (i < receivedOcppOperations.size()){
OcppOperation *el = receivedOcppOperations.get(i);
boolean success = el->sendConf();
if (success){
receivedOcppOperations.remove(i);
//TODO Review: are all recources freed here?
delete el;
//go on with the next element in the queue, which is now at receivedOcppOperations[i]
} else {
//There will be another attempt to send this conf message in a future loop call.
//Go on with the next element in the queue, which is now at receivedOcppOperations[i+1]
i++;
}
}
#endif
operation = receivedOcppOperations.begin();
while (operation != receivedOcppOperations.end()){
boolean success = (*operation)->sendConf();
if (success){
delete *operation;
operation = receivedOcppOperations.erase(operation);
//TODO Review: are all recources freed here?
//go on with the next element in the queue, which is now at receivedOcppOperations[i]
} else {
//There will be another attempt to send this conf message in a future loop call.
//Go on with the next element in the queue, which is now at receivedOcppOperations[i+1]
++operation; //TODO review: this makes conf's out-of-order. But if the first Op fails because of lacking RAM, this could save the device.
}
}
}
#endif
void setSmartChargingService(SmartChargingService *scs) {
ocppEngine_smartChargingService = scs;
}
SmartChargingService* getSmartChargingService(){
if (ocppEngine_smartChargingService == NULL) {
Serial.print(F("[OcppEngine] Error: in OcppEngine, there is no smartChargingService set, but it is accessed!\n"));
//no error catch
}
return ocppEngine_smartChargingService;
}
void setChargePointStatusService(ChargePointStatusService *cpss){
ocppEngine_chargePointStatusService = cpss;
}
ChargePointStatusService *getChargePointStatusService(){
if (ocppEngine_chargePointStatusService == NULL) {
Serial.print(F("[OcppEngine] Error: in OcppEngine, there is no chargePointStatusService set, but it is accessed!\n"));
//no error catch
}
return ocppEngine_chargePointStatusService;
}
ConnectorStatus *getConnectorStatus(int connectorId) {
if (getChargePointStatusService() == NULL) return NULL;
ConnectorStatus *result = getChargePointStatusService()->getConnector(connectorId);
if (result == NULL) {
Serial.print(F("[OcppEngine] Error in getConnectorStatus(): cannot fetch connector with given connectorId!\n"));
//no error catch
}
return result;
}
void setMeteringSerivce(MeteringService *meteringService) {
ocppEngine_meteringService = meteringService;
}
MeteringService* getMeteringService() {
if (ocppEngine_meteringService == NULL) {
Serial.print(F("[OcppEngine] Error: in OcppEngine, there is no ocppEngine_meteringService set, but it is accessed!\n"));
//no error catch
}
return ocppEngine_meteringService;
}
void ocppEngine_setOcppTime(OcppTime *ocppTime) {
ocppEngine_ocppTime = ocppTime;
}
OcppTime *getOcppTime() {
if (ocppEngine_ocppTime == NULL) {
Serial.print(F("[OcppEngine] Error: in OcppEngine, there is no ocppEngine_ocppTime set, but it is accessed!\n"));
//no error catch
}
return ocppEngine_ocppTime;
}
#if 0 //moved to OcppConnection
/*
* Tries to recover the Ocpp-Operation header from a broken message.
*
* Example input:
* [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {"key":"now the msg breaks...
*
* The Json library returns an error code when trying to deserialize that broken message. This
* function searches for the first occurence of the character '{' and writes "}]" after it.
*
* Example output:
* [2, "75705e50-682d-404e-b400-1bca33d41e19", "ChangeConfiguration", {}]
*
*/
size_t removePayload(const char *src, size_t src_size, char *dst, size_t dst_size) {
size_t res_len = 0;
for (size_t i = 0; i < src_size && i < dst_size-3; i++) {
if (src[i] == '\0'){
//no payload found within specified range. Cancel execution
break;
}
dst[i] = src[i];
if (src[i] == '{') {
dst[i+1] = '}';
dst[i+2] = ']';
res_len = i+3;
break;
}
}
dst[res_len] = '\0';
res_len++;
return res_len;
}
#endif
} //end namespace ArduinoOcpp