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