1 /*
2 * Copyright (c) 2025 Linumiz GmbH
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "ocpp_i.h"
8
9 #define OCPP_PDU_TIMEOUT 2
10 #define OCPP_USER_REQ_PDU_BUF 350
11 #define FILL_METER_TABLE(_mes, _smes, _unit)[_mes] = {.mes = _mes, \
12 .smes = _smes, .unit = _unit}
13
14 static struct {
15 enum ocpp_meter_measurand mes;
16 const char * const smes;
17 const char * const unit;
18 } mtr_ref_table[] = {
19 FILL_METER_TABLE(OCPP_OMM_CURRENT_FROM_EV,
20 "Current.Export", "A"),
21 FILL_METER_TABLE(OCPP_OMM_CURRENT_TO_EV,
22 "Current.Import", "A"),
23 FILL_METER_TABLE(OCPP_OMM_CURRENT_MAX_OFFERED_TO_EV,
24 "Current.OfferedMaximum", "A"),
25 FILL_METER_TABLE(OCPP_OMM_ACTIVE_ENERGY_FROM_EV,
26 "Energy.Active.Export.Register", "Wh"),
27 FILL_METER_TABLE(OCPP_OMM_ACTIVE_ENERGY_TO_EV,
28 "Energy.Active.Import.Register", "Wh"),
29 FILL_METER_TABLE(OCPP_OMM_REACTIVE_ENERGY_FROM_EV,
30 "Energy.Reactive.Export.Register", "varh"),
31 FILL_METER_TABLE(OCPP_OMM_REACTIVE_ENERGY_TO_EV,
32 "Energy.Reactive.Import.Register", "varh"),
33 FILL_METER_TABLE(OCPP_OMM_ACTIVE_POWER_FROM_EV,
34 "Power.Active.Export", "W"),
35 FILL_METER_TABLE(OCPP_OMM_ACTIVE_POWER_TO_EV,
36 "Power.Active.Import", "W"),
37 FILL_METER_TABLE(OCPP_OMM_REACTIVE_POWER_FROM_EV,
38 "Power.Reactive.Export", "var"),
39 FILL_METER_TABLE(OCPP_OMM_REACTIVE_POWER_TO_EV,
40 "Power.Reactive.Import", "var"),
41 FILL_METER_TABLE(OCPP_OMM_POWERLINE_FREQ,
42 "Frequency", NULL),
43 FILL_METER_TABLE(OCPP_OMM_POWER_FACTOR,
44 "Power.Factor", NULL),
45 FILL_METER_TABLE(OCPP_OMM_POWER_MAX_OFFERED_TO_EV,
46 "Power.Offered", NULL),
47 FILL_METER_TABLE(OCPP_OMM_FAN_SPEED,
48 "RPM", "rpm"),
49 FILL_METER_TABLE(OCPP_OMM_CHARGING_PERCENT,
50 "SoCState", "Percent"),
51 FILL_METER_TABLE(OCPP_OMM_TEMPERATURE,
52 "Temperature", "Celsius"),
53 FILL_METER_TABLE(OCPP_OMM_VOLTAGE_AC_RMS,
54 "Voltage", "V")
55 };
56
ocpp_boot_notification(ocpp_session_handle_t hndl,struct ocpp_cp_info * cpi)57 int ocpp_boot_notification(ocpp_session_handle_t hndl,
58 struct ocpp_cp_info *cpi)
59 {
60 int ret;
61 struct ocpp_session *sh = (struct ocpp_session *)hndl;
62 struct ocpp_info *ctx = sh->ctx;
63 struct ocpp_upstream_info *ui = &ctx->ui;
64 char *buf = ctx->pdu_buf;
65 ocpp_msg_fp_t fn;
66 struct ocpp_wamp_rpc_msg rmsg = {0};
67
68 fn = ctx->cfn[PDU_BOOTNOTIFICATION];
69 sh->uid = fn(buf, sizeof(ctx->pdu_buf), sh, cpi);
70
71 rmsg.ctx = ctx;
72 rmsg.msg = buf;
73 rmsg.msg_len = strlen(buf);
74 rmsg.sndlock = &ui->ws_sndlock;
75 rmsg.rspsig = &ui->ws_rspsig;
76
77 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
78 if (ret == 0 && sh->resp_status != BOOT_ACCEPTED) {
79 ret = -EAGAIN;
80 }
81
82 return ret;
83 }
84
ocpp_get_configuration(enum ocpp_key key,struct ocpp_info * ctx,char * uid)85 int ocpp_get_configuration(enum ocpp_key key, struct ocpp_info *ctx, char *uid)
86 {
87 int ret;
88 char tmp[32];
89 char *sval = NULL;
90 char *buf = ctx->pdu_buf;
91 struct ocpp_wamp_rpc_msg rmsg = {0};
92 union ocpp_keyval *kval;
93 ocpp_msg_fp_t fn;
94 bool is_rw;
95 enum ocpp_key_type ktype;
96
97 if (key >= OCPP_CFG_END) {
98 return -EINVAL;
99 }
100
101 ktype = ocpp_get_keyval_type(key);
102 is_rw = ocpp_is_key_rw(key);
103 kval = ocpp_get_key_val(key);
104
105 if (ktype < KEY_TYPE_STR) {
106 sval = tmp;
107 snprintk(tmp, sizeof(tmp), "%d", kval->ival);
108 } else {
109 sval = kval->str;
110 }
111
112 fn = ctx->cfn[PDU_GET_CONFIGURATION];
113 fn(buf, sizeof(ctx->pdu_buf), ocpp_get_key_literal(key), sval, is_rw, uid);
114
115 rmsg.ctx = ctx;
116 rmsg.msg = buf;
117 rmsg.msg_len = strlen(buf);
118
119 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
120 return ret;
121 }
122
ocpp_change_configuration(char * skey,struct ocpp_info * ctx,char * sval,char * uid)123 int ocpp_change_configuration(char *skey, struct ocpp_info *ctx,
124 char *sval, char *uid)
125 {
126 int ret = -EINVAL;
127 char *buf = ctx->pdu_buf;
128 struct ocpp_wamp_rpc_msg rmsg = {0};
129 union ocpp_keyval kval;
130 ocpp_msg_fp_t fn;
131 enum ocpp_key key;
132 const char *res = "Accepted";
133
134 key = ocpp_key_to_cfg(skey);
135 if (key < OCPP_CFG_END) {
136 enum ocpp_key_type ktype;
137
138 ktype = ocpp_get_keyval_type(key);
139
140 if (ktype < KEY_TYPE_STR) {
141 kval.ival = atoi(sval);
142 } else {
143 kval.str = sval;
144 }
145
146 ret = ocpp_update_cfg_val(key, &kval);
147 if (ret < 0) {
148 res = "Rejected";
149 }
150 } else {
151 res = "NotSupported";
152 }
153
154 if (ret == 0) {
155 switch (key) {
156 case CFG_MTR_VAL_SAMPLE_INTERVAL:
157 if (atomic_get(&ctx->mtr_timer_ref_cnt) <= 0) {
158 break;
159 }
160
161 k_timer_start(&ctx->mtr_timer,
162 K_SECONDS(kval.ival),
163 K_SECONDS(kval.ival));
164 break;
165
166 default:
167 break;
168 }
169 }
170
171 fn = ctx->cfn[PDU_CHANGE_CONFIGURATION];
172 fn(buf, sizeof(ctx->pdu_buf), res, uid);
173
174 rmsg.ctx = ctx;
175 rmsg.msg = buf;
176 rmsg.msg_len = strlen(buf);
177
178 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
179
180 return ret;
181 }
182
ocpp_authorize(ocpp_session_handle_t hndl,char * idtag,enum ocpp_auth_status * status,uint32_t timeout_ms)183 int ocpp_authorize(ocpp_session_handle_t hndl, char *idtag,
184 enum ocpp_auth_status *status,
185 uint32_t timeout_ms)
186 {
187 int ret;
188 struct ocpp_session *sh = (struct ocpp_session *)hndl;
189 struct ocpp_info *ctx;
190 struct ocpp_upstream_info *ui;
191 char buf[OCPP_USER_REQ_PDU_BUF];
192 union ocpp_keyval *val;
193 struct ocpp_wamp_rpc_msg rmsg = {0};
194 ocpp_msg_fp_t fn;
195
196 if (idtag == NULL || status == NULL || !ocpp_session_is_valid(sh)) {
197 return -EINVAL;
198 }
199
200 ctx = sh->ctx;
201 if (ctx->state < CP_STATE_READY) {
202 return -EAGAIN;
203 }
204
205 if (ctx->is_cs_offline) {
206 val = ocpp_get_key_val(CFG_LOCAL_AUTH_OFFLINE);
207 if (val != NULL && val->ival == 0) {
208 return -EAGAIN;
209 }
210 }
211
212 strncpy(sh->idtag, idtag, sizeof(sh->idtag));
213 ui = &ctx->ui;
214 fn = ctx->cfn[PDU_AUTHORIZE];
215 sh->uid = fn(buf, sizeof(buf), sh);
216
217 rmsg.ctx = ctx;
218 rmsg.msg = buf;
219 rmsg.msg_len = strlen(buf);
220 rmsg.sndlock = &ui->ws_sndlock;
221 rmsg.rspsig = &ui->ws_rspsig;
222 ret = ocpp_send_to_server(&rmsg, K_MSEC(timeout_ms));
223 if (ret < 0) {
224 return ret;
225 }
226
227 *status = sh->resp_status;
228 if (sh->resp_status == OCPP_AUTH_ACCEPTED) {
229 sh->is_active = true;
230 }
231
232 return 0;
233 }
234
ocpp_heartbeat(ocpp_session_handle_t hndl)235 int ocpp_heartbeat(ocpp_session_handle_t hndl)
236 {
237 int ret;
238 struct ocpp_session *sh = (struct ocpp_session *)hndl;
239 struct ocpp_info *ctx = sh->ctx;
240 struct ocpp_upstream_info *ui = &ctx->ui;
241 ocpp_msg_fp_t fn;
242 struct ocpp_wamp_rpc_msg rmsg = {0};
243 char *buf = ctx->pdu_buf;
244
245 fn = ctx->cfn[PDU_HEARTBEAT];
246 sh->uid = fn(buf, sizeof(ctx->pdu_buf), sh);
247
248 rmsg.ctx = ctx;
249 rmsg.msg = buf;
250 rmsg.msg_len = strlen(buf);
251 rmsg.sndlock = &ui->ws_sndlock;
252 rmsg.rspsig = &ui->ws_rspsig;
253
254 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
255 if (ret == 0 && sh->resp_status != BOOT_ACCEPTED) {
256 ret = -EAGAIN;
257 }
258
259 return ret;
260 }
261
ocpp_start_transaction(ocpp_session_handle_t hndl,int meter_val,uint8_t conn_id,uint32_t timeout_ms)262 int ocpp_start_transaction(ocpp_session_handle_t hndl,
263 int meter_val,
264 uint8_t conn_id,
265 uint32_t timeout_ms)
266 {
267 struct ocpp_session *sh = (struct ocpp_session *)hndl;
268 struct ocpp_info *ctx;
269 struct ocpp_upstream_info *ui;
270 char buf[OCPP_USER_REQ_PDU_BUF];
271 struct ocpp_wamp_rpc_msg rmsg = {0};
272 ocpp_msg_fp_t fn;
273 char utc[CISTR25] = {0};
274 atomic_val_t ref;
275 int ret;
276
277 if (!ocpp_session_is_valid(sh) ||
278 conn_id <= 0) {
279 return -EINVAL;
280 }
281 ctx = sh->ctx;
282 sh->idcon = conn_id;
283 if (ctx->state < CP_STATE_READY) {
284 return -EAGAIN;
285 }
286
287 if (ctx->is_cs_offline) {
288 /* todo: fill meter reading
289 * server offline, accept start txn and save it in Q.
290 */
291 return 0;
292 }
293
294 fn = ctx->cfn[PDU_START_TRANSACTION];
295 ocpp_get_utc_now(utc);
296 sh->uid = fn(buf, sizeof(buf), sh, meter_val, -1, utc);
297
298 ui = &ctx->ui;
299 rmsg.ctx = ctx;
300 rmsg.msg = buf;
301 rmsg.msg_len = strlen(buf);
302 rmsg.sndlock = &ui->ws_sndlock;
303 rmsg.rspsig = &ui->ws_rspsig;
304 ret = ocpp_send_to_server(&rmsg, K_MSEC(timeout_ms));
305 if (ret < 0) {
306 return ret;
307 }
308
309 if (sh->resp_status != OCPP_AUTH_ACCEPTED) {
310 sh->is_active = false;
311 ret = -EACCES;
312 } else {
313 union ocpp_keyval *keyval;
314
315 keyval = ocpp_get_key_val(CFG_MTR_VAL_SAMPLE_INTERVAL);
316 do {
317 ref = atomic_get(&ctx->mtr_timer_ref_cnt);
318 if (ref == 0) {
319 k_timer_start(&ctx->mtr_timer,
320 K_SECONDS(keyval->ival),
321 K_SECONDS(keyval->ival));
322 }
323 } while (!atomic_cas(&ctx->mtr_timer_ref_cnt, ref, ref + 1));
324 }
325
326 return ret;
327 }
328
ocpp_stop_transaction(ocpp_session_handle_t hndl,int meter_val,uint32_t timeout_ms)329 int ocpp_stop_transaction(ocpp_session_handle_t hndl,
330 int meter_val,
331 uint32_t timeout_ms)
332 {
333 struct ocpp_session *sh = (struct ocpp_session *)hndl;
334 struct ocpp_info *ctx;
335 struct ocpp_upstream_info *ui;
336 char buf[OCPP_USER_REQ_PDU_BUF];
337 char utc[CISTR25] = {0};
338 struct ocpp_wamp_rpc_msg rmsg = {0};
339 ocpp_msg_fp_t fn;
340 atomic_val_t ref;
341 int ret;
342
343 if (!ocpp_session_is_valid(sh)) {
344 return -EINVAL;
345 }
346
347 ctx = sh->ctx;
348 if (ctx->state < CP_STATE_READY) {
349 return -EAGAIN;
350 }
351
352 sh->is_active = false;
353 do {
354 ref = atomic_get(&ctx->mtr_timer_ref_cnt);
355 if (ref == 0) {
356 k_timer_stop(&ctx->mtr_timer);
357 break;
358 }
359 } while (!atomic_cas(&ctx->mtr_timer_ref_cnt, ref, ref - 1));
360
361 if (ctx->is_cs_offline) {
362 /* todo: fill meter reading
363 * server offline, accept start txn and save it in Q.
364 */
365 return 0;
366 }
367
368 fn = ctx->cfn[PDU_STOP_TRANSACTION];
369 ocpp_get_utc_now(utc);
370 sh->uid = fn(buf, sizeof(buf), sh, meter_val, NULL, utc);
371
372 ui = &ctx->ui;
373 rmsg.ctx = ctx;
374 rmsg.msg = buf;
375 rmsg.msg_len = strlen(buf);
376 rmsg.sndlock = &ui->ws_sndlock;
377 rmsg.rspsig = &ui->ws_rspsig;
378 ret = ocpp_send_to_server(&rmsg, K_MSEC(timeout_ms));
379
380 return ret;
381 }
382
ocpp_remote_start_transaction(struct ocpp_info * ctx,struct internal_msg * msg,char * uid)383 int ocpp_remote_start_transaction(struct ocpp_info *ctx,
384 struct internal_msg *msg,
385 char *uid)
386 {
387 char *buf = ctx->pdu_buf;
388 const char *resp = "Rejected";
389 struct ocpp_wamp_rpc_msg rmsg = {0};
390 ocpp_msg_fp_t fn;
391 int ret;
392
393 ret = k_msgq_put(ctx->msgq, msg, K_MSEC(100));
394 if (ret == 0) {
395 resp = "Accepted";
396 }
397
398 fn = ctx->cfn[PDU_REMOTE_START_TRANSACTION];
399 fn(buf, sizeof(ctx->pdu_buf), resp, uid);
400
401 rmsg.ctx = ctx;
402 rmsg.msg = buf;
403 rmsg.msg_len = strlen(buf);
404 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
405
406 return ret;
407 }
408
ocpp_remote_stop_transaction(struct ocpp_info * ctx,struct internal_msg * msg,int idtxn,char * uid)409 int ocpp_remote_stop_transaction(struct ocpp_info *ctx,
410 struct internal_msg *msg,
411 int idtxn, char *uid)
412 {
413 char *buf = ctx->pdu_buf;
414 const char *resp = "Rejected";
415 struct ocpp_wamp_rpc_msg rmsg = {0};
416 ocpp_msg_fp_t fn;
417 sys_snode_t *node;
418 struct ocpp_session *sh;
419 bool is_found = false;
420 int ret;
421
422 k_mutex_lock(&ctx->ilock, K_FOREVER);
423 SYS_SLIST_FOR_EACH_NODE(&ctx->slist, node) {
424 sh = to_session(node);
425 if (sh->is_active &&
426 sh->idtxn == idtxn) {
427 is_found = true;
428 break;
429 }
430 }
431 k_mutex_unlock(&ctx->ilock);
432
433 if (is_found) {
434 msg->usr.stop_charge.id_con = sh->idcon;
435 ret = k_msgq_put(ctx->msgq, msg, K_MSEC(100));
436 if (ret == 0) {
437 resp = "Accepted";
438 }
439 }
440
441 fn = ctx->cfn[PDU_REMOTE_STOP_TRANSACTION];
442 fn(buf, sizeof(ctx->pdu_buf), resp, uid);
443
444 rmsg.ctx = ctx;
445 rmsg.msg = buf;
446 rmsg.msg_len = strlen(buf);
447 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
448
449 return ret;
450 }
451
ocpp_unlock_connector(struct ocpp_info * ctx,struct internal_msg * msg,char * uid)452 int ocpp_unlock_connector(struct ocpp_info *ctx,
453 struct internal_msg *msg,
454 char *uid)
455 {
456 char *buf = ctx->pdu_buf;
457 char *resp = "UnlockFailed";
458 struct ocpp_wamp_rpc_msg rmsg = {0};
459 ocpp_msg_fp_t fn;
460 int ret;
461
462 ret = k_msgq_put(ctx->msgq, msg, K_MSEC(100));
463 if (ret == 0) {
464 resp = "Unlocked";
465 }
466
467 fn = ctx->cfn[PDU_UNLOCK_CONNECTOR];
468 fn(buf, sizeof(ctx->pdu_buf), resp, uid);
469
470 rmsg.ctx = ctx;
471 rmsg.msg = buf;
472 rmsg.msg_len = strlen(buf);
473 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
474
475 return ret;
476 }
477
ocpp_meter_values(ocpp_session_handle_t hndl,enum ocpp_meter_measurand mes,char * sval)478 int ocpp_meter_values(ocpp_session_handle_t hndl,
479 enum ocpp_meter_measurand mes,
480 char *sval)
481 {
482 struct ocpp_session *sh = (struct ocpp_session *)hndl;
483 struct ocpp_info *ctx = sh->ctx;
484 struct ocpp_upstream_info *ui = &ctx->ui;
485 char *buf = ctx->pdu_buf;
486 char utc[CISTR25] = {0};
487 struct ocpp_wamp_rpc_msg rmsg = {0};
488 ocpp_msg_fp_t fn;
489 int ret;
490
491 if (ctx->is_cs_offline) {
492 return -EAGAIN;
493 }
494
495 fn = ctx->cfn[PDU_METER_VALUES];
496 ocpp_get_utc_now(utc);
497 sh->uid = fn(buf, sizeof(ctx->pdu_buf), sh, utc, sval,
498 mtr_ref_table[mes].smes,
499 mtr_ref_table[mes].unit);
500
501 rmsg.ctx = ctx;
502 rmsg.msg = buf;
503 rmsg.msg_len = strlen(buf);
504 rmsg.sndlock = &ui->ws_sndlock;
505 rmsg.rspsig = &ui->ws_rspsig;
506 ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
507
508 return ret;
509 }
510