Skip to content

Commit a27cc2f

Browse files
committed
add HTTP API extension
1 parent e4575fc commit a27cc2f

File tree

5 files changed

+198
-26
lines changed

5 files changed

+198
-26
lines changed

src/api.cpp

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
// GPL-3.0 License
44

55
#include "api.h"
6+
#include "mongoose.h"
67

78
#include <MicroOcpp/Debug.h>
9+
#include <MicroOcpp/Model/ConnectorBase/EvseId.h>
10+
#include <MicroOcpp/Model/Authorization/IdToken.h>
811

912
#include "evse.h"
1013

1114
//simple matching function; takes * as a wildcard
1215
bool str_match(const char *query, const char *pattern) {
1316
size_t qi = 0, pi = 0;
1417

15-
while (query[qi] && pattern[pi]) {
16-
if (query[qi] == pattern[pi]) {
18+
while (pattern[pi]) {
19+
if (query[qi] && query[qi] == pattern[pi]) {
1720
qi++;
1821
pi++;
1922
} else if (pattern[pi] == '*') {
@@ -155,3 +158,138 @@ int mocpp_api_call(const char *endpoint, MicroOcpp::Method method, const char *b
155158

156159
return status;
157160
}
161+
162+
int mocpp_api2_call(const char *uri_raw, size_t uri_raw_len, MicroOcpp::Method method, const char *query_raw, size_t query_raw_len, char *resp_body, size_t resp_body_size) {
163+
164+
snprintf(resp_body, resp_body_size, "%s", "");
165+
166+
struct mg_str uri = mg_str_n(uri_raw, uri_raw_len);
167+
struct mg_str query = mg_str_n(query_raw, query_raw_len);
168+
169+
int evse_id = -1;
170+
int connector_id = -1;
171+
172+
unsigned int num;
173+
struct mg_str evse_id_str = mg_http_var(query, mg_str("evse_id"));
174+
if (evse_id_str.buf) {
175+
if (!mg_str_to_num(evse_id_str, 10, &num, sizeof(num)) || num < 1 || num >= MO_NUM_EVSEID) {
176+
snprintf(resp_body, resp_body_size, "invalid connector_id");
177+
return 400;
178+
}
179+
evse_id = (int)num;
180+
}
181+
182+
struct mg_str connector_id_str = mg_http_var(query, mg_str("connector_id"));
183+
if (connector_id_str.buf) {
184+
if (!mg_str_to_num(connector_id_str, 10, &num, sizeof(num)) || num != 1) {
185+
snprintf(resp_body, resp_body_size, "invalid connector_id");
186+
return 400;
187+
}
188+
connector_id = (int)num;
189+
}
190+
191+
if (mg_match(uri, mg_str("/plugin"), NULL)) {
192+
if (evse_id < 0) {
193+
snprintf(resp_body, resp_body_size, "no action taken");
194+
return 200;
195+
} else {
196+
snprintf(resp_body, resp_body_size, "%s", connectors[evse_id-1].getEvPlugged() ? "EV already plugged" : "plugged in EV");
197+
connectors[evse_id-1].setEvPlugged(true);
198+
connectors[evse_id-1].setEvReady(true);
199+
connectors[evse_id-1].setEvseReady(true);
200+
return 200;
201+
}
202+
} else if (mg_match(uri, mg_str("/plugout"), NULL)) {
203+
if (evse_id < 0) {
204+
snprintf(resp_body, resp_body_size, "no action taken");
205+
return 200;
206+
} else {
207+
snprintf(resp_body, resp_body_size, "%s", connectors[evse_id-1].getEvPlugged() ? "EV already unplugged" : "unplug EV");
208+
connectors[evse_id-1].setEvPlugged(false);
209+
connectors[evse_id-1].setEvReady(false);
210+
connectors[evse_id-1].setEvseReady(false);
211+
return 200;
212+
}
213+
} else if (mg_match(uri, mg_str("/end"), NULL)) {
214+
bool trackEvReady = false;
215+
for (size_t i = 0; i < connectors.size(); i++) {
216+
trackEvReady |= connectors[i].getEvReady();
217+
connectors[i].setEvReady(false);
218+
}
219+
snprintf(resp_body, resp_body_size, "%s", trackEvReady ? "suspended EV" : "EV already suspended");
220+
return 200;
221+
} else if (mg_match(uri, mg_str("/state"), NULL)) {
222+
struct mg_str ready_str = mg_http_var(query, mg_str("ready"));
223+
bool ready = true;
224+
if (ready_str.buf) {
225+
if (mg_match(ready_str, mg_str("true"), NULL)) {
226+
ready = true;
227+
} else if (mg_match(ready_str, mg_str("false"), NULL)) {
228+
ready = false;
229+
} else {
230+
snprintf(resp_body, resp_body_size, "invalid ready");
231+
return 400;
232+
}
233+
}
234+
bool trackEvReady = false;
235+
for (size_t i = 0; i < connectors.size(); i++) {
236+
if (connectors[i].getEvPlugged()) {
237+
bool trackEvReady = connectors[i].getEvReady();
238+
connectors[i].setEvReady(ready);
239+
snprintf(resp_body, resp_body_size, "%s, %s", ready ? "EV suspended" : "EV not suspended", trackEvReady ? "suspended before" : "not suspended before");
240+
return 200;
241+
}
242+
}
243+
snprintf(resp_body, resp_body_size, "no action taken - EV not plugged");
244+
return 200;
245+
} else if (mg_match(uri, mg_str("/authorize"), NULL)) {
246+
struct mg_str id = mg_http_var(query, mg_str("id"));
247+
if (!id.buf) {
248+
snprintf(resp_body, resp_body_size, "missing id");
249+
return 400;
250+
}
251+
struct mg_str type = mg_http_var(query, mg_str("type"));
252+
if (!id.buf) {
253+
snprintf(resp_body, resp_body_size, "missing type");
254+
return 400;
255+
}
256+
257+
int ret;
258+
char id_buf [MO_IDTOKEN_LEN_MAX + 1];
259+
ret = snprintf(id_buf, sizeof(id_buf), "%.*s", (int)id.len, id.buf);
260+
if (ret < 0 || ret >= sizeof(id_buf)) {
261+
snprintf(resp_body, resp_body_size, "invalid id");
262+
return 400;
263+
}
264+
char type_buf [128];
265+
ret = snprintf(type_buf, sizeof(type_buf), "%.*s", (int)type.len, type.buf);
266+
if (ret < 0 || ret >= sizeof(type_buf)) {
267+
snprintf(resp_body, resp_body_size, "invalid type");
268+
return 400;
269+
}
270+
271+
if (evse_id <= 0) {
272+
snprintf(resp_body, resp_body_size, "invalid evse_id");
273+
return 400;
274+
}
275+
276+
bool trackAuthActive = connectors[evse_id-1].getSessionIdTag();
277+
278+
if (!connectors[evse_id-1].presentNfcTag(id_buf, type_buf)) {
279+
snprintf(resp_body, resp_body_size, "invalid id and / or type");
280+
return 400;
281+
}
282+
283+
bool authActive = connectors[evse_id-1].getSessionIdTag();
284+
285+
snprintf(resp_body, resp_body_size, "%s",
286+
!trackAuthActive && authActive ? "authorize in progress" :
287+
trackAuthActive && !authActive ? "unauthorize in progress" :
288+
trackAuthActive && authActive ? "no action taken (EVSE still authorized)" :
289+
"no action taken (EVSE not authorized)");
290+
291+
return 200;
292+
}
293+
294+
return 404;
295+
}

src/api.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ enum class Method {
1919

2020
int mocpp_api_call(const char *endpoint, MicroOcpp::Method method, const char *body, char *resp_body, size_t resp_body_size);
2121

22+
int mocpp_api2_call(const char *endpoint, size_t endpoint_len, MicroOcpp::Method method, const char *query, size_t query_len, char *resp_body, size_t resp_body_size);
23+
2224
#endif

src/evse.cpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <MicroOcpp/Model/Transactions/Transaction.h>
1010
#include <MicroOcpp/Model/Transactions/TransactionService.h>
1111
#include <MicroOcpp/Model/Variables/VariableService.h>
12+
#include <MicroOcpp/Model/Authorization/IdToken.h>
1213
#include <MicroOcpp/Operations/StatusNotification.h>
1314
#include <MicroOcpp/Version.h>
1415
#include <MicroOcpp/Debug.h>
@@ -146,41 +147,54 @@ void Evse::loop() {
146147

147148
}
148149

149-
void Evse::presentNfcTag(const char *uid_cstr) {
150-
if (!uid_cstr) {
150+
void Evse::presentNfcTag(const char *uid) {
151+
if (!uid) {
151152
MO_DBG_ERR("invalid argument");
152153
return;
153154
}
154-
std::string uid = uid_cstr;
155155

156156
#if MO_ENABLE_V201
157157
if (auto context = getOcppContext()) {
158158
if (context->getVersion().major == 2) {
159-
if (auto txService = context->getModel().getTransactionService()) {
160-
if (auto evse = txService->getEvse(connectorId)) {
161-
if (evse->getTransaction() && evse->getTransaction()->isAuthorizationActive) {
162-
evse->endAuthorization(MicroOcpp::IdToken(uid_cstr, MicroOcpp::IdToken::Type::KeyCode));
163-
} else {
164-
evse->beginAuthorization(MicroOcpp::IdToken(uid_cstr, MicroOcpp::IdToken::Type::KeyCode));
165-
}
166-
return;
167-
}
168-
}
159+
presentNfcTag(uid, "ISO14443");
160+
return;
169161
}
170162
}
171163
#endif
172164

173165
if (isTransactionActive(connectorId)) {
174-
if (!uid.compare(getTransactionIdTag(connectorId))) {
175-
endTransaction(uid.c_str(), "Local", connectorId);
166+
if (!strcmp(uid, getTransactionIdTag(connectorId))) {
167+
endTransaction(uid, "Local", connectorId);
176168
} else {
177169
MO_DBG_INFO("RFID card denied");
178170
}
179171
} else {
180-
beginTransaction(uid.c_str(), connectorId);
172+
beginTransaction(uid, connectorId);
181173
}
182174
}
183175

176+
#if MO_ENABLE_V201
177+
bool Evse::presentNfcTag(const char *uid, const char *type) {
178+
179+
MicroOcpp::IdToken idToken {nullptr, MicroOcpp::IdToken::Type::UNDEFINED, "Simulator"};
180+
if (!idToken.parseCstr(uid, type)) {
181+
return false;
182+
}
183+
184+
if (auto txService = getOcppContext()->getModel().getTransactionService()) {
185+
if (auto evse = txService->getEvse(connectorId)) {
186+
if (evse->getTransaction() && evse->getTransaction()->isAuthorizationActive) {
187+
evse->endAuthorization(idToken);
188+
} else {
189+
evse->beginAuthorization(idToken);
190+
}
191+
return true;
192+
}
193+
}
194+
return false;
195+
}
196+
#endif
197+
184198
void Evse::setEvPlugged(bool plugged) {
185199
if (!trackEvPluggedBool) return;
186200
trackEvPluggedBool->setBool(plugged);

src/evse.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <array>
99
#include <string>
1010
#include <MicroOcpp/Core/Configuration.h>
11+
#include <MicroOcpp/Version.h>
1112

1213
#define SIMULATOR_FN MO_FILENAME_PREFIX "simulator.jsn"
1314

@@ -41,6 +42,10 @@ class Evse {
4142

4243
void presentNfcTag(const char *uid);
4344

45+
#if MO_ENABLE_V201
46+
bool presentNfcTag(const char *uid, const char *type);
47+
#endif //MO_ENABLE_V201
48+
4449
void setEvPlugged(bool plugged);
4550

4651
bool getEvPlugged();

src/net_mongoose.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ bool api_check_basic_auth(const char *user, const char *pass) {
4242
void http_serve(struct mg_connection *c, int ev, void *ev_data) {
4343
if (ev == MG_EV_ACCEPT) {
4444
if (mg_url_is_ssl((const char*)c->fn_data)) { // TLS listener!
45+
MO_DBG_VERBOSE("API TLS setup");
4546
struct mg_tls_opts opts = {0};
4647
opts.cert = mg_str(api_cert);
4748
opts.key = mg_str(api_key);
@@ -55,7 +56,7 @@ void http_serve(struct mg_connection *c, int ev, void *ev_data) {
5556
char user[64], pass[64];
5657
mg_http_creds(message_data, user, sizeof(user), pass, sizeof(pass));
5758
if (!api_check_basic_auth(user, pass)) {
58-
mg_http_reply(c, 403, final_headers, "Not Authorised\n");
59+
mg_http_reply(c, 401, final_headers, "Unauthorized. Expect Basic Auth user and / or password\n");
5960
return;
6061
}
6162

@@ -127,12 +128,24 @@ void http_serve(struct mg_connection *c, int ev, void *ev_data) {
127128
*c = '\0';
128129
}
129130

130-
int status = mocpp_api_call(
131-
message_data->uri.buf + strlen("/api"),
132-
method,
133-
message_data->body.buf,
134-
resp_buf, RESP_BUF_SIZE);
135-
131+
int status = 404;
132+
if (status == 404) {
133+
status = mocpp_api2_call(
134+
message_data->uri.buf + strlen("/api"),
135+
message_data->uri.len - strlen("/api"),
136+
method,
137+
message_data->query.buf,
138+
message_data->query.len,
139+
resp_buf, RESP_BUF_SIZE);
140+
}
141+
if (status == 404) {
142+
status = mocpp_api_call(
143+
message_data->uri.buf + strlen("/api"),
144+
method,
145+
message_data->body.buf,
146+
resp_buf, RESP_BUF_SIZE);
147+
}
148+
136149
mg_http_reply(c, status, final_headers, resp_buf);
137150
} else if (mg_match(message_data->uri, mg_str("/"), NULL)) { //if no specific path is given serve dashboard application file
138151
struct mg_http_serve_opts opts;
@@ -141,7 +154,7 @@ void http_serve(struct mg_connection *c, int ev, void *ev_data) {
141154
opts.extra_headers = "Content-Type: text/html\r\nContent-Encoding: gzip\r\n";
142155
mg_http_serve_file(c, message_data, "public/bundle.html.gz", &opts);
143156
} else {
144-
mg_http_reply(c, 404, final_headers, "The required parameters are not given");
157+
mg_http_reply(c, 404, final_headers, "API endpoint not found");
145158
}
146159
}
147160
}

0 commit comments

Comments
 (0)