forked from matth-x/MicroOcpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOcppConnection.cpp
More file actions
288 lines (251 loc) · 10.6 KB
/
Copy pathOcppConnection.cpp
File metadata and controls
288 lines (251 loc) · 10.6 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
// matth-x/ArduinoOcpp
// Copyright Matthias Akstaller 2019 - 2021
// MIT License
#include <ArduinoOcpp/Core/OcppConnection.h>
#include <ArduinoOcpp/SimpleOcppOperationFactory.h>
#include <ArduinoOcpp/Core/OcppError.h>
#include <Variants.h>
#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);
using namespace ArduinoOcpp;
OcppConnection::OcppConnection(OcppSocket *ocppSock) : ocppSock(ocppSock) {
ReceiveTXTcallback callback = [this] (const char *payload, size_t length) {
return this->processOcppSocketInputTXT(payload, length);
};
ocppSock->setReceiveTXTcallback(callback);
}
void OcppConnection::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.
*/
auto operation = initiatedOcppOperations.begin();
while (operation != initiatedOcppOperations.end()){
boolean timeout = (*operation)->sendReq(ocppSock); //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.
*/
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.
*/
operation = receivedOcppOperations.begin();
while (operation != receivedOcppOperations.end()){
boolean success = (*operation)->sendConf(ocppSock);
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.
}
}
}
void OcppConnection::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);
o->setInitiated();
}
bool OcppConnection::processOcppSocketInputTXT(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(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;
}
/**
* 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 OcppConnection::handleConfMessage(JsonDocument *json) {
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 OcppConnection::handleReqMessage(JsonDocument *json) {
OcppOperation *req = makeFromJson(json);
if (req == NULL) {
Serial.print(F("[OcppEngine] Couldn't make OppOperation from Request. Ignore request.\n"));
return;
}
handleReqMessage(json, req);
}
void OcppConnection::handleReqMessage(JsonDocument *json, OcppOperation *op) {
if (op == NULL) {
Serial.print(F("[OcppEngine] handleReqMessage: invalid argument\n"));
return;
}
receivedOcppOperations.push_back(op);; //enqueue so loop() plans conf sending
op->receiveReq(json); //"fire" the operation
}
void OcppConnection::handleErrMessage(JsonDocument *json) {
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"));
}
/*
* 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;
}