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