1 /*
2 * Copyright (c) 2025 Linumiz GmbH
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #undef _POSIX_C_SOURCE
8 #define _POSIX_C_SOURCE 200809L /* for strdup() */
9 #include <stdio.h>
10 #include <time.h>
11
12 #include <zephyr/posix/sys/socket.h>
13 #include <zephyr/posix/arpa/inet.h>
14 #include <zephyr/posix/netinet/in.h>
15 #include <zephyr/posix/unistd.h>
16 #include <zephyr/posix/netdb.h>
17
18 #include <zephyr/net/net_if.h>
19 #include <zephyr/net/net_core.h>
20 #include <zephyr/net/net_context.h>
21 #include <zephyr/net/net_mgmt.h>
22 #include <zephyr/net/sntp.h>
23 #include <zephyr/net/ocpp.h>
24 #include <zephyr/random/random.h>
25 #include <zephyr/zbus/zbus.h>
26
27 #include "net_sample_common.h"
28
29 LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
30
31 #define NO_OF_CONN 2
32 K_KERNEL_STACK_ARRAY_DEFINE(cp_stk, NO_OF_CONN, 2 * 1024);
33
34 static struct k_thread tinfo[NO_OF_CONN];
35 static k_tid_t tid[NO_OF_CONN];
36 static char idtag[NO_OF_CONN][25];
37
dns_query(const char * host,uint16_t port,int family,int socktype,struct sockaddr * addr,socklen_t * addrlen)38 static int dns_query(const char *host, uint16_t port, int family, int socktype,
39 struct sockaddr *addr, socklen_t *addrlen)
40 {
41 struct addrinfo hints = {
42 .ai_family = family,
43 .ai_socktype = socktype,
44 };
45
46 struct addrinfo *res = NULL;
47 char addr_str[INET6_ADDRSTRLEN] = {0};
48 int ret;
49
50 /* Perform DNS query */
51 ret = getaddrinfo(host, NULL, &hints, &res);
52 if (ret < 0) {
53 LOG_ERR("getaddrinfo failed (%d, errno %d)", ret, errno);
54 return ret;
55 }
56
57 /* Store the first result */
58 *addr = *res->ai_addr;
59 *addrlen = res->ai_addrlen;
60 /* Free the allocated memory */
61 freeaddrinfo(res);
62 /* Store the port */
63 net_sin(addr)->sin_port = htons(port);
64 /* Print the found address */
65 inet_ntop(addr->sa_family, &net_sin(addr)->sin_addr, addr_str, sizeof(addr_str));
66 LOG_INF("%s -> %s", host, addr_str);
67
68 return 0;
69 }
70
ocpp_get_time_from_sntp(void)71 static int ocpp_get_time_from_sntp(void)
72 {
73 struct sntp_ctx ctx;
74 struct sntp_time stime;
75 struct sockaddr addr;
76 socklen_t addrlen;
77 struct timespec tv;
78 int ret;
79
80 ret = dns_query(CONFIG_NET_SAMPLE_SNTP_SERVER, CONFIG_NET_SAMPLE_SNTP_SERVER_PORT,
81 AF_INET, SOCK_DGRAM, &addr, &addrlen);
82 if (ret != 0) {
83 LOG_ERR("Failed to lookup SNTP server (%d)", ret);
84 return ret;
85 }
86
87 ret = sntp_init(&ctx, &addr, addrlen);
88 if (ret < 0) {
89 LOG_ERR("Failed to init SNTP IPv4 ctx: %d", ret);
90 return ret;
91 }
92
93 ret = sntp_query(&ctx, 4 * MSEC_PER_SEC, &stime);
94 if (ret < 0) {
95 LOG_ERR("SNTP IPv4 request failed: %d", ret);
96 return ret;
97 }
98
99 LOG_INF("SNTP success, epoch seconds: %llu\n", stime.seconds);
100 tv.tv_sec = stime.seconds;
101 clock_settime(CLOCK_REALTIME, &tv);
102 sntp_close(&ctx);
103 return 0;
104 }
105
106 ZBUS_CHAN_DEFINE(ch_event, /* Name */
107 union ocpp_io_value,
108 NULL, /* Validator */
109 NULL, /* User data */
110 ZBUS_OBSERVERS_EMPTY, /* observers */
111 ZBUS_MSG_INIT(0) /* Initial value {0} */
112 );
113
114 ZBUS_SUBSCRIBER_DEFINE(cp_thread0, 5);
115 ZBUS_SUBSCRIBER_DEFINE(cp_thread1, 5);
116 struct zbus_observer *obs[NO_OF_CONN] = {(struct zbus_observer *)&cp_thread0,
117 (struct zbus_observer *)&cp_thread1};
118
119 static void ocpp_cp_entry(void *p1, void *p2, void *p3);
user_notify_cb(enum ocpp_notify_reason reason,union ocpp_io_value * io,void * user_data)120 static int user_notify_cb(enum ocpp_notify_reason reason,
121 union ocpp_io_value *io,
122 void *user_data)
123 {
124 static int wh = 6 + NO_OF_CONN;
125 int idx;
126 int i;
127
128 switch (reason) {
129 case OCPP_USR_GET_METER_VALUE:
130 if (OCPP_OMM_ACTIVE_ENERGY_TO_EV == io->meter_val.mes) {
131 snprintf(io->meter_val.val, CISTR50, "%u",
132 wh + io->meter_val.id_con);
133
134 wh++;
135 LOG_DBG("mtr reading val %s con %d", io->meter_val.val,
136 io->meter_val.id_con);
137
138 return 0;
139 }
140 break;
141
142 case OCPP_USR_START_CHARGING:
143 if (io->start_charge.id_con < 0) {
144 for (i = 0; i < NO_OF_CONN; i++) {
145 if (tid[i] == NULL) {
146 break;
147 }
148 }
149
150 if (i >= NO_OF_CONN) {
151 return -EBUSY;
152 }
153 idx = i;
154 } else {
155 idx = io->start_charge.id_con - 1;
156 }
157
158 if (tid[idx] == NULL) {
159 LOG_INF("Remote start charging idtag %s connector %d\n",
160 idtag[idx], idx + 1);
161
162 strncpy(idtag[idx], io->start_charge.idtag,
163 sizeof(idtag[0]));
164
165 tid[idx] = k_thread_create(&tinfo[idx], cp_stk[idx],
166 sizeof(cp_stk[idx]), ocpp_cp_entry,
167 (void *)(uintptr_t)(idx + 1), idtag[idx],
168 obs[idx], 7, 0, K_NO_WAIT);
169
170 return 0;
171 }
172 break;
173
174 case OCPP_USR_STOP_CHARGING:
175 zbus_chan_pub(&ch_event, io, K_MSEC(100));
176 return 0;
177
178 case OCPP_USR_UNLOCK_CONNECTOR:
179 LOG_INF("unlock connector %d\n", io->unlock_con.id_con);
180 return 0;
181 }
182
183 return -ENOTSUP;
184 }
185
ocpp_cp_entry(void * p1,void * p2,void * p3)186 static void ocpp_cp_entry(void *p1, void *p2, void *p3)
187 {
188 int ret;
189 int idcon = (int)(uintptr_t)p1;
190 char *idtag = (char *)p2;
191 struct zbus_observer *obs = (struct zbus_observer *)p3;
192 ocpp_session_handle_t sh = NULL;
193 enum ocpp_auth_status status;
194 const uint32_t timeout_ms = 500;
195
196 ret = ocpp_session_open(&sh);
197 if (ret < 0) {
198 LOG_ERR("ocpp open ses idcon %d> res %d\n", idcon, ret);
199 return;
200 }
201
202 while (1) {
203 /* Avoid quick retry since authorization request is possible only
204 * after Bootnotification process (handled in lib) completed.
205 */
206
207 k_sleep(K_SECONDS(5));
208 ret = ocpp_authorize(sh,
209 idtag,
210 &status,
211 timeout_ms);
212 if (ret < 0) {
213 LOG_ERR("ocpp auth %d> idcon %d status %d\n",
214 ret, idcon, status);
215 } else {
216 LOG_INF("ocpp auth %d> idcon %d status %d\n",
217 ret, idcon, status);
218 break;
219 }
220 }
221
222 if (status != OCPP_AUTH_ACCEPTED) {
223 LOG_ERR("ocpp start idcon %d> not authorized status %d\n",
224 idcon, status);
225 return;
226 }
227
228 ret = ocpp_start_transaction(sh, sys_rand32_get(), idcon, timeout_ms);
229 if (ret == 0) {
230 const struct zbus_channel *chan;
231 union ocpp_io_value io;
232
233 LOG_INF("ocpp start charging connector id %d\n", idcon);
234 memset(&io, 0xff, sizeof(io));
235
236 /* wait for stop charging event from main or remote CS */
237 zbus_chan_add_obs(&ch_event, obs, K_SECONDS(1));
238 do {
239 zbus_sub_wait(obs, &chan, K_FOREVER);
240 zbus_chan_read(chan, &io, K_SECONDS(1));
241
242 if (io.stop_charge.id_con == idcon) {
243 break;
244 }
245
246 } while (1);
247 }
248
249 ret = ocpp_stop_transaction(sh, sys_rand32_get(), timeout_ms);
250 if (ret < 0) {
251 LOG_ERR("ocpp stop txn idcon %d> %d\n", idcon, ret);
252 return;
253 }
254
255 LOG_INF("ocpp stop charging connector id %d\n", idcon);
256 k_sleep(K_SECONDS(1));
257 ocpp_session_close(sh);
258 tid[idcon - 1] = NULL;
259 k_sleep(K_SECONDS(1));
260 k_thread_abort(k_current_get());
261 }
262
ocpp_getaddrinfo(char * server,int port,char ** ip)263 static int ocpp_getaddrinfo(char *server, int port, char **ip)
264 {
265 int ret;
266 uint8_t retry = 5;
267 char addr_str[INET_ADDRSTRLEN];
268 struct sockaddr_storage b;
269 struct addrinfo *result = NULL;
270 struct addrinfo *addr;
271 struct addrinfo hints = {
272 .ai_family = AF_INET,
273 .ai_socktype = SOCK_STREAM
274 };
275
276 LOG_INF("cs server %s %d", server, port);
277 do {
278 ret = getaddrinfo(server, NULL, &hints, &result);
279 if (ret == -EAGAIN) {
280 LOG_ERR("ERROR: getaddrinfo %d, rebind", ret);
281 k_sleep(K_SECONDS(1));
282 } else if (ret != 0) {
283 LOG_ERR("ERROR: getaddrinfo failed %d", ret);
284 return ret;
285 }
286 } while (--retry && ret);
287
288 addr = result;
289 while (addr != NULL) {
290 /* IPv4 Address. */
291 if (addr->ai_addrlen == sizeof(struct sockaddr_in)) {
292 struct sockaddr_in *broker =
293 ((struct sockaddr_in *)&b);
294
295 broker->sin_addr.s_addr =
296 ((struct sockaddr_in *)addr->ai_addr)
297 ->sin_addr.s_addr;
298 broker->sin_family = AF_INET;
299 broker->sin_port = htons(port);
300
301 inet_ntop(AF_INET, &broker->sin_addr, addr_str,
302 sizeof(addr_str));
303
304 *ip = strdup(addr_str);
305 LOG_INF("IPv4 Address %s", addr_str);
306 break;
307 }
308
309 LOG_ERR("error: ai_addrlen = %u should be %u or %u",
310 (unsigned int)addr->ai_addrlen,
311 (unsigned int)sizeof(struct sockaddr_in),
312 (unsigned int)sizeof(struct sockaddr_in6));
313
314 addr = addr->ai_next;
315 }
316
317 /* Free the address. */
318 freeaddrinfo(result);
319
320 return 0;
321 }
322
main(void)323 int main(void)
324 {
325 int ret;
326 int i;
327 char *ip = NULL;
328
329 struct ocpp_cp_info cpi = { "basic", "zephyr", .num_of_con = NO_OF_CONN };
330 struct ocpp_cs_info csi = {NULL,
331 CONFIG_NET_SAMPLE_OCPP_WS_PATH,
332 CONFIG_NET_SAMPLE_OCPP_PORT,
333 AF_INET};
334
335 printk("OCPP sample %s\n", CONFIG_BOARD);
336
337 wait_for_network();
338
339 ret = ocpp_getaddrinfo(CONFIG_NET_SAMPLE_OCPP_SERVER, CONFIG_NET_SAMPLE_OCPP_PORT, &ip);
340 if (ret < 0) {
341 return ret;
342 }
343
344 csi.cs_ip = ip;
345
346 ocpp_get_time_from_sntp();
347
348 ret = ocpp_init(&cpi,
349 &csi,
350 user_notify_cb,
351 NULL);
352 if (ret < 0) {
353 LOG_ERR("ocpp init failed %d\n", ret);
354 return ret;
355 }
356
357 /* Spawn threads for each connector */
358 for (i = 0; i < NO_OF_CONN; i++) {
359 snprintf(idtag[i], sizeof(idtag[0]), "ZepId%02d", i);
360
361 tid[i] = k_thread_create(&tinfo[i], cp_stk[i],
362 sizeof(cp_stk[i]),
363 ocpp_cp_entry, (void *)(uintptr_t)(i + 1),
364 idtag[i], obs[i], 7, 0, K_NO_WAIT);
365 }
366
367 /* Active charging session */
368 k_sleep(K_SECONDS(30));
369
370 /* Send stop charging to thread */
371 for (i = 0; i < NO_OF_CONN; i++) {
372 union ocpp_io_value io = {0};
373
374 io.stop_charge.id_con = i + 1;
375
376 zbus_chan_pub(&ch_event, &io, K_MSEC(100));
377 k_sleep(K_SECONDS(1));
378 }
379
380 /* User could trigger remote start/stop transaction from CS server */
381 k_sleep(K_SECONDS(1200));
382
383 return 0;
384 }
385