diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml
index ed368a990e9b2..854d00ebd09cf 100644
--- a/MAINTAINERS.yml
+++ b/MAINTAINERS.yml
@@ -2573,6 +2573,7 @@ Networking:
files-exclude:
- doc/connectivity/networking/api/gptp.rst
- doc/connectivity/networking/api/ieee802154.rst
+ - doc/connectivity/networking/api/ocpp.rst
- doc/connectivity/networking/api/wifi.rst
- include/zephyr/net/gptp.h
- include/zephyr/net/ieee802154*.h
@@ -2582,6 +2583,7 @@ Networking:
- samples/net/gptp/
- samples/net/sockets/coap_*/
- samples/net/lwm2m_client/
+ - samples/net/ocpp/
- samples/net/wifi/
- samples/net/dhcpv4_client/
- subsys/net/buf*.c
@@ -2591,10 +2593,12 @@ Networking:
- subsys/net/lib/coap/
- subsys/net/lib/config/ieee802154*
- subsys/net/lib/lwm2m/
+ - subsys/net/lib/ocpp/
- subsys/net/lib/tls_credentials/
- subsys/net/lib/dhcpv4/
- tests/net/dhcpv4/
- tests/net/ieee802154/
+ - tests/net/lib/ocpp/
- tests/net/wifi/
labels:
- "area: Networking"
@@ -2764,6 +2768,17 @@ Networking:
tests:
- net.ieee802154
+"Networking: OCPP":
+ status: maintained
+ maintainers:
+ - ssekar15
+ files:
+ - samples/net/ocpp/
+ - subsys/net/lib/ocpp/
+ - tests/net/lib/ocpp/
+ labels:
+ - "area: OCPP"
+
"Networking: OpenThread":
status: maintained
maintainers:
@@ -4928,3 +4943,12 @@ zbus:
- "area: Linkable Loadable Extensions"
tests:
- llext
+
+"West project: json-c":
+ status: maintained
+ maintainers:
+ - parthitce
+ files:
+ - modules/json-c/
+ labels:
+ - "area: JSON"
diff --git a/doc/connectivity/networking/api/ocpp.rst b/doc/connectivity/networking/api/ocpp.rst
new file mode 100644
index 0000000000000..d4f403997fbe3
--- /dev/null
+++ b/doc/connectivity/networking/api/ocpp.rst
@@ -0,0 +1,110 @@
+.. _ocpp_interface:
+
+Open Charge Point Protocol (OCPP)
+#################################
+
+.. contents::
+ :local:
+ :depth: 2
+
+Overview
+********
+
+Open Charge Point Protocol (OCPP) is an application protocol for communication
+between Charge Points (Electric vehicle (EV) charging stations) and a central
+management system, also known as a charging station network. OCPP is a
+`standard `_
+defined by The Open Charge Alliance and goal is to offer a uniform solution for
+the method of communication between charge point and central system. With this
+protocol it is possible to connect any central system with any charge point,
+regardless of the vendor
+
+Zephyr provides an OCPP Charge Point (CP) library built on top of websocket API
+with payload in json format. The library can be enabled with
+:kconfig:option:`CONFIG_OCPP` Kconfig option. Currently OCPP 1.6 with basic
+core profile is supported.
+
+OCPP charge point (CP) require a Central System (CS) server to connect, an open
+source SteVe server shall be setup locally for devlopment purpose, See
+`SteVe server `_
+for more information about the setup.
+
+The Zephyr OCPP CP library implements the following items:
+
+* engine to process socket connectivity and events
+* OCPP core functions to frame/parse payload, user notification for OCPP events,
+ heartbeat notification
+
+Sample usage
+************
+
+Init ocpp library with overall CP and CS information. Prior to init an OCPP
+library, a network interface should be ready using ethernet or wifi or modem.
+A filled CP, CS structure and user callback needs to be passed in ocpp_init.
+
+.. code-block:: c
+
+ static int user_notify_cb(ocpp_notify_reason_t reason,
+ ocpp_io_value_t *io,
+ void *user_data)
+ {
+
+ switch (reason) {
+ case OCPP_USR_GET_METER_VALUE:
+ ...
+ break;
+
+ case OCPP_USR_START_CHARGING:
+ ...
+ break;
+
+ ...
+ ...
+ }
+
+ /* OCPP configuration */
+ ocpp_cp_info_t cpi = { "basic", "zephyr", .num_of_con = 1};
+ ocpp_cs_info_t csi = {"192.168.1.3", /* ip address */
+ "/steve/websocket/CentralSystemService/zephyr",
+ 8180,
+ AF_INET};
+
+ ret = ocpp_init(&cpi, &csi, user_notify_cb, NULL);
+
+A unique session must open for each physical connector before any ocpp
+transcation API call.
+
+.. code-block:: c
+
+ ocpp_session_handle_t sh = NULL;
+ ret = ocpp_session_open(&sh);
+
+idtag is EV user's authentication token which should match with list on CS.
+Authorize request must call to ensure validity of idtag (if charging request
+originate from local CP) before start energy transfer to EV.
+
+.. code-block:: c
+
+ ocpp_auth_status_t status;
+ ret = ocpp_authorize(sh, idtag, &status, 500);
+
+On successful, authorization status is available in status.
+
+Apart from local CP, charging request may originate from CS is notified to user
+in callback with OCPP_USR_START_CHARGING, here authorize request call is
+optional. When the CS is ready to provide power to EV, a start transaction
+is notified to CS with meter reading and connector id using ocpp_start_transaction.
+
+.. code-block:: c
+
+ const int idcon = 1;
+ const int mval = 25; //meter reading in wh
+ ret = ocpp_start_transaction(sh, mval, idcon, 200);
+
+Once the start transaction is success, user callback is invoked to get meter
+readings from the library. callback should be not be hold for longer time.
+
+API Reference
+*************
+
+.. doxygengroup:: ocpp_api
diff --git a/doc/connectivity/networking/api/protocols.rst b/doc/connectivity/networking/api/protocols.rst
index 57aeaae8d021a..3d28869cb4096 100644
--- a/doc/connectivity/networking/api/protocols.rst
+++ b/doc/connectivity/networking/api/protocols.rst
@@ -15,4 +15,5 @@ Protocols
lwm2m
mqtt
mqtt_sn
+ ocpp
tftp
diff --git a/include/zephyr/net/ocpp.h b/include/zephyr/net/ocpp.h
new file mode 100644
index 0000000000000..b34ef5fb6eeea
--- /dev/null
+++ b/include/zephyr/net/ocpp.h
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file ocpp.h
+ *
+ * @defgroup ocpp_api OCPP library
+ * @ingroup networking
+ * @{
+ * @brief OCPP Charge Point Implementation
+ *
+ * @note The implementation assumes Websocket module is enabled.
+ *
+ * @note By default the implementation uses OCPP version 1.6.
+ */
+
+#ifndef ZEPHYR_INCLUDE_NET_OCPP_H_
+#define ZEPHYR_INCLUDE_NET_OCPP_H_
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Max length of string literals e.g idtag */
+#define CISTR50 50
+
+/**
+ * @brief OCPP IdTag authorization status in result to ocpp request
+ * authorization
+ */
+enum ocpp_auth_status {
+ OCPP_AUTH_INVALID, /*< IdTag not valid */
+ OCPP_AUTH_ACCEPTED, /*< accepted, allowed to charge */
+ OCPP_AUTH_BLOCKED, /*< blocked to charge */
+ OCPP_AUTH_EXPIRED, /*< IdTag expired, not allowed to charge */
+ OCPP_AUTH_CONCURRENT_TX /*< Parallel access of same IdTag */
+};
+
+enum ocpp_notify_reason {
+ /** User must fill the current reading */
+ OCPP_USR_GET_METER_VALUE,
+
+ /** Process the start charging request as like idtag received from local
+ * e.g authorize etc
+ */
+ OCPP_USR_START_CHARGING,
+
+ /** Process the stop charging sequence */
+ OCPP_USR_STOP_CHARGING,
+
+ /** Unlock mechanical connector of CP */
+ OCPP_USR_UNLOCK_CONNECTOR,
+};
+
+/** @brief OCPP meter readings to be filled on user callback request from
+ * library
+ */
+enum ocpp_meter_measurand {
+ OCPP_OMM_CURRENT_FROM_EV, /*< current from EV, in A */
+ OCPP_OMM_CURRENT_TO_EV, /*< current to EV, in A */
+ OCPP_OMM_CURRENT_MAX_OFFERED_TO_EV, /*< maximum current offered to EV, in A */
+ OCPP_OMM_ACTIVE_ENERGY_FROM_EV, /*< active energy from EV, in Wh */
+ OCPP_OMM_ACTIVE_ENERGY_TO_EV, /*< active energy to EV, in Wh */
+ OCPP_OMM_REACTIVE_ENERGY_FROM_EV, /*< reactive energy from EV, in varh */
+ OCPP_OMM_REACTIVE_ENERGY_TO_EV, /*< reactive energy to EV, in varh */
+ OCPP_OMM_ACTIVE_POWER_FROM_EV, /*< active power from EV, in W */
+ OCPP_OMM_ACTIVE_POWER_TO_EV, /*< active power to EV, in W */
+ OCPP_OMM_REACTIVE_POWER_FROM_EV, /*< reactive power from EV, in var */
+ OCPP_OMM_REACTIVE_POWER_TO_EV, /*< reactive power to EV, in var */
+ OCPP_OMM_POWERLINE_FREQ, /*< powerline frequency, in Hz */
+ OCPP_OMM_POWER_FACTOR, /*< power factor of supply */
+ OCPP_OMM_POWER_MAX_OFFERED_TO_EV, /*< maximum power offered to EV, in W */
+ OCPP_OMM_FAN_SPEED, /*< fan speed, in rpm */
+ OCPP_OMM_CHARGING_PERCENT, /*< charging percentage */
+ OCPP_OMM_TEMPERATURE, /*< temperature inside charge point, in Celsius */
+ OCPP_OMM_VOLTAGE_AC_RMS, /*< AC RMS supply voltage, in V */
+
+ OCPP_OMM_END
+};
+
+/** @brief OCPP user callback notification/request of input/output values
+ * union member should be accessed with enum value ocpp_notify_reason
+ * correspondingly.
+ * e.g. callback reason is OCPP_USR_GET_METER_VALUE, struct meter_val
+ * is valid
+ */
+union ocpp_io_value {
+
+ struct {
+ /** Input to user, requested connector_id or 0 - main meter */
+ int id_con;
+
+ /** Input to user, measurand */
+ enum ocpp_meter_measurand mes;
+
+ /** To be filled by user, value as string */
+ char val[CISTR50];
+ } meter_val;
+
+ struct {
+ char idtag[CISTR50]; /**< Input to user */
+
+ /** Input to user(optional). connector id -1 means invalid */
+ int id_con;
+ } start_charge;
+
+ struct {
+ int id_con; /**< Input to user, to stop charging connector */
+ } stop_charge;
+
+ struct {
+ int id_con; /**< Input to user, to unlock connector id. */
+ } unlock_con;
+};
+
+/** @brief Parameters for ocpp_init information about Charge Point (CP)
+ * all are string literal except num_of_con
+ */
+struct ocpp_cp_info {
+ char *model; /**< Charge Point (CP) model */
+ char *vendor; /**< CP vendor */
+
+ int num_of_con; /**< Max. number of connector supports */
+
+ /** optional fields */
+ char *sl_no; /**< CP serial number */
+ char *box_sl_no; /**< Box serial number */
+ char *fw_ver; /**< Firmware version */
+ char *iccid; /**< ICC ID */
+ char *imsi; /**< IMSI */
+ char *meter_sl_no; /**< Main power meter serial number */
+ char *meter_type; /**< Main power meter type */
+};
+
+/** @brief Parameters for ocpp_init information about central system (CS) */
+struct ocpp_cs_info {
+ char *cs_ip; /**< Central system IP address */
+ char *ws_url; /**< Websocket url exclude ipaddr & port */
+ int port; /**< Central system port number */
+ sa_family_t sa_family; /** IP protocol family type 4/6 */
+};
+
+/** @brief Parameters opaque session handle for ocpp_* API */
+typedef void *ocpp_session_handle_t;
+
+/**
+ * @brief Asynchronous event notification callback registered by the
+ * application. advised callback should not be hold for longer time
+ * to unblock the ocpp protocol stack/lib.
+ *
+ * @param[in] reason for callback invoked.
+ * @param[in] io reffered corresponding to reason.
+ * @param[in] user_data passed on ocpp_init.
+ *
+ * @return 0 or a negative error code (errno.h)
+ */
+typedef int (*ocpp_user_notify_callback_t)(enum ocpp_notify_reason reason,
+ union ocpp_io_value *io,
+ void *user_data);
+
+/**
+ * @brief OCPP library init.
+ *
+ * @param[in] cpi Charge Point information
+ * @param[in] csi Central System information
+ * @param[in] cb user register callback
+ * @param[in] user_data same reference will be passed on callback
+ *
+ * @return 0 on success or a negative error code (errno.h) indicating reason of failure
+ *
+ * @note Must be called before any other ocpp API
+ */
+int ocpp_init(struct ocpp_cp_info *cpi,
+ struct ocpp_cs_info *csi,
+ ocpp_user_notify_callback_t cb,
+ void *user_data);
+
+/**
+ * @brief API to request a new Session
+ *
+ * @param[out] hndl a valid opaque handle
+ *
+ * @return 0 on success or a negative error code (errno.h) indicating reason of failure
+ *
+ * @note Each connector should open unique session after ocpp_init and
+ * prior to anyother ocpp_* request message api
+ */
+int ocpp_session_open(ocpp_session_handle_t *hndl);
+
+/**
+ * @brief API to close a Session
+ *
+ * @param[in] hndl a handle received from session open
+ */
+void ocpp_session_close(ocpp_session_handle_t hndl);
+
+/**
+ * @brief Authorize request call to CS to get validity of idtag
+ *
+ * @param[in] hndl session handle
+ * @param[in] idtag (string literal) to get authorize validity
+ * @param[out] status authorization status
+ * @param[in] timeout_ms in msec
+ *
+ * @return 0 on success or a negative error code (errno.h) indicating reason of failure
+ */
+int ocpp_authorize(ocpp_session_handle_t hndl,
+ char *idtag,
+ enum ocpp_auth_status *status,
+ uint32_t timeout_ms);
+
+/**
+ * @brief Notify transaction start to CS
+ *
+ * @param[in] hndl session handle
+ * @param[in] Wh energy meter reading of this connector
+ * @param[in] conn_id connector id should be > 0 and sequential number
+ * @param[in] timeout_ms timeout in msec
+ *
+ * @return: 0 on success
+ * EACCES - not authorized, should follow the stop charging process
+ * a negative error code (errno.h) indicating reason of failure
+ */
+int ocpp_start_transaction(ocpp_session_handle_t hndl,
+ int Wh,
+ uint8_t conn_id,
+ uint32_t timeout_ms);
+
+/**
+ * @brief Notify transaction stopped to CS
+ *
+ * @param[in] hndl session handle
+ * @param[in] Wh energy meter reading of this connector
+ * @param[in] timeout_ms timeout in msec
+ *
+ * @return 0 on success or a negative error code (errno.h) indicating reason of failure
+ */
+int ocpp_stop_transaction(ocpp_session_handle_t hndl,
+ int Wh,
+ uint32_t timeout_ms);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZEPHYR_INCLUDE_NET_OCPP_H_ */
+
+/**@} */
diff --git a/modules/json-c/CMakeLists.txt b/modules/json-c/CMakeLists.txt
new file mode 100644
index 0000000000000..f036e3b096793
--- /dev/null
+++ b/modules/json-c/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright (c) 2023 Linumiz
+# SPDX-License-Identifier: Apache-2.0
+
+if(CONFIG_JSONC)
+
+set(JSONC_UPSTREAM ${ZEPHYR_JSON_C_MODULE_DIR})
+
+zephyr_interface_library_named(JSONC)
+
+zephyr_include_directories(include)
+zephyr_include_directories(${JSONC_UPSTREAM})
+
+zephyr_library()
+zephyr_library_sources(
+ ${JSONC_UPSTREAM}/arraylist.c
+ ${JSONC_UPSTREAM}/debug.c
+ ${JSONC_UPSTREAM}/json_c_version.c
+ ${JSONC_UPSTREAM}/json_object.c
+ ${JSONC_UPSTREAM}/json_object_iterator.c
+ ${JSONC_UPSTREAM}/json_pointer.c
+ ${JSONC_UPSTREAM}/json_tokener.c
+ ${JSONC_UPSTREAM}/json_util.c
+ ${JSONC_UPSTREAM}/json_visit.c
+ ${JSONC_UPSTREAM}/libjson.c
+ ${JSONC_UPSTREAM}/linkhash.c
+ ${JSONC_UPSTREAM}/printbuf.c
+ ${JSONC_UPSTREAM}/random_seed.c
+ ${JSONC_UPSTREAM}/strerror_override.c
+)
+
+zephyr_library_link_libraries(JSONC)
+target_link_libraries(JSONC INTERFACE zephyr_interface)
+
+endif()
diff --git a/modules/json-c/Kconfig b/modules/json-c/Kconfig
new file mode 100644
index 0000000000000..69ab879d24d2d
--- /dev/null
+++ b/modules/json-c/Kconfig
@@ -0,0 +1,10 @@
+# Copyright (c) 2023 Linumiz
+# SPDX-License-Identifier: Apache-2.0
+
+config ZEPHYR_JSONC_MODULE
+ bool
+
+config JSONC
+ bool "JSON implementation in C"
+ help
+ This option enables json-c support.
diff --git a/modules/json-c/include/apps_config.h b/modules/json-c/include/apps_config.h
new file mode 100644
index 0000000000000..79d3497efd711
--- /dev/null
+++ b/modules/json-c/include/apps_config.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2023 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_RESOURCE_H
+
+/* Define if you have the `getrusage' function. */
+#define HAVE_GETRUSAGE
+
+/* #undef HAVE_JSON_TOKENER_GET_PARSE_END */
diff --git a/modules/json-c/include/config.h b/modules/json-c/include/config.h
new file mode 100644
index 0000000000000..693a83018f6a4
--- /dev/null
+++ b/modules/json-c/include/config.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2023 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/* Enable RDRAND Hardware RNG Hash Seed */
+/* #undef ENABLE_RDRAND */
+
+/* Override json_c_get_random_seed() with custom code */
+/* #undef OVERRIDE_GET_RANDOM_SEED */
+
+/* Enable partial threading support */
+/* #undef ENABLE_THREADING */
+
+/* Define if .gnu.warning accepts long strings. */
+/* #undef HAS_GNU_WARNING_LONG */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_DLFCN_H
+
+/* Define to 1 if you have the header file. */
+/* #define HAVE_ENDIAN_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_FCNTL_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_INTTYPES_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_LIMITS_H
+
+/* Define to 1 if you have the header file. */
+/* #define HAVE_LOCALE_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_MEMORY_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDARG_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDINT_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDLIB_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRINGS_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRING_H
+
+/* Define to 1 if you have the header file. */
+/* #define HAVE_SYSLOG_H 1 */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_CDEFS_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the header file. */
+/* #define HAVE_SYS_RANDOM_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_RESOURCE_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_XLOCALE_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_BSD_STDLIB_H */
+
+/* Define to 1 if you have `arc4random' */
+/* #define HAVE_ARC4RANDOM */
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+/* #undef HAVE_DOPRNT */
+
+/* Has atomic builtins */
+#define HAVE_ATOMIC_BUILTINS
+
+/* Define to 1 if you have the declaration of `INFINITY', and to 0 if you don't. */
+#define HAVE_DECL_INFINITY
+
+/* Define to 1 if you have the declaration of `isinf', and to 0 if you don't. */
+#define HAVE_DECL_ISINF
+
+/* Define to 1 if you have the declaration of `isnan', and to 0 if you don't. */
+#define HAVE_DECL_ISNAN
+
+/* Define to 1 if you have the declaration of `nan', and to 0 if you don't. */
+#define HAVE_DECL_NAN
+
+/* Define to 1 if you have the declaration of `_finite', and to 0 if you don't. */
+/* #undef HAVE_DECL__FINITE */
+
+/* Define to 1 if you have the declaration of `_isnan', and to 0 if you don't. */
+/* #undef HAVE_DECL__ISNAN */
+
+/* Define to 1 if you have the `open' function. */
+#define HAVE_OPEN
+
+/* Define to 1 if you have the `realloc' function. */
+#define HAVE_REALLOC
+
+/* Define to 1 if you have the `setlocale' function. */
+/* #define HAVE_SETLOCALE */
+
+/* Define to 1 if you have the `snprintf' function. */
+#define HAVE_SNPRINTF
+
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `uselocale' function. */
+/* #define HAVE_USELOCALE */
+
+/* Define to 1 if you have the `vasprintf' function. */
+#define HAVE_VASPRINTF
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF
+
+/* Define to 1 if you have the `vsnprintf' function. */
+#define HAVE_VSNPRINTF
+
+/* Define to 1 if you have the `vsyslog' function. */
+/* #define HAVE_VSYSLOG 1 */
+
+/* Define if you have the `getrandom' function. */
+/* #define HAVE_GETRANDOM */
+
+/* Define if you have the `getrusage' function. */
+#define HAVE_GETRUSAGE
+
+#define HAVE_STRTOLL
+#if !defined(HAVE_STRTOLL)
+#define strtoll strtoll
+/* #define json_c_strtoll strtoll*/
+#endif
+
+#define HAVE_STRTOULL
+#if !defined(HAVE_STRTOULL)
+#define strtoull strtoull
+/* #define json_c_strtoull strtoull */
+#endif
+
+/* Have __thread */
+/* #define HAVE___THREAD */
+
+/* Public define for json_inttypes.h */
+#define JSON_C_HAVE_INTTYPES_H 1
+
+/* Name of package */
+#define PACKAGE "json-c"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "json-c@googlegroups.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "json-c"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "json-c 0.16.99"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "json-c"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "https://github.com/json-c/json-c"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "0.16.99"
+
+/* The number of bytes in type int */
+#define SIZEOF_INT 4
+
+/* The number of bytes in type int64_t */
+#define SIZEOF_INT64_T 8
+
+/* The number of bytes in type long */
+#define SIZEOF_LONG 8
+
+/* The number of bytes in type long long */
+#define SIZEOF_LONG_LONG 8
+
+/* The number of bytes in type size_t */
+#define SIZEOF_SIZE_T 8
+
+/* The number of bytes in type ssize_t */
+#define SIZEOF_SSIZE_T 8
+
+/* Specifier for __thread */
+#define SPEC___THREAD __thread
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS
+
+/* Version number of package */
+#define VERSION "0.16.99"
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `unsigned int' if does not define. */
+/* #undef size_t */
diff --git a/modules/json-c/include/json.h b/modules/json-c/include/json.h
new file mode 100644
index 0000000000000..6e66f9fc91a79
--- /dev/null
+++ b/modules/json-c/include/json.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2023 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef _json_h_
+#define _json_h_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "arraylist.h"
+#include "debug.h"
+#include "json_c_version.h"
+#include "json_object.h"
+#include "json_object_iterator.h"
+#include "json_pointer.h"
+#include "json_tokener.h"
+#include "json_util.h"
+#include "linkhash.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/modules/json-c/include/json_config.h b/modules/json-c/include/json_config.h
new file mode 100644
index 0000000000000..c7f08c63b6f94
--- /dev/null
+++ b/modules/json-c/include/json_config.h
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2023 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/* Define to 1 if you have the header file. */
+#define JSON_C_HAVE_INTTYPES_H 1
diff --git a/samples/net/ocpp/CMakeLists.txt b/samples/net/ocpp/CMakeLists.txt
new file mode 100644
index 0000000000000..f431c91d1c8b3
--- /dev/null
+++ b/samples/net/ocpp/CMakeLists.txt
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(ocpp)
+
+target_sources(app PRIVATE src/main.c)
+set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/)
+
+function(pem_to_mbedtls target input_file_fullpath)
+ get_filename_component(input_file ${input_file_fullpath} NAME)
+ file(READ ${input_file_fullpath} input_file_content)
+ string(REGEX REPLACE "\n" "\\\\n" input_file_content "${input_file_content}")
+ set(GENERATED_FILE ${gen_dir}/${input_file}.inc)
+ file(WRITE ${GENERATED_FILE} "\"${input_file_content}\"\n")
+ generate_unique_target_name_from_filename(${input_file} generated_target_name)
+ add_custom_target(${generated_target_name} DEPENDS ${GENERATED_FILE})
+ add_dependencies(${target} ${generated_target_name})
+endfunction()
+
+pem_to_mbedtls(app ${ZEPHYR_BASE}/subsys/net/lib/ocpp/credentials/ec.crt)
+
diff --git a/samples/net/ocpp/Kconfig b/samples/net/ocpp/Kconfig
new file mode 100644
index 0000000000000..8edaa266d1e16
--- /dev/null
+++ b/samples/net/ocpp/Kconfig
@@ -0,0 +1,22 @@
+# Config options for OCPP sample application
+
+# Copyright (c) 2024 Linumiz
+# SPDX-License-Identifier: Apache-2.0
+
+mainmenu "OCPP sample application"
+config NET_SAMPLE_OCPP_SERVER
+ string "OCPP server ip"
+ help
+ OCPP central system server ip address
+
+config NET_SAMPLE_OCPP_PORT
+ int "OCPP server port"
+ help
+ OCPP central system server port
+
+config NET_SAMPLE_SNTP_SERVER
+ string "SNTP server ip"
+ help
+ SNTP server ip to get the time from network
+
+source "Kconfig.zephyr"
diff --git a/samples/net/ocpp/README.rst b/samples/net/ocpp/README.rst
new file mode 100644
index 0000000000000..0b9bb7707463f
--- /dev/null
+++ b/samples/net/ocpp/README.rst
@@ -0,0 +1,69 @@
+.. zephyr:code-sample:: ocpp
+ :name: OCPP charge point
+ :relevant-api: ocpp_api
+
+ Implement an OCPP charge point that connects to a Central System server and
+ simulates the meter readings.
+
+Overview
+********
+
+Open Charge Point Protocol (OCPP) is an application protocol for communication
+between Charge Points (Electric vehicle (EV) charging stations) and a central
+management system, also known as a charging station network.
+
+This ocpp sample application for Zephyr implements the ocpp library
+and establishes a connection to an Central System server using the web socket
+
+The source code for this sample application can be found at:
+:zephyr_file:`samples/net/ocpp`.
+
+Requirements
+************
+
+- Linux machine
+- STEM32 Discovery kit (32F769IDISCOVERY) or any network interface device
+- SteVe Demo Server ()
+- LAN for testing purposes (Ethernet)
+
+Building and Running
+********************
+
+Build the ocpp sample application like this:
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/net/ocpp
+ :board:
+ :goals: build
+ :compact:
+
+The sample application is to built and tested on
+
+.. code-block:: console
+
+ west build -b stm32f769i_disco
+ west flash
+
+The output of sample is:
+
+.. code-block:: console
+
+ *** Booting Zephyr OS build v3.6.0-rc1-37-g8c035d8f24cf ***
+ OCPP sample stm32f769i_disco
+ [00:00:02.642,000] net_dhcpv4: Received: 192.168.1.101
+ [00:00:02.642,000] main: net mgr cb
+ [00:00:02.642,000] main: Your address: 192.168.1.101
+ [00:00:02.642,000] main: Lease time: 86400 seconds
+ [00:00:02.642,000] main: Subnet: 255.255.255.0
+ [00:00:02.643,000] main: Router: 192.168.1.1
+ [00:00:07.011,000] main: cs server 122.165.245.213 8180
+ [00:00:07.011,000] main: IPv4 Address 122.165.245.213
+ [00:00:07.024,000] main: sntp succ since Epoch: 1707890823
+ [00:00:07.024,000] ocpp: upstream init
+ [00:00:07.025,000] ocpp: ocpp init success
+ [00:00:17.066,000] main: ocpp auth 0> idcon 1 status 1
+ [00:00:17.101,000] main: ocpp auth 0> idcon 2 status 1
+ [00:00:17.197,000] main: ocpp start charging connector id 1
+ [00:00:17.255,000] main: ocpp start charging connector id 2
+ [00:01:07.064,000] main: ocpp stop charging connector id 1
+ [00:01:08.063,000] main: ocpp stop charging connector id 2
diff --git a/samples/net/ocpp/overlay-ec.conf b/samples/net/ocpp/overlay-ec.conf
new file mode 100644
index 0000000000000..c109147eb8e50
--- /dev/null
+++ b/samples/net/ocpp/overlay-ec.conf
@@ -0,0 +1,7 @@
+CONFIG_PSA_WANT_ALG_ECDH=y
+CONFIG_PSA_WANT_ALG_ECDSA=y
+CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE=y
+CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT=y
+CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT=y
+CONFIG_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_DERIVE=y
+CONFIG_PSA_WANT_ECC_SECP_R1_256=y
diff --git a/samples/net/ocpp/overlay-tls13.conf b/samples/net/ocpp/overlay-tls13.conf
new file mode 100644
index 0000000000000..f5d6315ce6d95
--- /dev/null
+++ b/samples/net/ocpp/overlay-tls13.conf
@@ -0,0 +1,40 @@
+# Enable Secure Sockets
+CONFIG_NET_SOCKETS_SOCKOPT_TLS=y
+
+# Mbed TLS configuration
+CONFIG_MBEDTLS=y
+CONFIG_MBEDTLS_BUILTIN=y
+CONFIG_MBEDTLS_ENABLE_HEAP=y
+CONFIG_MBEDTLS_HEAP_SIZE=20000
+CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048
+CONFIG_MBEDTLS_PEM_CERTIFICATE_FORMAT=y
+CONFIG_MBEDTLS_SERVER_NAME_INDICATION=y
+# Build the PSA Crypto core so that the TLS stack uses the PSA crypto API.
+CONFIG_MBEDTLS_PSA_CRYPTO_C=y
+CONFIG_ENTROPY_GENERATOR=y
+
+# Disable some Kconfigs that are implied by CONFIG_NET_SOCKETS_SOCKOPT_TLS.
+CONFIG_MBEDTLS_TLS_VERSION_1_2=n
+CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_ENABLED=n
+CONFIG_MBEDTLS_MD=n
+CONFIG_MBEDTLS_RSA_C=n
+CONFIG_MBEDTLS_CIPHER_AES_ENABLED=n
+CONFIG_PSA_WANT_KEY_TYPE_AES=n
+CONFIG_PSA_WANT_ALG_CBC_NO_PADDING=n
+
+# Debug log options (optional)
+# CONFIG_MBEDTLS_LOG_LEVEL_DBG=y
+# CONFIG_MBEDTLS_DEBUG=y
+
+# TLS1.3 Configuration
+CONFIG_MBEDTLS_TLS_VERSION_1_3=y
+CONFIG_PSA_WANT_ALG_HKDF_EXTRACT=y
+CONFIG_PSA_WANT_ALG_HKDF_EXPAND=y
+CONFIG_PSA_WANT_ALG_GCM=y
+CONFIG_PSA_WANT_KEY_TYPE_AES=y
+CONFIG_PSA_WANT_ALG_CBC_NO_PADDING=y
+CONFIG_PSA_WANT_ALG_SHA_256=y
+CONFIG_PSA_WANT_ALG_SHA_384=y
+CONFIG_MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED=y
+
+
diff --git a/samples/net/ocpp/prj.conf b/samples/net/ocpp/prj.conf
new file mode 100644
index 0000000000000..fc78757a4054f
--- /dev/null
+++ b/samples/net/ocpp/prj.conf
@@ -0,0 +1,60 @@
+CONFIG_HTTP_CLIENT=y
+CONFIG_WEBSOCKET_CLIENT=y
+
+# Networking config
+CONFIG_NETWORKING=y
+CONFIG_NET_IPV4=y
+CONFIG_NET_IPV6=n
+CONFIG_NET_TCP=y
+CONFIG_NET_SOCKETS=y
+CONFIG_NET_SOCKETS_POLL_MAX=4
+CONFIG_NET_L2_ETHERNET=y
+CONFIG_ETH_DRIVER=y
+CONFIG_LOG=y
+
+CONFIG_LOG_MODE_IMMEDIATE=n
+
+# Networking debug (Optional)
+CONFIG_NET_LOG=n
+CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=n
+
+# OCPP
+CONFIG_OCPP=y
+
+# Please set the server addresses when compiling the sample
+CONFIG_NET_SAMPLE_SNTP_SERVER=""
+CONFIG_NET_SAMPLE_OCPP_SERVER=""
+CONFIG_NET_SAMPLE_OCPP_PORT=8443
+
+CONFIG_MAIN_STACK_SIZE=4096
+CONFIG_HEAP_MEM_POOL_SIZE=15000
+
+CONFIG_JSONC=y
+CONFIG_PICOLIBC=y
+CONFIG_NET_ARP=y
+CONFIG_NET_UDP=y
+CONFIG_NET_DHCPV4=y
+CONFIG_NET_DHCPV4_OPTION_CALLBACKS=y
+CONFIG_TEST_RANDOM_GENERATOR=y
+
+CONFIG_NET_TX_STACK_SIZE=2048
+CONFIG_NET_RX_STACK_SIZE=2048
+CONFIG_NET_PKT_RX_COUNT=28
+CONFIG_NET_BUF_RX_COUNT=60
+CONFIG_NET_MGMT=y
+CONFIG_NET_MGMT_EVENT=y
+
+CONFIG_SNTP=y
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_RUNTIME_OBSERVERS=y
+
+CONFIG_POSIX_API=y
+CONFIG_POSIX_CLOCK_SELECTION=y
+
+# Logging
+CONFIG_LOG=y
+CONFIG_PRINTK=y
+
+
+
diff --git a/samples/net/ocpp/sample.yaml b/samples/net/ocpp/sample.yaml
new file mode 100644
index 0000000000000..53607dfc68b55
--- /dev/null
+++ b/samples/net/ocpp/sample.yaml
@@ -0,0 +1,18 @@
+sample:
+ description: OCPP sample application
+ name: OCPP charge point
+common:
+ harness: net
+ tags:
+ - net
+ - ocpp
+
+tests:
+ sample.net.ocpp:
+ platform_allow:
+ - stm32f769i_disco
+ integration_platforms:
+ - qemu_x86
+ tags:
+ - net
+ - ocpp
diff --git a/samples/net/ocpp/src/main.c b/samples/net/ocpp/src/main.c
new file mode 100644
index 0000000000000..0c77c8eaef055
--- /dev/null
+++ b/samples/net/ocpp/src/main.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
+
+#define NO_OF_CONN 2
+K_KERNEL_STACK_ARRAY_DEFINE(cp_stk, NO_OF_CONN, 2 * 1024);
+K_SEM_DEFINE(run_app, 0, 1);
+
+static struct k_thread tinfo[NO_OF_CONN];
+static k_tid_t tid[NO_OF_CONN];
+static char idtag[NO_OF_CONN][25];
+
+#if defined(CONFIG_NET_DHCPV4)
+static struct net_mgmt_event_callback ocpp_dhcp_cb;
+
+static void ocpp_handler_cb(struct net_mgmt_event_callback *cb,
+ uint32_t mgmt_event, struct net_if *iface)
+{
+ char buf[NET_IPV4_ADDR_LEN];
+
+ LOG_INF("net mgr cb");
+ if (mgmt_event != NET_EVENT_IPV4_DHCP_BOUND) {
+ return;
+ }
+
+ LOG_INF("Your address: %s",
+ net_addr_ntop(AF_INET,
+ &iface->config.dhcpv4.requested_ip,
+ buf, sizeof(buf)));
+ LOG_INF("Lease time: %u seconds",
+ iface->config.dhcpv4.lease_time);
+ LOG_INF("Router: %s",
+ net_addr_ntop(AF_INET,
+ &iface->config.ip.ipv4->gw,
+ buf, sizeof(buf)));
+
+ k_sem_give(&run_app);
+}
+
+static int ocpp_test_dhcp_init(void)
+{
+ struct net_if *iface;
+
+ net_mgmt_init_event_callback(&ocpp_dhcp_cb, ocpp_handler_cb,
+ NET_EVENT_IPV4_DHCP_BOUND);
+
+ net_mgmt_add_event_callback(&ocpp_dhcp_cb);
+
+ iface = net_if_get_default();
+ if (!iface) {
+ LOG_ERR("wifi/eth interface not available");
+ return -1;
+ }
+
+ net_dhcpv4_start(iface);
+
+ return 0;
+}
+#endif
+
+static int ocpp_get_time_from_sntp(void)
+{
+ struct sntp_ctx ctx;
+ struct sntp_time stime;
+ struct sockaddr_in addr;
+ struct timespec tv;
+ int ret;
+
+ /* ipv4 */
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(123);
+ inet_pton(AF_INET, CONFIG_NET_SAMPLE_SNTP_SERVER, &addr.sin_addr);
+
+ ret = sntp_init(&ctx, (struct sockaddr *) &addr,
+ sizeof(struct sockaddr_in));
+ if (ret < 0) {
+ LOG_ERR("Failed to init SNTP IPv4 ctx: %d", ret);
+ return ret;
+ }
+
+ ret = sntp_query(&ctx, 60, &stime);
+ if (ret < 0) {
+ LOG_ERR("SNTP IPv4 request failed: %d", ret);
+ return ret;
+ }
+
+ LOG_INF("sntp succ since Epoch: %llu\n", stime.seconds);
+ tv.tv_sec = stime.seconds;
+ clock_settime(CLOCK_REALTIME, &tv);
+ sntp_close(&ctx);
+ return 0;
+}
+
+ZBUS_CHAN_DEFINE(ch_event, /* Name */
+ union ocpp_io_value,
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+ZBUS_SUBSCRIBER_DEFINE(cp_thread0, 5);
+ZBUS_SUBSCRIBER_DEFINE(cp_thread1, 5);
+struct zbus_observer *obs[NO_OF_CONN] = {&cp_thread0, &cp_thread1};
+
+static void ocpp_cp_entry(void *p1, void *p2, void *p3);
+static int user_notify_cb(enum ocpp_notify_reason reason,
+ union ocpp_io_value *io,
+ void *user_data)
+{
+ static int wh = 6 + NO_OF_CONN;
+ int idx;
+ int i;
+
+ switch (reason) {
+ case OCPP_USR_GET_METER_VALUE:
+ if (OCPP_OMM_ACTIVE_ENERGY_TO_EV == io->meter_val.mes) {
+ snprintf(io->meter_val.val, CISTR50, "%u",
+ wh + io->meter_val.id_con);
+
+ wh++;
+ LOG_DBG("mtr reading val %s con %d", io->meter_val.val,
+ io->meter_val.id_con);
+
+ return 0;
+ }
+ break;
+
+ case OCPP_USR_START_CHARGING:
+ if (io->start_charge.id_con < 0) {
+ for (i = 0; i < NO_OF_CONN; i++) {
+ if (!tid[i]) {
+ break;
+ }
+ }
+
+ if (i >= NO_OF_CONN) {
+ return -EBUSY;
+ }
+ idx = i;
+ } else {
+ idx = io->start_charge.id_con - 1;
+ }
+
+ if (!tid[idx]) {
+ LOG_INF("Remote start charging idtag %s connector %d\n",
+ idtag[idx], idx + 1);
+
+ strncpy(idtag[idx], io->start_charge.idtag,
+ sizeof(idtag[0]));
+
+ tid[idx] = k_thread_create(&tinfo[idx], cp_stk[idx],
+ sizeof(cp_stk[idx]), ocpp_cp_entry,
+ (void *)(idx + 1), idtag[idx],
+ obs[idx], 7, 0, K_NO_WAIT);
+
+ return 0;
+ }
+ break;
+
+ case OCPP_USR_STOP_CHARGING:
+ zbus_chan_pub(&ch_event, io, K_MSEC(100));
+ return 0;
+
+ case OCPP_USR_UNLOCK_CONNECTOR:
+ LOG_INF("unlock connector %d\n", io->unlock_con.id_con);
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+static void ocpp_cp_entry(void *p1, void *p2, void *p3)
+{
+ int ret;
+ int idcon = (uint32_t)p1;
+ char *idtag = (char *)p2;
+ struct zbus_observer *obs = (struct zbus_observer *)p3;
+ ocpp_session_handle_t sh = NULL;
+ enum ocpp_auth_status status;
+ const uint32_t timeout_ms = 500;
+
+ ret = ocpp_session_open(&sh);
+ if (ret) {
+ LOG_ERR("ocpp open ses idcon %d> res %d\n", idcon, ret);
+ return;
+ }
+
+ while (1) {
+ /* Avoid quick retry since authorization request is possible only
+ * after Bootnotification process (handled in lib) completed.
+ */
+
+ k_sleep(K_SECONDS(5));
+ ret = ocpp_authorize(sh,
+ idtag,
+ &status,
+ timeout_ms);
+ if (ret) {
+ LOG_ERR("ocpp auth %d> idcon %d status %d\n",
+ ret, idcon, status);
+ } else {
+ LOG_INF("ocpp auth %d> idcon %d status %d\n",
+ ret, idcon, status);
+ break;
+ }
+
+ }
+
+ if (status != OCPP_AUTH_ACCEPTED) {
+ LOG_ERR("ocpp start idcon %d> not authorized status %d\n",
+ idcon, status);
+ return;
+ }
+
+ ret = ocpp_start_transaction(sh, sys_rand32_get(), idcon, timeout_ms);
+ if (!ret) {
+ const struct zbus_channel *chan;
+ union ocpp_io_value io;
+
+ LOG_INF("ocpp start charging connector id %d\n", idcon);
+ memset(&io, 0xff, sizeof(io));
+
+ /* wait for stop charging event from main or remote CS */
+ zbus_chan_add_obs(&ch_event, obs, K_SECONDS(1));
+ do {
+ zbus_sub_wait(obs, &chan, K_FOREVER);
+ zbus_chan_read(chan, &io, K_SECONDS(1));
+
+ if (io.stop_charge.id_con == idcon) {
+ break;
+ }
+
+ } while (1);
+ }
+
+ ret = ocpp_stop_transaction(sh, sys_rand32_get(), timeout_ms);
+ if (ret) {
+ LOG_ERR("ocpp stop txn idcon %d> %d\n", idcon, ret);
+ return;
+ }
+
+ LOG_INF("ocpp stop charging connector id %d\n", idcon);
+ k_sleep(K_SECONDS(1));
+ ocpp_session_close(sh);
+ tid[idcon - 1] = NULL;
+ k_sleep(K_SECONDS(1));
+ k_thread_abort(k_current_get());
+}
+
+static int ocpp_getaddrinfo(char *server, int port, char **ip)
+{
+ int ret;
+ uint8_t retry = 5;
+ char addr_str[INET_ADDRSTRLEN];
+ struct sockaddr_storage b;
+ struct addrinfo *result = NULL;
+ struct addrinfo *addr;
+ struct addrinfo hints = {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_STREAM
+ };
+
+ LOG_INF("cs server %s %d", server, port);
+ do {
+ ret = getaddrinfo(server, NULL, &hints, &result);
+ if (ret == -EAGAIN) {
+ LOG_ERR("ERROR: getaddrinfo %d, rebind", ret);
+ k_sleep(K_SECONDS(1));
+ } else if (ret) {
+ LOG_ERR("ERROR: getaddrinfo failed %d", ret);
+ return ret;
+ }
+ } while (--retry && ret);
+
+ addr = result;
+ while (addr != NULL) {
+ /* IPv4 Address. */
+ if (addr->ai_addrlen == sizeof(struct sockaddr_in)) {
+ struct sockaddr_in *broker =
+ ((struct sockaddr_in *)&b);
+
+ broker->sin_addr.s_addr =
+ ((struct sockaddr_in *)addr->ai_addr)
+ ->sin_addr.s_addr;
+ broker->sin_family = AF_INET;
+ broker->sin_port = htons(port);
+
+ inet_ntop(AF_INET, &broker->sin_addr, addr_str,
+ sizeof(addr_str));
+
+ *ip = strdup(addr_str);
+ LOG_INF("IPv4 Address %s", addr_str);
+ break;
+ }
+
+ LOG_ERR("error: ai_addrlen = %u should be %u or %u",
+ (unsigned int)addr->ai_addrlen,
+ (unsigned int)sizeof(struct sockaddr_in),
+ (unsigned int)sizeof(struct sockaddr_in6));
+
+ addr = addr->ai_next;
+ }
+
+ /* Free the address. */
+ freeaddrinfo(result);
+
+ return 0;
+}
+
+int main(void)
+{
+ int ret;
+ int i;
+ char *ip = NULL;
+
+ struct ocpp_cp_info cpi = { "basic", "zephyr", .num_of_con = NO_OF_CONN };
+ struct ocpp_cs_info csi = {NULL,
+ "/steve/websocket/CentralSystemService/zephyr",
+ CONFIG_NET_SAMPLE_OCPP_PORT,
+ AF_INET};
+
+ printk("OCPP sample %s\n", CONFIG_BOARD);
+
+#if defined(CONFIG_NET_DHCPV4)
+ ocpp_test_dhcp_init();
+#endif
+
+ /* Wait for device dhcp ip recive */
+ k_sem_take(&run_app, K_FOREVER);
+
+ ret = ocpp_getaddrinfo(CONFIG_NET_SAMPLE_OCPP_SERVER, CONFIG_NET_SAMPLE_OCPP_PORT, &ip);
+ if (ret < 0) {
+ return ret;
+ }
+
+ csi.cs_ip = ip;
+
+ ocpp_get_time_from_sntp();
+
+ ret = ocpp_init(&cpi,
+ &csi,
+ user_notify_cb,
+ NULL);
+ if (ret) {
+ LOG_ERR("ocpp init failed %d\n", ret);
+ return ret;
+ }
+
+
+ /* Spawn threads for each connector */
+ for (i = 0; i < NO_OF_CONN; i++) {
+ snprintf(idtag[i], sizeof(idtag[0]), "ZepId%02d", i);
+
+ tid[i] = k_thread_create(&tinfo[i], cp_stk[i],
+ sizeof(cp_stk[i]),
+ ocpp_cp_entry, (void *)(i + 1),
+ idtag[i], obs[i], 7, 0, K_NO_WAIT);
+ }
+
+ /* Active charging session */
+ k_sleep(K_SECONDS(30));
+
+ /* Send stop charging to thread */
+ for (i = 0; i < NO_OF_CONN; i++) {
+ union ocpp_io_value io = {0};
+
+ io.stop_charge.id_con = i + 1;
+
+ zbus_chan_pub(&ch_event, &io, K_MSEC(100));
+ k_sleep(K_SECONDS(1));
+ }
+
+ /* User could trigger remote start/stop transcation from CS server */
+ k_sleep(K_SECONDS(1200));
+
+ return 0;
+}
diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt
index 02d43e2190af5..2ab4b4d5f3025 100644
--- a/subsys/net/lib/CMakeLists.txt
+++ b/subsys/net/lib/CMakeLists.txt
@@ -16,6 +16,7 @@ add_subdirectory_ifdef(CONFIG_NET_ZPERF zperf)
add_subdirectory_ifdef(CONFIG_NET_SHELL shell)
add_subdirectory_ifdef(CONFIG_NET_TRICKLE trickle)
add_subdirectory_ifdef(CONFIG_NET_DHCPV6 dhcpv6)
+add_subdirectory_ifdef(CONFIG_OCPP ocpp)
if (CONFIG_NET_DHCPV4 OR CONFIG_NET_DHCPV4_SERVER)
add_subdirectory(dhcpv4)
diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig
index b8c92030c4373..ac3b7f614f48f 100644
--- a/subsys/net/lib/Kconfig
+++ b/subsys/net/lib/Kconfig
@@ -23,6 +23,8 @@ source "subsys/net/lib/socks/Kconfig"
source "subsys/net/lib/sntp/Kconfig"
+source "subsys/net/lib/ocpp/Kconfig"
+
endmenu
menu "Network Libraries"
diff --git a/subsys/net/lib/ocpp/CMakeLists.txt b/subsys/net/lib/ocpp/CMakeLists.txt
new file mode 100644
index 0000000000000..5d7a237bd3bf6
--- /dev/null
+++ b/subsys/net/lib/ocpp/CMakeLists.txt
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: Apache-2.0
+
+zephyr_include_directories(.)
+
+zephyr_interface_library_named(ocpp)
+
+zephyr_library()
+
+zephyr_library_sources(
+ ocpp.c
+ core.c
+ key_mgmt.c
+ ocpp_j.c
+ ocpp_wamp_rpc.c
+)
+
+zephyr_library_link_libraries(ocpp)
diff --git a/subsys/net/lib/ocpp/Kconfig b/subsys/net/lib/ocpp/Kconfig
new file mode 100644
index 0000000000000..0fae04bf05095
--- /dev/null
+++ b/subsys/net/lib/ocpp/Kconfig
@@ -0,0 +1,83 @@
+# Copyright (c) 2024 Linumiz
+# SPDX-License-Identifier: Apache-2.0
+
+config OCPP
+ bool "Open Charge Point Protocol [EXPERIMENTAL]"
+ depends on JSONC
+ depends on WEBSOCKET_CLIENT
+ select EXPERIMENTAL
+ help
+ This option enables the open charge point protocol library
+
+if OCPP
+
+module=OCPP
+module-dep=NET_LOG
+module-str=Log level for OCPP
+module-help=Enables ocpp debug messages.
+source "subsys/net/Kconfig.template.log_config.net"
+
+config OCPP_INT_THREAD_STACKSIZE
+ int "OCPP internal thread stacksize"
+ default 4096
+ help
+ OCPP internal thread stacksize
+
+config OCPP_WSREADER_THREAD_STACKSIZE
+ int "OCPP websocket reader thread stacksize"
+ default 4096
+ help
+ OCPP websocket reader thread stacksize
+
+config OCPP_RECV_BUFFER_SIZE
+ int "OCPP websocket recive buffer size"
+ default 2048
+ help
+ OCPP websocket recive buffer size
+
+config OCPP_INTERNAL_MSGQ_CNT
+ int "OCPP internal message queue count"
+ default 10
+ help
+ Messages communication between websocket reader and internal
+ process thread
+
+config OCPP_MSG_JSON
+ bool "PDU message format as JSON"
+ default y
+ help
+ If enabled y, then the Charge Point and Central System PDU message
+ format as JSON
+
+config OCPP_PROFILE_SMART_CHARGE
+ bool "OCPP profile smart charging"
+ help
+ Enables the OCPP library to support the Smart Charging profile
+ functionality. Charge Point may support this profile as optional.
+
+config OCPP_PROFILE_REMOTE_TRIG
+ bool "OCPP profile remote trigger"
+ help
+ Enables the OCPP library to support the Remote Trigger
+ functionality. Charge Point may support this profile as optional.
+
+config OCPP_PROFILE_RESERVATION
+ bool "OCPP profile reservation"
+ help
+ Enables the OCPP library to support the Profile Reservation
+ functionality. Charge Point may support this profile as optional.
+
+config OCPP_PROFILE_LOCAL_AUTH_LIST
+ bool "OCPP profile local authorization list"
+ help
+ Enables the OCPP library to support the Local Authorization List
+ functionality. Stores IdTag and its validity information in the
+ persistent stroage. Charge Point may support this as optional.
+
+config OCPP_PROFILE_FIRMWARE_MGNT
+ bool "OCPP profile firmware management"
+ help
+ Enables the OCPP library to support the Firmware Management of
+ Charge Point as optional profile.
+
+endif # OCPP
diff --git a/subsys/net/lib/ocpp/core.c b/subsys/net/lib/ocpp/core.c
new file mode 100644
index 0000000000000..44d7c3579bd55
--- /dev/null
+++ b/subsys/net/lib/ocpp/core.c
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "ocpp_i.h"
+
+#define OCPP_PDU_TIMEOUT 2
+#define OCPP_USER_REQ_PDU_BUF 350
+#define FILL_METER_TABLE(_mes, _smes, _unit)[_mes] = {.mes = _mes, \
+ .smes = _smes, .unit = _unit}
+
+static struct {
+ enum ocpp_meter_measurand mes;
+ const char * const smes;
+ const char * const unit;
+} mtr_ref_table[] = {
+ FILL_METER_TABLE(OCPP_OMM_CURRENT_FROM_EV,
+ "Current.Export", "A"),
+ FILL_METER_TABLE(OCPP_OMM_CURRENT_TO_EV,
+ "Current.Import", "A"),
+ FILL_METER_TABLE(OCPP_OMM_CURRENT_MAX_OFFERED_TO_EV,
+ "Current.OfferedMaximum", "A"),
+ FILL_METER_TABLE(OCPP_OMM_ACTIVE_ENERGY_FROM_EV,
+ "Energy.Active.Export.Register", "Wh"),
+ FILL_METER_TABLE(OCPP_OMM_ACTIVE_ENERGY_TO_EV,
+ "Energy.Active.Import.Register", "Wh"),
+ FILL_METER_TABLE(OCPP_OMM_REACTIVE_ENERGY_FROM_EV,
+ "Energy.Reactive.Export.Register", "varh"),
+ FILL_METER_TABLE(OCPP_OMM_REACTIVE_ENERGY_TO_EV,
+ "Energy.Reactive.Import.Register", "varh"),
+ FILL_METER_TABLE(OCPP_OMM_ACTIVE_POWER_FROM_EV,
+ "Power.Active.Export", "W"),
+ FILL_METER_TABLE(OCPP_OMM_ACTIVE_POWER_TO_EV,
+ "Power.Active.Import", "W"),
+ FILL_METER_TABLE(OCPP_OMM_REACTIVE_POWER_FROM_EV,
+ "Power.Reactive.Export", "var"),
+ FILL_METER_TABLE(OCPP_OMM_REACTIVE_POWER_TO_EV,
+ "Power.Reactive.Import", "var"),
+ FILL_METER_TABLE(OCPP_OMM_POWERLINE_FREQ,
+ "Frequency", NULL),
+ FILL_METER_TABLE(OCPP_OMM_POWER_FACTOR,
+ "Power.Factor", NULL),
+ FILL_METER_TABLE(OCPP_OMM_POWER_MAX_OFFERED_TO_EV,
+ "Power.Offered", NULL),
+ FILL_METER_TABLE(OCPP_OMM_FAN_SPEED,
+ "RPM", "rpm"),
+ FILL_METER_TABLE(OCPP_OMM_CHARGING_PERCENT,
+ "SoCState", "Percent"),
+ FILL_METER_TABLE(OCPP_OMM_TEMPERATURE,
+ "Temperature", "Celsius"),
+ FILL_METER_TABLE(OCPP_OMM_VOLTAGE_AC_RMS,
+ "Voltage", "V")
+};
+
+int ocpp_boot_notification(ocpp_session_handle_t hndl,
+ struct ocpp_cp_info *cpi)
+{
+ int ret;
+ struct ocpp_session *sh = (struct ocpp_session *)hndl;
+ struct ocpp_info *ctx = sh->ctx;
+ struct ocpp_upstream_info *ui = &ctx->ui;
+ char *buf = ctx->pdu_buf;
+ ocpp_msg_fp_t fn;
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+
+ fn = ctx->cfn[PDU_BOOTNOTIFICATION];
+ sh->uid = fn(buf, sizeof(ctx->pdu_buf), sh, cpi);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ rmsg.sndlock = &ui->ws_sndlock;
+ rmsg.rspsig = &ui->ws_rspsig;
+
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+ if (!ret && sh->resp_status != BOOT_ACCEPTED) {
+ ret = -EAGAIN;
+ }
+
+ return ret;
+}
+
+int ocpp_get_configuration(enum ocpp_key key, struct ocpp_info *ctx, char *uid)
+{
+ int ret;
+ char tmp[32];
+ char *sval = NULL;
+ char *buf = ctx->pdu_buf;
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ union ocpp_keyval *kval;
+ ocpp_msg_fp_t fn;
+ bool is_rw;
+ enum ocpp_key_type ktype;
+
+ if (key >= OCPP_CFG_END) {
+ return -EINVAL;
+ }
+
+ ktype = ocpp_get_keyval_type(key);
+ is_rw = ocpp_is_key_rw(key);
+ kval = ocpp_get_key_val(key);
+
+ if (ktype < KEY_TYPE_STR) {
+ sval = tmp;
+ snprintk(tmp, sizeof(tmp), "%d", kval->ival);
+ } else {
+ sval = kval->str;
+ }
+
+ fn = ctx->cfn[PDU_GET_CONFIGURATION];
+ fn(buf, sizeof(ctx->pdu_buf), ocpp_get_key_literal(key), sval, is_rw, uid);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+ return ret;
+}
+
+int ocpp_change_configuration(char *skey, struct ocpp_info *ctx,
+ char *sval, char *uid)
+{
+ int ret = -EINVAL;
+ char *buf = ctx->pdu_buf;
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ union ocpp_keyval kval;
+ ocpp_msg_fp_t fn;
+ enum ocpp_key key;
+ const char *res = "Accepted";
+
+ key = ocpp_key_to_cfg(skey);
+ if (key < OCPP_CFG_END) {
+ enum ocpp_key_type ktype;
+
+ ktype = ocpp_get_keyval_type(key);
+
+ if (ktype < KEY_TYPE_STR) {
+ kval.ival = atoi(sval);
+ } else {
+ kval.str = sval;
+ }
+
+ ret = ocpp_update_cfg_val(key, &kval);
+ if (ret) {
+ res = "Rejected";
+ }
+ } else {
+ res = "NotSupported";
+ }
+
+ if (ret == 0) {
+ switch (key) {
+ case CFG_MTR_VAL_SAMPLE_INTERVAL:
+ if (atomic_get(&ctx->mtr_timer_ref_cnt) <= 0) {
+ break;
+ }
+
+ k_timer_start(&ctx->mtr_timer,
+ K_SECONDS(kval.ival),
+ K_SECONDS(kval.ival));
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ fn = ctx->cfn[PDU_CHANGE_CONFIGURATION];
+ fn(buf, sizeof(ctx->pdu_buf), res, uid);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+
+ return ret;
+}
+
+int ocpp_authorize(ocpp_session_handle_t hndl, char *idtag,
+ enum ocpp_auth_status *status,
+ uint32_t timeout_ms)
+{
+ int ret;
+ struct ocpp_session *sh = (struct ocpp_session *)hndl;
+ struct ocpp_info *ctx;
+ struct ocpp_upstream_info *ui;
+ char buf[OCPP_USER_REQ_PDU_BUF];
+ union ocpp_keyval *val;
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ ocpp_msg_fp_t fn;
+
+ if (!idtag || !status || !ocpp_session_is_valid(sh)) {
+ return -EINVAL;
+ }
+
+ ctx = sh->ctx;
+ if (ctx->state < CP_STATE_READY) {
+ return -EAGAIN;
+ }
+
+ if (ctx->is_cs_offline) {
+ val = ocpp_get_key_val(CFG_LOCAL_AUTH_OFFLINE);
+ if (val && !val->ival) {
+ return -EAGAIN;
+ }
+ }
+
+ strncpy(sh->idtag, idtag, sizeof(sh->idtag));
+ ui = &ctx->ui;
+ fn = ctx->cfn[PDU_AUTHORIZE];
+ sh->uid = fn(buf, sizeof(buf), sh);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ rmsg.sndlock = &ui->ws_sndlock;
+ rmsg.rspsig = &ui->ws_rspsig;
+ ret = ocpp_send_to_server(&rmsg, K_MSEC(timeout_ms));
+ if (ret < 0) {
+ return ret;
+ }
+
+ *status = sh->resp_status;
+ if (sh->resp_status == OCPP_AUTH_ACCEPTED) {
+ sh->is_active = true;
+ }
+
+ return 0;
+}
+
+int ocpp_heartbeat(ocpp_session_handle_t hndl)
+{
+ int ret;
+ struct ocpp_session *sh = (struct ocpp_session *)hndl;
+ struct ocpp_info *ctx = sh->ctx;
+ struct ocpp_upstream_info *ui = &ctx->ui;
+ ocpp_msg_fp_t fn;
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ char *buf = ctx->pdu_buf;
+
+ fn = ctx->cfn[PDU_HEARTBEAT];
+ sh->uid = fn(buf, sizeof(ctx->pdu_buf), sh);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ rmsg.sndlock = &ui->ws_sndlock;
+ rmsg.rspsig = &ui->ws_rspsig;
+
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+ if (!ret && sh->resp_status != BOOT_ACCEPTED) {
+ ret = -EAGAIN;
+ }
+
+ return ret;
+}
+
+int ocpp_start_transaction(ocpp_session_handle_t hndl,
+ int Wh,
+ uint8_t conn_id,
+ uint32_t timeout_ms)
+{
+ struct ocpp_session *sh = (struct ocpp_session *)hndl;
+ struct ocpp_info *ctx;
+ struct ocpp_upstream_info *ui;
+ char buf[OCPP_USER_REQ_PDU_BUF];
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ ocpp_msg_fp_t fn;
+ char utc[CISTR25] = {0};
+ int ret;
+
+ if (!ocpp_session_is_valid(sh) ||
+ conn_id <= 0) {
+ return -EINVAL;
+ }
+ ctx = sh->ctx;
+ sh->idcon = conn_id;
+ if (ctx->state < CP_STATE_READY) {
+ return -EAGAIN;
+ }
+
+ if (ctx->is_cs_offline) {
+ /* todo: fill meter reading
+ * server offline, accept start txn and save it in Q.
+ */
+ return 0;
+ }
+
+ fn = ctx->cfn[PDU_START_TRANSACTION];
+ ocpp_get_utc_now(utc);
+ sh->uid = fn(buf, sizeof(buf), sh, Wh, -1, utc);
+
+ ui = &ctx->ui;
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ rmsg.sndlock = &ui->ws_sndlock;
+ rmsg.rspsig = &ui->ws_rspsig;
+ ret = ocpp_send_to_server(&rmsg, K_MSEC(timeout_ms));
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (sh->resp_status != OCPP_AUTH_ACCEPTED) {
+ sh->is_active = false;
+ ret = -EACCES;
+ } else {
+ union ocpp_keyval *keyval;
+
+ keyval = ocpp_get_key_val(CFG_MTR_VAL_SAMPLE_INTERVAL);
+ if (!atomic_inc(&ctx->mtr_timer_ref_cnt)) {
+ k_timer_start(&ctx->mtr_timer,
+ K_SECONDS(keyval->ival),
+ K_SECONDS(keyval->ival));
+ }
+ }
+
+ return ret;
+}
+
+int ocpp_stop_transaction(ocpp_session_handle_t hndl,
+ int Wh,
+ uint32_t timeout_ms)
+{
+ struct ocpp_session *sh = (struct ocpp_session *)hndl;
+ struct ocpp_info *ctx;
+ struct ocpp_upstream_info *ui;
+ char buf[OCPP_USER_REQ_PDU_BUF];
+ char utc[CISTR25] = {0};
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ ocpp_msg_fp_t fn;
+ int ret;
+
+ if (!ocpp_session_is_valid(sh)) {
+ return -EINVAL;
+ }
+
+ ctx = sh->ctx;
+ if (ctx->state < CP_STATE_READY) {
+ return -EAGAIN;
+ }
+
+ sh->is_active = false;
+ if (atomic_dec(&ctx->mtr_timer_ref_cnt) <= 1) {
+ k_timer_stop(&ctx->mtr_timer);
+ }
+
+ if (ctx->is_cs_offline) {
+ /* todo: fill meter reading
+ * server offline, accept start txn and save it in Q.
+ */
+ return 0;
+ }
+
+ fn = ctx->cfn[PDU_STOP_TRANSACTION];
+ ocpp_get_utc_now(utc);
+ sh->uid = fn(buf, sizeof(buf), sh, Wh, NULL, utc);
+
+ ui = &ctx->ui;
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ rmsg.sndlock = &ui->ws_sndlock;
+ rmsg.rspsig = &ui->ws_rspsig;
+ ret = ocpp_send_to_server(&rmsg, K_MSEC(timeout_ms));
+
+ return ret;
+}
+
+int ocpp_remote_start_transaction(struct ocpp_info *ctx,
+ struct internal_msg *msg,
+ char *uid)
+{
+ char *buf = ctx->pdu_buf;
+ const char *resp = "Rejected";
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ ocpp_msg_fp_t fn;
+ int ret;
+
+ ret = k_msgq_put(ctx->msgq, msg, K_MSEC(100));
+ if (ret == 0) {
+ resp = "Accepted";
+ }
+
+ fn = ctx->cfn[PDU_REMOTE_START_TRANSACTION];
+ fn(buf, sizeof(ctx->pdu_buf), resp, uid);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+
+ return ret;
+}
+
+int ocpp_remote_stop_transaction(struct ocpp_info *ctx,
+ struct internal_msg *msg,
+ int idtxn, char *uid)
+{
+ char *buf = ctx->pdu_buf;
+ const char *resp = "Rejected";
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ ocpp_msg_fp_t fn;
+ sys_snode_t *node;
+ struct ocpp_session *sh;
+ bool is_found = false;
+ int ret;
+
+ k_mutex_lock(&ctx->ilock, K_FOREVER);
+ SYS_SLIST_FOR_EACH_NODE(&ctx->slist, node) {
+ sh = to_session(node);
+ if (sh->is_active &&
+ sh->idtxn == idtxn) {
+ is_found = true;
+ break;
+ }
+ }
+ k_mutex_unlock(&ctx->ilock);
+
+ if (is_found) {
+ msg->usr.stop_charge.id_con = sh->idcon;
+ ret = k_msgq_put(ctx->msgq, msg, K_MSEC(100));
+ if (ret == 0) {
+ resp = "Accepted";
+ }
+ }
+
+ fn = ctx->cfn[PDU_REMOTE_STOP_TRANSACTION];
+ fn(buf, sizeof(ctx->pdu_buf), resp, uid);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+
+ return ret;
+}
+
+int ocpp_unlock_connector(struct ocpp_info *ctx,
+ struct internal_msg *msg,
+ char *uid)
+{
+ char *buf = ctx->pdu_buf;
+ char *resp = "UnlockFailed";
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ ocpp_msg_fp_t fn;
+ int ret;
+
+ ret = k_msgq_put(ctx->msgq, msg, K_MSEC(100));
+ if (ret == 0) {
+ resp = "Unlocked";
+ }
+
+ fn = ctx->cfn[PDU_UNLOCK_CONNECTOR];
+ fn(buf, sizeof(ctx->pdu_buf), resp, uid);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+
+ return ret;
+}
+
+int ocpp_meter_values(ocpp_session_handle_t hndl,
+ enum ocpp_meter_measurand mes,
+ char *sval)
+{
+ struct ocpp_session *sh = (struct ocpp_session *)hndl;
+ struct ocpp_info *ctx = sh->ctx;
+ struct ocpp_upstream_info *ui = &ctx->ui;
+ char *buf = ctx->pdu_buf;
+ char utc[CISTR25] = {0};
+ struct ocpp_wamp_rpc_msg rmsg = {0};
+ ocpp_msg_fp_t fn;
+ int ret;
+
+ if (ctx->is_cs_offline) {
+ return -EAGAIN;
+ }
+
+ fn = ctx->cfn[PDU_METER_VALUES];
+ ocpp_get_utc_now(utc);
+ sh->uid = fn(buf, sizeof(ctx->pdu_buf), sh, utc, sval,
+ mtr_ref_table[mes].smes,
+ mtr_ref_table[mes].unit);
+
+ rmsg.ctx = ctx;
+ rmsg.msg = buf;
+ rmsg.msg_len = strlen(buf) + 1;
+ rmsg.sndlock = &ui->ws_sndlock;
+ rmsg.rspsig = &ui->ws_rspsig;
+ ret = ocpp_send_to_server(&rmsg, K_SECONDS(OCPP_PDU_TIMEOUT));
+
+ return ret;
+}
diff --git a/subsys/net/lib/ocpp/credentials/certificate.h b/subsys/net/lib/ocpp/credentials/certificate.h
new file mode 100644
index 0000000000000..5a32b4f5d469d
--- /dev/null
+++ b/subsys/net/lib/ocpp/credentials/certificate.h
@@ -0,0 +1,6 @@
+#define CA_CERTIFICATE_TAG 1
+#define TLS_PEER_HOSTNAME "localhost"
+
+static const unsigned char ca_certificate[] = {
+#include "ec.crt.inc"
+};
diff --git a/subsys/net/lib/ocpp/credentials/ec-priv.key b/subsys/net/lib/ocpp/credentials/ec-priv.key
new file mode 100644
index 0000000000000..782c19d3fa0f8
--- /dev/null
+++ b/subsys/net/lib/ocpp/credentials/ec-priv.key
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgwecn6Plv8ONZs2cF
+IIlr4B4xaoPYEMHm9mSG4esgQ2uhRANCAARVR4/COFJhHmmGdERod/1DhM5hBcq/
+xQHuUtxC1a977tMFCzINWTy155+/E8uj35FUhsLeFMoyGtgvKHKIBpgt
+-----END PRIVATE KEY-----
diff --git a/subsys/net/lib/ocpp/credentials/ec.crt b/subsys/net/lib/ocpp/credentials/ec.crt
new file mode 100644
index 0000000000000..8ee9a3598fe06
--- /dev/null
+++ b/subsys/net/lib/ocpp/credentials/ec.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBfDCCASOgAwIBAgIUW0crZnSm9CwlYmnYdDSohFSG5UwwCgYIKoZIzj0EAwIw
+FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDgyNzA5NDcxN1oXDTM0MDgyNTA5
+NDcxN1owFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D
+AQcDQgAEVUePwjhSYR5phnREaHf9Q4TOYQXKv8UB7lLcQtWve+7TBQsyDVk8teef
+vxPLo9+RVIbC3hTKMhrYLyhyiAaYLaNTMFEwHQYDVR0OBBYEFDi6b5XH5Z5d4cSe
+S5OVBHaWjB8SMB8GA1UdIwQYMBaAFDi6b5XH5Z5d4cSeS5OVBHaWjB8SMA8GA1Ud
+EwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgMEnVFWqIHRphQtWn5CbXomkH
+H/mDhf4ux5k55dmRRH8CIFwL0gYBrp26n0AsRSpVN1RroAt7M1MpCgEycVr3QNMQ
+-----END CERTIFICATE-----
diff --git a/subsys/net/lib/ocpp/key_mgmt.c b/subsys/net/lib/ocpp/key_mgmt.c
new file mode 100644
index 0000000000000..2031c42b48a6a
--- /dev/null
+++ b/subsys/net/lib/ocpp/key_mgmt.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "ocpp_i.h"
+
+#if defined(CONFIG_OCPP_PROFILE_SMART_CHARGE)
+#define CP_SC ",SmartCharging"
+#else
+#define CP_SC ""
+#endif
+
+#if defined(CONFIG_OCPP_PROFILE_REMOTE_TRIG)
+#define CP_RT ",RemoteTrigger"
+#else
+#define CP_RT ""
+#endif
+
+#if defined(CONFIG_OCPP_PROFILE_RESERVATION)
+#define CP_RE ",Reservation"
+#else
+#define CP_RE ""
+#endif
+
+#if defined(CONFIG_OCPP_PROFILE_LOCAL_AUTH_LIST)
+#define CP_LAL ",LocalAuthListManagement"
+#else
+#define CP_LAL ""
+#endif
+
+#if defined(CONFIG_OCPP_PROFILE_FIRMWARE_MGNT)
+#define CP_FM ",FirmwareManagement"
+#else
+#define CP_FM ""
+#endif
+
+#define CP_CORE "Core"
+
+struct ocpp_cfg_info {
+ enum ocpp_key_type type;
+ bool is_rw;
+ enum ocpp_key key;
+ char *skey;
+ union ocpp_keyval val;
+};
+
+#define FILL_KEY_TABLE(_type, _rw, _key, _skey, _val)[_key] = \
+ {.type = _type, .is_rw = _rw, .key = _key, .skey = _skey, .val = {_val}}
+
+static struct ocpp_cfg_info cached_key_cfg[OCPP_CFG_END] = {
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 1, CFG_ALLOW_OFFLINE_TX_FOR_UNKN_ID,
+ "AllowOfflineTxForUnknownId", false),
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 1, CFG_AUTH_CACHE_ENABLED,
+ "AuthorizationCacheEnabled", false),
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 0, CFG_AUTH_REMOTE_TX_REQ,
+ "AuthorizeRemoteTxRequests", true),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_BLINK_REPEAT, "BlinkRepeat", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_CLK_ALIGN_DATA_INTERVAL,
+ "ClockAlignedDataInterval", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_CONN_TIMEOUT,
+ "ConnectionTimeOut", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 0, CFG_GETCFG_MAX_KEY,
+ "GetConfigurationMaxKeys", 1),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_HEARTBEAT_INTERVAL,
+ "HeartbeatInterval", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_LIGHT_INTENSITY,
+ "LightIntensity", 0),
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 1, CFG_LOCAL_AUTH_OFFLINE,
+ "LocalAuthorizeOffline", 0),
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 1, CFG_LOCAL_PREAUTH,
+ "LocalPreAuthorize", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_MAX_ENERGYON_INVL_ID,
+ "MaxEnergyOnInvalidId", 0),
+ FILL_KEY_TABLE(KEY_TYPE_CSL, 1, CFG_MTR_VAL_ALGIN_DATA,
+ "MeterValuesAlignedData", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 0, CFG_MTR_VAL_ALGIN_DATA_MAXLEN,
+ "MeterValuesAlignedDataMaxLength", 1),
+ FILL_KEY_TABLE(KEY_TYPE_CSL, 1, CFG_MTR_VAL_SAMPLED_DATA,
+ "MeterValuesSampledData", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 0, CFG_MTR_VAL_SAMPLED_DATA_MAXLEN,
+ "MeterValuesSampledDataMaxLength", 1),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_MTR_VAL_SAMPLE_INTERVAL,
+ "MeterValueSampleInterval", 5),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_MIN_STATUS_DURATION,
+ "MinimumStatusDuration", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 0, CFG_NO_OF_CONNECTORS,
+ "NumberOfConnectors", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_REST_RETRIES,
+ "ResetRetries", 0),
+ FILL_KEY_TABLE(KEY_TYPE_CSL, 1, CFG_CONN_PHASE_ROT,
+ "ConnectorPhaseRotation", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 0, CFG_CONN_PHASE_ROT_MAXLEN,
+ "ConnectorPhaseRotationMaxLength", 1),
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 1, CFG_STOP_TXN_ON_EVSIDE_DISCON,
+ "StopTransactionOnEVSideDisconnect", 0),
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 1, CFG_STOP_TXN_ON_INVL_ID,
+ "StopTransactionOnInvalidId", 0),
+ FILL_KEY_TABLE(KEY_TYPE_CSL, 1, CFG_STOP_TXN_ALIGNED_DATA,
+ "StopTxnAlignedData", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 0, CFG_STOP_TXN_ALIGNED_DATA_MAXLEN,
+ "StopTxnAlignedDataMaxLength", 1),
+ FILL_KEY_TABLE(KEY_TYPE_CSL, 0, CFG_SUPPORTED_FEATURE_PROFILE,
+ "SupportedFeatureProfiles",
+ (int) CP_CORE CP_SC CP_RT CP_RE CP_LAL CP_FM),
+ FILL_KEY_TABLE(KEY_TYPE_CSL, 0, CFG_SUPPORTED_FEATURE_PROFILE_MAXLEN,
+ "SupportedFeatureProfilesMaxLength", 6),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_TXN_MSG_ATTEMPTS,
+ "TransactionMessageAttempts", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_TXN_MSG_RETRY_INTERVAL,
+ "TransactionMessageRetryInterval", 0),
+ FILL_KEY_TABLE(KEY_TYPE_BOOL, 1, CFG_UNLOCK_CONN_ON_EVSIDE_DISCON,
+ "UnlockConnectorOnEVSideDisconnect", 0),
+ FILL_KEY_TABLE(KEY_TYPE_INT, 1, CFG_WEBSOCK_PING_INTERVAL,
+ "WebSocketPingInterval", 0),
+
+ /* optional */
+};
+
+enum ocpp_key_type ocpp_get_keyval_type(enum ocpp_key key)
+{
+ return cached_key_cfg[key].type;
+}
+
+enum ocpp_key ocpp_key_to_cfg(const char *skey)
+{
+ int i;
+ struct ocpp_cfg_info *cfg = cached_key_cfg;
+
+ for (i = 0; i < OCPP_CFG_END; i++) {
+ if (!strncmp(cfg[i].skey, skey, strlen(cfg[i].skey))) {
+ break;
+ }
+ }
+
+ return i;
+}
+
+bool ocpp_is_key_rw(enum ocpp_key key)
+{
+ return cached_key_cfg[key].is_rw;
+}
+
+union ocpp_keyval *ocpp_get_key_val(enum ocpp_key key)
+{
+ if (key >= OCPP_CFG_END) {
+ return NULL;
+ }
+
+ return &cached_key_cfg[key].val;
+}
+
+char *ocpp_get_key_literal(enum ocpp_key key)
+{
+ if (key >= OCPP_CFG_END) {
+ return NULL;
+ }
+
+ return cached_key_cfg[key].skey;
+}
+
+int ocpp_set_cfg_val(enum ocpp_key key, union ocpp_keyval *val)
+{
+ enum ocpp_key_type type;
+ struct ocpp_cfg_info *key_cfg;
+
+ key_cfg = &cached_key_cfg[key];
+ type = ocpp_get_keyval_type(key);
+ if (type < KEY_TYPE_STR) {
+ key_cfg->val.ival = val->ival;
+ } else {
+ if (key_cfg->val.str) {
+ free(key_cfg->val.str);
+ }
+ key_cfg->val.str = strdup(val->str);
+ }
+
+ return 0;
+}
+
+int ocpp_update_cfg_val(enum ocpp_key key, union ocpp_keyval *val)
+{
+
+ if (key >= OCPP_CFG_END ||
+ !cached_key_cfg[key].is_rw) {
+ return -EINVAL;
+ }
+
+ return ocpp_set_cfg_val(key, val);
+}
diff --git a/subsys/net/lib/ocpp/ocpp.c b/subsys/net/lib/ocpp/ocpp.c
new file mode 100644
index 0000000000000..f6113cf2f93d2
--- /dev/null
+++ b/subsys/net/lib/ocpp/ocpp.c
@@ -0,0 +1,772 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "ocpp_i.h"
+#include
+#include
+
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
+#include "credentials/certificate.h"
+#endif
+
+LOG_MODULE_REGISTER(ocpp, CONFIG_OCPP_LOG_LEVEL);
+
+#define OCPP_UPSTREAM_PRIORITY 7
+#define OCPP_WS_TIMEOUT 5000
+#define OCPP_INTERNAL_MSG_SIZE (sizeof(struct internal_msg))
+#define FILL_PDU_TABLE(_pdu, _spdu)[_pdu] = {_pdu, _spdu, }
+
+struct ocpp_msg_table {
+ enum ocpp_pdu_msg pdu;
+ char *spdu;
+};
+
+static K_THREAD_STACK_DEFINE(ocpp_int_handler_stack, CONFIG_OCPP_INT_THREAD_STACKSIZE);
+static K_THREAD_STACK_DEFINE(ocpp_wsreader_stack, CONFIG_OCPP_WSREADER_THREAD_STACKSIZE);
+
+
+K_MSGQ_DEFINE(ocpp_iq, OCPP_INTERNAL_MSG_SIZE, CONFIG_OCPP_INTERNAL_MSGQ_CNT, sizeof(uint32_t));
+
+struct ocpp_info *gctx;
+
+static struct ocpp_msg_table pdu_msg_table[] = {
+ FILL_PDU_TABLE(PDU_BOOTNOTIFICATION, "BootNotification"),
+ FILL_PDU_TABLE(PDU_AUTHORIZE, "Authorize"),
+ FILL_PDU_TABLE(PDU_START_TRANSACTION, "StartTransaction"),
+ FILL_PDU_TABLE(PDU_STOP_TRANSACTION, "StopTransaction"),
+ FILL_PDU_TABLE(PDU_HEARTBEAT, "Heartbeat"),
+ FILL_PDU_TABLE(PDU_METER_VALUES, "MeterValues"),
+ FILL_PDU_TABLE(PDU_CLEAR_CACHE, "ClearCache"),
+ FILL_PDU_TABLE(PDU_REMOTE_START_TRANSACTION, "RemoteStartTransaction"),
+ FILL_PDU_TABLE(PDU_REMOTE_STOP_TRANSACTION, "RemoteStopTransaction"),
+ FILL_PDU_TABLE(PDU_GET_CONFIGURATION, "GetConfiguration"),
+ FILL_PDU_TABLE(PDU_CHANGE_CONFIGURATION, "ChangeConfiguration"),
+ FILL_PDU_TABLE(PDU_CHANGE_AVAILABILITY, "ChangeAvailability"),
+ FILL_PDU_TABLE(PDU_UNLOCK_CONNECTOR, "UnlockConnector"),
+ FILL_PDU_TABLE(PDU_RESET, "Reset"),
+};
+
+const char *ocpp_get_pdu_literal(enum ocpp_pdu_msg pdu)
+{
+ if (pdu >= PDU_MSG_END) {
+ return "";
+ }
+
+ return pdu_msg_table[pdu].spdu;
+}
+
+int ocpp_find_pdu_from_literal(const char *msg)
+{
+ struct ocpp_msg_table *mt = pdu_msg_table;
+ int i;
+
+ for (i = 0; i < PDU_MSG_END; i++) {
+ if (!strncmp(mt[i].spdu, msg, strlen(mt[i].spdu))) {
+ break;
+ }
+ }
+
+ return i;
+}
+
+void ocpp_get_utc_now(char utc[CISTR25])
+{
+ struct timespec tv;
+ struct tm htime = {0};
+
+ gettimeofday(&tv, NULL);
+ gmtime_r(&tv.tv_sec, &htime);
+
+ snprintk(utc, CISTR25, "%04d-%02d-%02dT%02d:%02d:%02dZ",
+ htime.tm_year + 1900,
+ htime.tm_mon + 1,
+ htime.tm_mday,
+ htime.tm_hour,
+ htime.tm_min,
+ htime.tm_sec);
+}
+
+bool ocpp_session_is_valid(struct ocpp_session *sh)
+{
+ sys_snode_t *node;
+ struct ocpp_session *lsh;
+ bool is_found = false;
+
+ if (!sh) {
+ return is_found;
+ }
+
+ k_mutex_lock(&gctx->ilock, K_FOREVER);
+ SYS_SLIST_FOR_EACH_NODE(&gctx->slist, node) {
+ lsh = to_session(node);
+ if (lsh == sh) {
+ is_found = true;
+ break;
+ }
+ }
+ k_mutex_unlock(&gctx->ilock);
+
+ return is_found;
+}
+
+static int ocpp_ws_connect_cb(int ws_sock, struct http_request *req,
+ void *user_data)
+{
+ struct ocpp_info *ctx = user_data;
+
+ ctx->is_cs_offline = false;
+ return 0;
+}
+
+static int ocpp_connect_to_cs(struct ocpp_info *ctx)
+{
+ int ret;
+ struct websocket_request config = {0};
+ struct ocpp_upstream_info *ui = &ctx->ui;
+ struct sockaddr addr4;
+ struct sockaddr_in6 addr6;
+ struct sockaddr *addr;
+ int addr_size;
+
+ if (ui->csi.sa_family == AF_INET) {
+ addr = &addr4;
+ addr_size = sizeof(addr4);
+ addr->sa_family = ui->csi.sa_family;
+ net_sin(addr)->sin_port = htons(ui->csi.port);
+ zsock_inet_pton(addr->sa_family, ui->csi.cs_ip,
+ &net_sin(addr)->sin_addr);
+ } else {
+ addr = (struct sockaddr *)&addr6;
+ addr_size = sizeof(addr6);
+ addr->sa_family = ui->csi.sa_family;
+ net_sin6(addr)->sin6_port = htons(ui->csi.port);
+ zsock_inet_pton(addr->sa_family, ui->csi.cs_ip,
+ &net_sin6(addr)->sin6_addr);
+ }
+
+ ret = zsock_connect(ui->tcpsock, addr, addr_size);
+ if (ret < 0 && errno != EALREADY && errno != EISCONN) {
+ LOG_ERR("tcp socket connect fail %d %d", ret, errno);
+ return ret;
+ }
+
+ if (ui->wssock >= 0) {
+ websocket_disconnect(ui->wssock);
+ }
+
+ if (ui->wssock < 0) {
+ char buf[128];
+ char const *optional_hdr[] = {
+ "Sec-WebSocket-Protocol: ocpp1.6\r\n",
+ NULL };
+
+ snprintk(buf, sizeof(buf), "%s:%u", ui->csi.cs_ip, ui->csi.port);
+ config.url = ui->csi.ws_url;
+ config.host = buf;
+ config.tmp_buf = ui->wsrecv_buf;
+ config.tmp_buf_len = sizeof(ui->wsrecv_buf);
+ config.cb = ocpp_ws_connect_cb;
+ config.optional_headers = optional_hdr;
+
+ ret = websocket_connect(ui->tcpsock, &config,
+ OCPP_WS_TIMEOUT, ctx);
+ if (ret < 0) {
+ LOG_ERR("Websocket connect fail %d", ret);
+ return ret;
+ }
+ ui->wssock = ret;
+ }
+ LOG_DBG("WS connect success %d", ui->wssock);
+ return 0;
+}
+
+static inline void bootnotification_free_resource(struct ocpp_cp_info *cpi)
+{
+ free(cpi->model);
+ free(cpi->vendor);
+ free(cpi->sl_no);
+ free(cpi->box_sl_no);
+ free(cpi->fw_ver);
+ free(cpi->iccid);
+ free(cpi->imsi);
+ free(cpi->meter_sl_no);
+ free(cpi->meter_type);
+ k_free(cpi);
+}
+
+static void ocpp_internal_handler(void *p1, void *p2, void *p3)
+{
+ struct ocpp_info *ctx = p1;
+ struct ocpp_cp_info *cpi = p2;
+ ocpp_session_handle_t sh;
+ struct internal_msg msg = {0};
+ int ret;
+ int i;
+
+ /* open internal session */
+ ocpp_session_open(&sh);
+
+ while (!k_msgq_get(ctx->msgq, &msg, K_FOREVER)) {
+ switch (msg.msgtype) {
+ case PDU_BOOTNOTIFICATION:
+
+ if (!ctx->is_cs_offline && cpi) {
+ ret = ocpp_boot_notification(sh, cpi);
+ if (!ret) {
+ bootnotification_free_resource(cpi);
+ cpi = NULL;
+ ctx->state = CP_STATE_READY;
+ }
+ }
+
+ k_timer_start(&ctx->hb_timer,
+ K_SECONDS(ctx->hb_sec),
+ K_NO_WAIT);
+ break;
+
+ case PDU_METER_VALUES:
+ union ocpp_io_value io;
+ sys_snode_t *node;
+ struct ocpp_session *lsh;
+
+ /* list of all active session */
+ k_mutex_lock(&ctx->ilock, K_FOREVER);
+ SYS_SLIST_FOR_EACH_NODE(&ctx->slist, node) {
+ lsh = to_session(node);
+ if (lsh == sh ||
+ !lsh->is_active) {
+ continue;
+ }
+
+ k_mutex_lock(&lsh->slock, K_FOREVER);
+ k_mutex_unlock(&ctx->ilock);
+ io.meter_val.id_con = lsh->idcon;
+ for (i = 0; i < OCPP_OMM_END; i++) {
+ io.meter_val.mes = i;
+ ret = ctx->cb(OCPP_USR_GET_METER_VALUE,
+ &io, ctx->user_data);
+ if (ret) {
+ continue;
+ }
+ ocpp_meter_values(lsh, i,
+ io.meter_val.val);
+ }
+ k_mutex_lock(&ctx->ilock, K_FOREVER);
+ k_mutex_unlock(&lsh->slock);
+ }
+ k_mutex_unlock(&ctx->ilock);
+ break;
+
+ case PDU_HEARTBEAT:
+ ocpp_heartbeat(sh);
+ /* adjust local time with cs time ! */
+ k_timer_start(&ctx->hb_timer,
+ K_SECONDS(ctx->hb_sec),
+ K_NO_WAIT);
+ break;
+
+ case PDU_CS_ONLINE:
+ /* check offline msg and do nothing on empty
+ * else read msg and send to server
+ */
+ break;
+
+ case PDU_REMOTE_START_TRANSACTION:
+ msg.msgtype = OCPP_USR_START_CHARGING;
+ ctx->cb(msg.msgtype, &msg.usr, ctx->user_data);
+ break;
+
+ case PDU_REMOTE_STOP_TRANSACTION:
+ msg.msgtype = OCPP_USR_STOP_CHARGING;
+ ctx->cb(msg.msgtype, &msg.usr, ctx->user_data);
+ break;
+
+ case PDU_UNLOCK_CONNECTOR:
+ msg.msgtype = OCPP_USR_UNLOCK_CONNECTOR;
+ ctx->cb(msg.msgtype, &msg.usr, ctx->user_data);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static int ocpp_process_server_msg(struct ocpp_info *ctx)
+{
+ ocpp_msg_fp_t fn;
+ struct ocpp_session *sh = NULL;
+ struct ocpp_upstream_info *ui = &ctx->ui;
+ char *buf, *tmp;
+ char uid[128];
+ int ret, idtxn, pdu, i;
+ bool is_rsp;
+ struct internal_msg msg;
+
+ ret = parse_rpc_msg(ui->recv_buf, sizeof(ui->recv_buf),
+ uid, sizeof(uid), &pdu, &is_rsp);
+ if (ret) {
+ return ret;
+ }
+
+ if (is_rsp) {
+ buf = strtok_r(uid, "-", &tmp);
+ sh = (struct ocpp_session *) atoi(buf);
+
+ buf = strtok_r(NULL, "-", &tmp);
+ pdu = atoi(buf);
+
+ if (!ocpp_session_is_valid(sh)) {
+ sh = NULL;
+ }
+ }
+
+ if (pdu >= PDU_MSG_END) {
+ return -EINVAL;
+ }
+
+ fn = ctx->pfn[pdu];
+
+ switch (pdu) {
+ char skey[CISTR50];
+
+ case PDU_BOOTNOTIFICATION:
+ struct boot_notif binfo;
+
+ ret = fn(ui->recv_buf, &binfo);
+ if (!ret && sh) {
+ sh->resp_status = binfo.status;
+ ctx->hb_sec = binfo.interval;
+ }
+ break;
+
+ case PDU_AUTHORIZE:
+ case PDU_STOP_TRANSACTION:
+ struct ocpp_idtag_info idinfo = {0};
+
+ if (!sh) {
+ break;
+ }
+
+ ret = fn(ui->recv_buf, &idinfo);
+ if (!ret) {
+ sh->resp_status = idinfo.auth_status;
+ }
+ break;
+
+ case PDU_START_TRANSACTION:
+ if (!sh) {
+ break;
+ }
+
+ ret = fn(ui->recv_buf, &idtxn, &idinfo);
+ if (!ret) {
+ sh->idtxn = idtxn;
+ sh->resp_status = idinfo.auth_status;
+ }
+ break;
+
+ case PDU_GET_CONFIGURATION:
+ memset(skey, 0, sizeof(skey));
+
+ ret = fn(ui->recv_buf, skey);
+ if (ret) {
+ break;
+ }
+
+ if (*skey) {
+ enum ocpp_key key;
+
+ key = ocpp_key_to_cfg(skey);
+ ocpp_get_configuration(key, ctx, uid);
+ } else {
+ for (i = 0; i < OCPP_CFG_END; i++) {
+ ocpp_get_configuration(i, ctx, uid);
+ }
+ }
+
+ break;
+
+ case PDU_CHANGE_CONFIGURATION:
+ char sval[CISTR500] = {0};
+
+ memset(skey, 0, sizeof(skey));
+ ret = fn(ui->recv_buf, skey, sval);
+ if (ret) {
+ break;
+ }
+
+ ocpp_change_configuration(skey, ctx, sval, uid);
+ break;
+
+ case PDU_HEARTBEAT:
+ /* todo : sync time */
+ break;
+
+ case PDU_REMOTE_START_TRANSACTION:
+ memset(&msg, 0, OCPP_INTERNAL_MSG_SIZE);
+ msg.msgtype = PDU_REMOTE_START_TRANSACTION;
+
+ ret = fn(ui->recv_buf, &msg.usr.start_charge.id_con,
+ msg.usr.start_charge.idtag);
+ if (ret) {
+ break;
+ }
+
+ ocpp_remote_start_transaction(ctx, &msg, uid);
+ break;
+
+ case PDU_REMOTE_STOP_TRANSACTION:
+ memset(&msg, 0, OCPP_INTERNAL_MSG_SIZE);
+ msg.msgtype = PDU_REMOTE_STOP_TRANSACTION;
+
+ ret = fn(ui->recv_buf, &idtxn);
+ if (ret) {
+ break;
+ }
+
+ ocpp_remote_stop_transaction(ctx, &msg, idtxn, uid);
+ break;
+
+ case PDU_UNLOCK_CONNECTOR:
+ memset(&msg, 0, OCPP_INTERNAL_MSG_SIZE);
+ msg.msgtype = PDU_UNLOCK_CONNECTOR;
+
+ ret = fn(ui->recv_buf, &msg.usr.unlock_con.id_con);
+ if (ret) {
+ break;
+ }
+
+ ocpp_unlock_connector(ctx, &msg, uid);
+ break;
+
+ case PDU_METER_VALUES:
+ default:
+ break;
+ }
+
+ if (is_rsp) {
+ k_poll_signal_raise(&ui->ws_rspsig, 0);
+ }
+ return 0;
+}
+
+#define TCP_CONNECT_AFTER 20 /* fixme arbitrary */
+static void ocpp_wsreader(void *p1, void *p2, void *p3)
+{
+ struct ocpp_info *ctx = p1;
+ struct ocpp_upstream_info *ui = &ctx->ui;
+ struct zsock_pollfd tcpfd;
+ uint8_t ret;
+ uint8_t retry_cnt = 0;
+
+ ctx->is_cs_offline = true;
+ tcpfd.fd = ui->tcpsock;
+
+ tcpfd.events = ZSOCK_POLLIN | ZSOCK_POLLERR
+ | ZSOCK_POLLHUP | ZSOCK_POLLNVAL;
+
+ while (1) {
+
+ if (ctx->is_cs_offline &&
+ !(retry_cnt++ % TCP_CONNECT_AFTER)) {
+ k_mutex_lock(&ctx->ilock, K_FOREVER);
+ ocpp_connect_to_cs(ctx);
+ k_mutex_unlock(&ctx->ilock);
+ }
+
+ ret = zsock_poll(&tcpfd, 1, 200);
+ if (ret <= 0) {
+ continue;
+ }
+
+ if ((tcpfd.revents & ZSOCK_POLLERR) ||
+ (tcpfd.revents & ZSOCK_POLLHUP) ||
+ (tcpfd.revents & ZSOCK_POLLNVAL)) {
+ LOG_ERR("poll err %d", tcpfd.revents);
+ ctx->is_cs_offline = true;
+ continue;
+ }
+
+ if (tcpfd.revents & ZSOCK_POLLIN) {
+ struct ocpp_wamp_rpc_msg rcv = {0};
+
+ rcv.ctx = ctx;
+ rcv.msg = ui->recv_buf;
+ rcv.sndlock = &ui->ws_sndlock;
+ rcv.msg_len = sizeof(ui->recv_buf);
+
+ memset(ui->recv_buf, 0, rcv.msg_len);
+ ret = ocpp_receive_from_server(&rcv, 200);
+ if (ret < 0) {
+ continue;
+ }
+
+ ocpp_process_server_msg(ctx);
+ }
+ }
+}
+
+
+int ocpp_upstream_init(struct ocpp_info *ctx, struct ocpp_cs_info *csi)
+{
+ struct ocpp_upstream_info *ui = &ctx->ui;
+ ui->csi.ws_url = strdup(csi->ws_url);
+ ui->csi.cs_ip = strdup(csi->cs_ip);
+ ui->csi.port = csi->port;
+ ui->csi.sa_family = csi->sa_family;
+
+ LOG_INF("upstream init");
+
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
+ int ret = tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, ca_certificate, sizeof(ca_certificate));
+
+ if (ret < 0)
+ {
+ LOG_ERR("Failed to register CA certificate: %d", ret);
+ return ret;
+ }
+
+ ui->tcpsock = zsock_socket(csi->sa_family, SOCK_STREAM, IPPROTO_TLS_1_3);
+ if (ui->tcpsock < 0)
+ {
+ LOG_ERR("Failed to create TLS socket: %d", errno);
+ return -errno;
+ }
+
+ sec_tag_t sec_tag_list[] = {CA_CERTIFICATE_TAG};
+
+ ret = zsock_setsockopt(ui->tcpsock, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_list, sizeof(sec_tag_list));
+
+ if (ret < 0)
+ {
+ LOG_ERR("Failed to set TLS_SEC_TAG_LIST: %d", -errno);
+ goto fail;
+ }
+
+ ret = zsock_setsockopt(ui->tcpsock, SOL_TLS, TLS_HOSTNAME, TLS_PEER_HOSTNAME, sizeof(TLS_PEER_HOSTNAME));
+ if (ret < 0)
+ {
+ LOG_ERR("Failed to set TLS_HOSTNAME: %d", -errno);
+ goto fail;
+ }
+#else
+ ui->tcpsock = zsock_socket(csi->sa_family, SOCK_STREAM, IPPROTO_TCP);
+
+ if (ui->tcpsock < 0)
+ {
+ LOG_ERR("Failed to create TCP socket: %d", -errno);
+ return -errno;
+ }
+#endif
+
+ k_mutex_init(&ui->ws_sndlock);
+ k_poll_signal_init(&ui->ws_rspsig);
+ ui->wssock = -1;
+
+ websocket_init();
+
+ k_thread_create(&ui->tinfo, ocpp_wsreader_stack,
+ CONFIG_OCPP_WSREADER_THREAD_STACKSIZE,
+ ocpp_wsreader, ctx, NULL, NULL,
+ OCPP_UPSTREAM_PRIORITY, 0, K_MSEC(100));
+
+ return 0;
+
+fail:
+ if (ui->tcpsock >= 0)
+ {
+ zsock_close(ui->tcpsock);
+ ui->tcpsock = -1;
+ }
+ return -errno;
+}
+
+
+static void timer_heartbeat_cb(struct k_timer *t)
+{
+ struct ocpp_info *ctx;
+ struct internal_msg msg = {PDU_HEARTBEAT};
+
+ ctx = k_timer_user_data_get(t);
+ if (ctx->state == CP_STATE_BOOTNOTIF) {
+ msg.msgtype = PDU_BOOTNOTIFICATION;
+ }
+
+ k_msgq_put(ctx->msgq, &msg, K_NO_WAIT);
+}
+
+static void timer_meter_cb(struct k_timer *t)
+{
+ struct ocpp_info *ctx;
+ struct internal_msg msg = {PDU_METER_VALUES};
+
+ ctx = k_timer_user_data_get(t);
+
+ k_msgq_put(ctx->msgq, &msg, K_NO_WAIT);
+}
+
+static inline void bootnotification_fill_resource(struct ocpp_cp_info *cp,
+ struct ocpp_cp_info *cpi,
+ struct ocpp_info *ctx)
+{
+ struct k_timer *hbt = &ctx->hb_timer;
+
+ cp->model = strdup(cpi->model);
+ cp->vendor = strdup(cpi->vendor);
+ if (cp->sl_no) {
+ cp->sl_no = strdup(cpi->sl_no);
+ }
+
+ if (cp->box_sl_no) {
+ cp->box_sl_no = strdup(cpi->box_sl_no);
+ }
+
+ if (cp->fw_ver) {
+ cp->fw_ver = strdup(cpi->fw_ver);
+ }
+
+ if (cp->iccid) {
+ cp->iccid = strdup(cpi->iccid);
+ }
+
+ if (cp->imsi) {
+ cp->imsi = strdup(cpi->imsi);
+ }
+
+ if (cp->meter_sl_no) {
+ cp->meter_sl_no = strdup(cpi->meter_sl_no);
+ }
+
+ if (cp->meter_type) {
+ cp->meter_type = strdup(cpi->meter_type);
+ }
+
+ ctx->state = CP_STATE_BOOTNOTIF;
+ ctx->hb_sec = 10;
+ k_timer_start(hbt, K_SECONDS(1), K_NO_WAIT);
+}
+
+int ocpp_session_open(ocpp_session_handle_t *hndl)
+{
+ struct ocpp_session *sh;
+
+ if (!hndl) {
+ return -EINVAL;
+ }
+
+ sh = (struct ocpp_session *)k_calloc(1, sizeof(struct ocpp_session));
+ if (!sh) {
+ return -ENOMEM;
+ }
+
+ sh->is_active = false;
+ sh->idcon = INVALID_CONN_ID;
+ sh->idtxn = INVALID_TXN_ID;
+ sh->ctx = gctx;
+ k_mutex_init(&sh->slock);
+
+ k_mutex_lock(&gctx->ilock, K_FOREVER);
+ sys_slist_append(&gctx->slist, &sh->node);
+ k_mutex_unlock(&gctx->ilock);
+
+ *hndl = (void *)sh;
+
+ return 0;
+}
+
+void ocpp_session_close(ocpp_session_handle_t hndl)
+{
+ bool is_removed;
+ struct ocpp_session *sh = (struct ocpp_session *)hndl;
+
+ if (!sh) {
+ return;
+ }
+
+ k_mutex_lock(&gctx->ilock, K_FOREVER);
+ is_removed = sys_slist_find_and_remove(&gctx->slist, &sh->node);
+ k_mutex_unlock(&gctx->ilock);
+
+ if (is_removed) {
+ k_mutex_lock(&sh->slock, K_FOREVER);
+ k_free(sh);
+ }
+}
+
+int ocpp_init(struct ocpp_cp_info *cpi,
+ struct ocpp_cs_info *csi,
+ ocpp_user_notify_callback_t cb,
+ void *user_data)
+{
+ struct ocpp_info *ctx;
+ struct ocpp_cp_info *cp;
+ union ocpp_keyval val;
+ int ret;
+
+ if (!cpi ||
+ !cpi->model ||
+ !cpi->vendor ||
+ !csi ||
+ !csi->cs_ip ||
+ !csi->ws_url ||
+ !cb ||
+ cpi->num_of_con < 1) {
+ return -EINVAL;
+ }
+
+ ctx = (struct ocpp_info *)k_calloc(1, sizeof(struct ocpp_info));
+ if (!ctx) {
+ return -ENOMEM;
+ }
+
+ gctx = ctx;
+ k_mutex_init(&ctx->ilock);
+ sys_slist_init(&ctx->slist);
+ ocpp_parser_init(&ctx->cfn, &ctx->pfn);
+
+ ctx->state = CP_STATE_INIT;
+ ctx->msgq = &ocpp_iq;
+
+ k_timer_init(&ctx->hb_timer, timer_heartbeat_cb, NULL);
+ k_timer_user_data_set(&ctx->hb_timer, ctx);
+ k_timer_init(&ctx->mtr_timer, timer_meter_cb, NULL);
+ k_timer_user_data_set(&ctx->mtr_timer, ctx);
+ atomic_set(&ctx->mtr_timer_ref_cnt, 0);
+
+ ctx->user_data = user_data;
+ ctx->cb = cb;
+ val.ival = cpi->num_of_con;
+ ocpp_set_cfg_val(CFG_NO_OF_CONNECTORS, &val);
+
+ ret = ocpp_upstream_init(ctx, csi);
+ if (ret) {
+ LOG_ERR("ocpp upstream init fail %d", ret);
+ goto out;
+ }
+
+ /* free after success of bootnotification to CS */
+ cp = (struct ocpp_cp_info *)k_calloc(1, sizeof(struct ocpp_cp_info));
+ if (!cp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ bootnotification_fill_resource(cp, cpi, ctx);
+
+ k_thread_create(&ctx->tinfo, ocpp_int_handler_stack,
+ CONFIG_OCPP_INT_THREAD_STACKSIZE,
+ ocpp_internal_handler, ctx,
+ cp, NULL, OCPP_UPSTREAM_PRIORITY,
+ 0, K_NO_WAIT);
+
+ LOG_INF("ocpp init success");
+ return 0;
+
+out:
+ k_free(ctx);
+ return ret;
+}
diff --git a/subsys/net/lib/ocpp/ocpp_i.h b/subsys/net/lib/ocpp/ocpp_i.h
new file mode 100644
index 0000000000000..5f56168be6d9c
--- /dev/null
+++ b/subsys/net/lib/ocpp/ocpp_i.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2023 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef __OCPP_I_
+#define __OCPP_I_
+
+#include "string.h"
+#include
+#include
+#include
+#include
+#include
+
+/* case-insensitive */
+#define CISTR20 20
+#define CISTR25 25
+#define CISTR500 500
+
+#define INVALID_CONN_ID ((uint8_t) -1)
+#define INVALID_TXN_ID ((int) -1)
+
+#define to_session(ptr) CONTAINER_OF(ptr, struct ocpp_session, node)
+
+enum ocpp_pdu_msg {
+ PDU_BOOTNOTIFICATION,
+ PDU_AUTHORIZE,
+ PDU_START_TRANSACTION,
+ PDU_STOP_TRANSACTION,
+ PDU_HEARTBEAT,
+ PDU_METER_VALUES,
+ PDU_CLEAR_CACHE,
+ PDU_REMOTE_START_TRANSACTION,
+ PDU_REMOTE_STOP_TRANSACTION,
+ PDU_GET_CONFIGURATION,
+ PDU_CHANGE_CONFIGURATION,
+ PDU_CHANGE_AVAILABILITY,
+ PDU_UNLOCK_CONNECTOR,
+ PDU_RESET,
+
+ PDU_MSG_END,
+
+ /* internal msg */
+ PDU_CS_ONLINE,
+};
+
+enum ocpp_key_type {
+ KEY_TYPE_BOOL = 1,
+ KEY_TYPE_INT = sizeof(int),
+ KEY_TYPE_STR,
+ KEY_TYPE_CSL,
+};
+
+enum boot_status {
+ BOOT_ACCEPTED,
+ BOOT_PENDING,
+ BOOT_REJECTED
+};
+
+enum ocpp_key {
+ /* core mandatory */
+ CFG_ALLOW_OFFLINE_TX_FOR_UNKN_ID,
+ CFG_AUTH_CACHE_ENABLED,
+ CFG_AUTH_REMOTE_TX_REQ,
+ CFG_BLINK_REPEAT,
+ CFG_CLK_ALIGN_DATA_INTERVAL,
+ CFG_CONN_TIMEOUT,
+ CFG_GETCFG_MAX_KEY,
+ CFG_HEARTBEAT_INTERVAL,
+ CFG_LIGHT_INTENSITY,
+ CFG_LOCAL_AUTH_OFFLINE,
+ CFG_LOCAL_PREAUTH,
+ CFG_MAX_ENERGYON_INVL_ID,
+ CFG_MTR_VAL_ALGIN_DATA,
+ CFG_MTR_VAL_ALGIN_DATA_MAXLEN,
+ CFG_MTR_VAL_SAMPLED_DATA,
+ CFG_MTR_VAL_SAMPLED_DATA_MAXLEN,
+ CFG_MTR_VAL_SAMPLE_INTERVAL,
+ CFG_MIN_STATUS_DURATION,
+ CFG_NO_OF_CONNECTORS,
+ CFG_REST_RETRIES,
+ CFG_CONN_PHASE_ROT,
+ CFG_CONN_PHASE_ROT_MAXLEN,
+ CFG_STOP_TXN_ON_EVSIDE_DISCON,
+ CFG_STOP_TXN_ON_INVL_ID,
+ CFG_STOP_TXN_ALIGNED_DATA,
+ CFG_STOP_TXN_ALIGNED_DATA_MAXLEN,
+ CFG_SUPPORTED_FEATURE_PROFILE,
+ CFG_SUPPORTED_FEATURE_PROFILE_MAXLEN,
+ CFG_TXN_MSG_ATTEMPTS,
+ CFG_TXN_MSG_RETRY_INTERVAL,
+ CFG_UNLOCK_CONN_ON_EVSIDE_DISCON,
+ CFG_WEBSOCK_PING_INTERVAL,
+
+ /* optional */
+
+ OCPP_CFG_END
+};
+
+enum ocpp_cp_state {
+ CP_STATE_INIT,
+ CP_STATE_BOOTNOTIF,
+ CP_STATE_READY,
+};
+
+enum ocpp_wamp_rpc {
+ OCPP_WAMP_RPC_REQ = '2',
+ OCPP_WAMP_RPC_RESP = '3',
+ OCPP_WAMP_RPC_ERR = '4'
+};
+
+typedef int (*ocpp_msg_fp_t)(char *buf, ...);
+
+struct boot_notif {
+ enum boot_status status;
+ int interval;
+ struct timeval date;
+};
+
+struct ocpp_idtag_info {
+ char idtag[CISTR20];
+ char p_idtag[CISTR20];
+ enum ocpp_auth_status auth_status;
+ char exptime[CISTR25];
+};
+
+struct ocpp_upstream_info {
+ struct k_mutex ws_sndlock; /* to server */
+ struct k_poll_signal ws_rspsig; /* wait for resp parsed */
+ int tcpsock;
+ int wssock;
+ struct k_thread tinfo;
+ char recv_buf[CONFIG_OCPP_RECV_BUFFER_SIZE];
+ char wsrecv_buf[CONFIG_OCPP_RECV_BUFFER_SIZE * 2];
+
+ struct ocpp_cs_info csi;
+};
+
+struct ocpp_info {
+ struct k_mutex ilock; /* internal to lib */
+ sys_slist_t slist; /* session list */
+ ocpp_msg_fp_t *cfn;
+ ocpp_msg_fp_t *pfn;
+ bool is_cs_offline;
+ struct k_timer hb_timer;
+ struct k_timer mtr_timer;
+ atomic_t mtr_timer_ref_cnt;
+ int hb_sec; /* heartbeat interval */
+ struct k_msgq *msgq;
+ struct k_thread tinfo;
+ struct ocpp_upstream_info ui;
+ bool is_cs_connected; /* central system */
+
+ ocpp_user_notify_callback_t cb;
+ void *user_data;
+ enum ocpp_cp_state state;
+ /* only for pdu message from internal thread (ocpp_internal_handler) */
+ char pdu_buf[512];
+};
+
+struct ocpp_session {
+ struct k_mutex slock; /* session lock */
+ char idtag[CISTR20];
+ bool is_active;
+ uint8_t idcon;
+ int idtxn;
+ int resp_status;
+ int uid;
+ sys_snode_t node;
+ struct ocpp_info *ctx;
+};
+
+union ocpp_keyval {
+ int ival;
+ char *str;
+};
+
+struct ocpp_wamp_rpc_msg {
+ char *msg;
+ size_t msg_len;
+ struct ocpp_info *ctx;
+ struct k_mutex *sndlock;
+ struct k_poll_signal *rspsig;
+};
+
+struct internal_msg {
+ enum ocpp_pdu_msg msgtype;
+ union ocpp_io_value usr;
+};
+
+void ocpp_parser_init(ocpp_msg_fp_t **cfn, ocpp_msg_fp_t **pfn);
+int parse_rpc_msg(char *msg, int msglen, char *uid, int uidlen,
+ int *pdu, bool *is_rsp);
+int ocpp_send_to_server(struct ocpp_wamp_rpc_msg *snd, k_timeout_t timeout);
+int ocpp_receive_from_server(struct ocpp_wamp_rpc_msg *rcv, uint32_t timeout);
+
+int ocpp_unlock_connector(struct ocpp_info *ctx,
+ struct internal_msg *msg, char *uid);
+int ocpp_get_configuration(enum ocpp_key key, struct ocpp_info *ctx, char *uid);
+int ocpp_boot_notification(ocpp_session_handle_t hndl,
+ struct ocpp_cp_info *cpi);
+int ocpp_heartbeat(ocpp_session_handle_t hndl);
+void ocpp_get_utc_now(char utc[CISTR25]);
+bool ocpp_session_is_valid(struct ocpp_session *sh);
+int ocpp_remote_start_transaction(struct ocpp_info *ctx,
+ struct internal_msg *msg,
+ char *uid);
+int ocpp_remote_stop_transaction(struct ocpp_info *ctx,
+ struct internal_msg *msg,
+ int idtxn, char *uid);
+int ocpp_change_configuration(char *skey, struct ocpp_info *ctx,
+ char *sval, char *uid);
+int ocpp_meter_values(ocpp_session_handle_t hndl,
+ enum ocpp_meter_measurand mes,
+ char *sval);
+
+union ocpp_keyval *ocpp_get_key_val(enum ocpp_key key);
+enum ocpp_key ocpp_key_to_cfg(const char *skey);
+enum ocpp_key_type ocpp_get_keyval_type(enum ocpp_key key);
+char *ocpp_get_key_literal(enum ocpp_key key);
+bool ocpp_is_key_rw(enum ocpp_key key);
+int ocpp_set_cfg_val(enum ocpp_key key, union ocpp_keyval *val);
+int ocpp_update_cfg_val(enum ocpp_key key, union ocpp_keyval *val);
+int ocpp_find_pdu_from_literal(const char *msg);
+const char *ocpp_get_pdu_literal(enum ocpp_pdu_msg pdu);
+#endif /* __OCPP_I_ */
diff --git a/subsys/net/lib/ocpp/ocpp_j.c b/subsys/net/lib/ocpp/ocpp_j.c
new file mode 100644
index 0000000000000..7f87332bbcf30
--- /dev/null
+++ b/subsys/net/lib/ocpp/ocpp_j.c
@@ -0,0 +1,936 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "ocpp_i.h"
+#include
+#include
+
+/* construct msg to server */
+static int frame_rpc_call_req(char *rpcbuf, int len, int pdu, uint32_t ses,
+ struct json_object *pdumsg)
+{
+ const char *to_send;
+ char uid[32] = {0};
+ uint32_t rnd;
+ struct json_object *rpc;
+
+ rpc = json_object_new_array();
+ if (!rpc) {
+ return -ENOMEM;
+ }
+
+ json_object_array_put_idx(rpc, 0, json_object_new_int(2));
+
+ rnd = sys_rand32_get();
+ snprintk(uid, sizeof(uid), "%u-%u-%u", ses, pdu, rnd);
+ json_object_array_put_idx(rpc, 1, json_object_new_string(uid));
+
+ json_object_array_put_idx(rpc, 2,
+ json_object_new_string(ocpp_get_pdu_literal(pdu)));
+
+ json_object_array_put_idx(rpc, 3, pdumsg);
+ to_send = json_object_to_json_string_ext(rpc, JSON_C_TO_STRING_PLAIN);
+
+ strncpy(rpcbuf, to_send, len);
+
+ json_object_put(rpc);
+
+ return 0;
+}
+
+static int frame_rpc_call_res(char *rpcbuf, int len, char *uid,
+ struct json_object *pdumsg)
+{
+ const char *to_send;
+ struct json_object *rpc;
+
+ rpc = json_object_new_array();
+ if (!rpc) {
+ return -ENOMEM;
+ }
+
+ json_object_array_put_idx(rpc, 0, json_object_new_int(3));
+ json_object_array_put_idx(rpc, 1, json_object_new_string(uid));
+
+ json_object_array_put_idx(rpc, 2, pdumsg);
+ to_send = json_object_to_json_string_ext(rpc, JSON_C_TO_STRING_PLAIN);
+
+ strncpy(rpcbuf, to_send, len);
+
+ json_object_put(rpc);
+
+ return 0;
+}
+
+static int frame_authorize_msg(char *buf, int len,
+ struct ocpp_session *ses)
+{
+ int ret;
+ struct json_object *auth;
+ struct json_object *tmp;
+
+ auth = json_object_new_object();
+ if (!auth) {
+ return -ENOMEM;
+ }
+
+ tmp = json_object_new_string(ses->idtag);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ json_object_object_add(auth, "idTag", tmp);
+ ret = frame_rpc_call_req(buf, len, PDU_AUTHORIZE, (uint32_t)ses,
+ auth);
+ if (ret) {
+ goto out;
+ }
+
+ return 0;
+
+out:
+ json_object_put(auth);
+ return ret;
+}
+
+static int frame_heartbeat_msg(char *buf, int len,
+ struct ocpp_session *ses)
+{
+ int ret;
+ struct json_object *hb;
+
+ hb = json_object_new_object();
+ if (!hb) {
+ return -ENOMEM;
+ }
+
+ ret = frame_rpc_call_req(buf, len, PDU_HEARTBEAT,
+ (uint32_t)ses, hb);
+ if (ret) {
+ json_object_put(hb);
+ }
+
+ return ret;
+}
+
+static int frame_bootnotif_msg(char *buf, int len,
+ struct ocpp_session *ses,
+ struct ocpp_cp_info *cpi)
+{
+ int ret = -ENOMEM;
+ struct json_object *txn;
+ struct json_object *tmp;
+
+ txn = json_object_new_object();
+ if (!txn) {
+ return -ENOMEM;
+ }
+
+ tmp = json_object_new_string(cpi->model);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "chargePointModel", tmp);
+ tmp = json_object_new_string(cpi->vendor);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "chargePointVendor", tmp);
+ if (cpi->box_sl_no) {
+ tmp = json_object_new_string(cpi->box_sl_no);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "chargeBoxSerialNumber", tmp);
+ }
+
+ if (cpi->sl_no) {
+ tmp = json_object_new_string(cpi->sl_no);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "chargePointSerialNumber", tmp);
+ }
+
+ if (cpi->fw_ver) {
+ tmp = json_object_new_string(cpi->fw_ver);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "firmwareVersion", tmp);
+ }
+
+ if (cpi->iccid) {
+ tmp = json_object_new_string(cpi->iccid);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "iccid", tmp);
+ }
+
+ if (cpi->imsi) {
+ tmp = json_object_new_string(cpi->imsi);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "imsi", tmp);
+ }
+
+ if (cpi->meter_sl_no) {
+ tmp = json_object_new_string(cpi->meter_sl_no);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "meterSerialNumber", tmp);
+ }
+
+ if (cpi->meter_type) {
+ tmp = json_object_new_string(cpi->meter_type);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "meterType", tmp);
+ }
+
+ ret = frame_rpc_call_req(buf, len, PDU_BOOTNOTIFICATION,
+ (uint32_t)ses, txn);
+ if (ret) {
+ goto out;
+ }
+
+ return 0;
+
+out:
+ json_object_put(txn);
+ return ret;
+}
+
+static int frame_meter_val_msg(char *buf, int len, struct ocpp_session *ses,
+ char *timestamp, char *val, char *measurand,
+ char *unit)
+{
+ int ret = -ENOMEM;
+ struct json_object *mtr;
+ struct json_object *tmp;
+ struct json_object *persample;
+ struct json_object *sampleval;
+ struct json_object *smplarr;
+ struct json_object *mtrarr;
+
+
+ mtr = json_object_new_object();
+ if (!mtr) {
+ return -ENOMEM;
+ }
+
+ tmp = json_object_new_int(ses ? ses->idcon : 0);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(mtr, "connectorId", tmp);
+ if (ses) {
+ tmp = json_object_new_int(ses->idtxn);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(mtr, "transactionId", tmp);
+ }
+
+ persample = json_object_new_object();
+ if (!persample) {
+ goto out;
+ }
+
+ tmp = json_object_new_string(timestamp);
+ if (!tmp) {
+ goto out_persample;
+ }
+
+ json_object_object_add(persample, "timestamp", tmp);
+
+ sampleval = json_object_new_object();
+ if (!sampleval) {
+ goto out_persample;
+ }
+
+ tmp = json_object_new_string(measurand);
+ if (!tmp) {
+ goto out_sampleval;
+ }
+
+ json_object_object_add(sampleval, "measurand", tmp);
+ tmp = json_object_new_string(val);
+ if (!tmp) {
+ goto out_sampleval;
+ }
+
+ json_object_object_add(sampleval, "value", tmp);
+ if (unit) {
+ tmp = json_object_new_string(unit);
+ if (!tmp) {
+ goto out_sampleval;
+ }
+
+ json_object_object_add(sampleval, "unit", tmp);
+ }
+ smplarr = json_object_new_array();
+ if (!smplarr) {
+ goto out_sampleval;
+ }
+
+ mtrarr = json_object_new_array();
+ if (!mtrarr) {
+ goto out_smplarr;
+ }
+
+ json_object_array_put_idx(smplarr, 0, sampleval);
+ json_object_object_add(persample, "sampledValue", smplarr);
+ json_object_array_put_idx(mtrarr, 0, persample);
+ json_object_object_add(mtr, "meterValue", mtrarr);
+
+ ret = frame_rpc_call_req(buf, len, PDU_METER_VALUES,
+ (uint32_t)ses, mtr);
+ if (ret) {
+ goto out;
+ }
+
+ return 0;
+
+out_smplarr:
+ json_object_put(smplarr);
+
+out_sampleval:
+ json_object_put(sampleval);
+
+out_persample:
+ json_object_put(persample);
+
+out:
+ json_object_put(mtr);
+ return ret;
+}
+
+static int frame_stop_txn_msg(char *buf, int len, struct ocpp_session *ses,
+ int Wh, char *reason, char *timestamp)
+{
+ int ret = -ENOMEM;
+ struct json_object *txn;
+ struct json_object *tmp;
+
+ txn = json_object_new_object();
+ if (!txn) {
+ return -ENOMEM;
+ }
+
+ tmp = json_object_new_int(ses->idtxn);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "transactionId", tmp);
+ tmp = json_object_new_int(Wh);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "meterStop", tmp);
+ tmp = json_object_new_string(timestamp);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "timestamp", tmp);
+ if (reason) {
+ tmp = json_object_new_string(reason);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "reason", tmp);
+ }
+
+ if (!ses->idtag[0]) {
+ tmp = json_object_new_string(ses->idtag);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "idTag", tmp);
+ }
+
+
+ ret = frame_rpc_call_req(buf, len, PDU_STOP_TRANSACTION,
+ (uint32_t)ses, txn);
+ if (ret) {
+ goto out;
+ }
+
+ return 0;
+
+out:
+ json_object_put(txn);
+ return ret;
+}
+
+static int frame_start_txn_msg(char *buf, int len, struct ocpp_session *ses,
+ int Wh, int reserv_id, char *timestamp)
+{
+ int ret = -ENOMEM;
+ struct json_object *txn;
+ struct json_object *tmp;
+
+ txn = json_object_new_object();
+ if (!txn) {
+ return -ENOMEM;
+ }
+
+ tmp = json_object_new_int(ses->idcon);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "connectorId", tmp);
+ tmp = json_object_new_string(ses->idtag);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "idTag", tmp);
+ tmp = json_object_new_int(Wh);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "meterStart", tmp);
+ tmp = json_object_new_string(timestamp);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "timestamp", tmp);
+ if (reserv_id >= 0) {
+ tmp = json_object_new_int(reserv_id);
+ if (!tmp) {
+ goto out;
+ }
+
+ json_object_object_add(txn, "reservationId", tmp);
+ }
+
+ ret = frame_rpc_call_req(buf, len, PDU_START_TRANSACTION, (uint32_t)ses,
+ txn);
+ if (ret) {
+ goto out;
+ }
+
+ return 0;
+
+out:
+ json_object_put(txn);
+ return ret;
+}
+
+static int frame_getconfig_msg(char *buf, int len, char *key,
+ char *val, bool is_rw, char *uid)
+{
+ int ret = -ENOMEM;
+ struct json_object *cfg;
+ struct json_object *keyval;
+ struct json_object *arr;
+ struct json_object *tmp;
+
+ cfg = json_object_new_object();
+ if (!cfg) {
+ return -ENOMEM;
+ }
+
+ tmp = json_object_new_string(key);
+ if (!tmp) {
+ goto out;
+ }
+
+ if (val) {
+ keyval = json_object_new_object();
+ if (!keyval) {
+ goto out;
+ }
+
+ json_object_object_add(keyval, "key", tmp);
+ tmp = json_object_new_int(!is_rw);
+ if (!tmp) {
+ goto out_keyval;
+ }
+
+ json_object_object_add(keyval, "readonly", tmp);
+ tmp = json_object_new_string(val);
+ if (!tmp) {
+ goto out_keyval;
+ }
+
+ json_object_object_add(keyval, "value", tmp);
+
+ arr = json_object_new_array();
+ if (!arr) {
+ goto out_keyval;
+ }
+
+ json_object_array_put_idx(arr, 0, keyval);
+ json_object_object_add(cfg, "configurationKey", arr);
+ } else {
+ json_object_object_add(cfg, "unknownKey", tmp);
+ }
+
+ ret = frame_rpc_call_res(buf, len, uid, cfg);
+ if (ret) {
+ goto out;
+ }
+
+ return 0;
+
+out_keyval:
+ json_object_put(keyval);
+
+out:
+ json_object_put(cfg);
+ return ret;
+}
+
+static int frame_status_resp_msg(char *buf, int len, char *res, char *uid)
+{
+ int ret;
+ struct json_object *stat;
+ struct json_object *tmp;
+
+ stat = json_object_new_object();
+ if (!stat) {
+ return -ENOMEM;
+ }
+
+ tmp = json_object_new_string(res);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ json_object_object_add(stat, "status", tmp);
+ ret = frame_rpc_call_res(buf, len, uid, stat);
+ if (ret) {
+ goto out;
+ }
+
+ return 0;
+
+out:
+ json_object_put(stat);
+ return ret;
+}
+
+/* parse msg from server */
+int parse_rpc_msg(char *msg, int msglen, char *uid, int uidlen,
+ int *pdu, bool *is_rsp)
+{
+ int ret = 0;
+ int idx = 0, rpc_id;
+ const char *str, *spdu, *payload;
+ struct json_object *rpc;
+ struct json_object *tmp;
+
+ rpc = json_tokener_parse(msg);
+ if (!rpc) {
+ return -EINVAL;
+ }
+
+ tmp = json_object_array_get_idx(rpc, idx++);
+ rpc_id = json_object_get_int(tmp);
+ tmp = json_object_array_get_idx(rpc, idx++);
+ str = json_object_get_string(tmp);
+ strncpy(uid, str, uidlen);
+
+ switch (rpc_id + '0') {
+ case OCPP_WAMP_RPC_REQ:
+ tmp = json_object_array_get_idx(rpc, idx++);
+ spdu = json_object_get_string(tmp);
+ *pdu = ocpp_find_pdu_from_literal(spdu);
+ /* fall through */
+
+ case OCPP_WAMP_RPC_RESP:
+ *is_rsp = rpc_id - 2;
+
+ tmp = json_object_array_get_idx(rpc, idx);
+ payload = json_object_get_string(tmp);
+ strncpy(msg, payload, msglen);
+ break;
+
+ case OCPP_WAMP_RPC_ERR:
+ /* fall through */
+
+ default:
+ ret = -EINVAL;
+ }
+
+ json_object_put(rpc);
+
+ return ret;
+}
+
+static int parse_idtag_info(struct json_object *root,
+ struct ocpp_idtag_info *idtag_info)
+{
+ struct json_object *idinfo;
+ struct json_object *tmp;
+ const char *str;
+
+ if (!json_object_object_get_ex(root, "idTagInfo", &idinfo)) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(idinfo, "status", &tmp)) {
+ return -EINVAL;
+ }
+
+ str = json_object_get_string(tmp);
+ switch (*str) {
+ case 'A':
+ idtag_info->auth_status = OCPP_AUTH_ACCEPTED;
+ break;
+
+ case 'B':
+ idtag_info->auth_status = OCPP_AUTH_BLOCKED;
+ break;
+
+ case 'E':
+ idtag_info->auth_status = OCPP_AUTH_EXPIRED;
+ break;
+
+ case 'I':
+ idtag_info->auth_status = OCPP_AUTH_INVALID;
+ break;
+
+ case 'C':
+ idtag_info->auth_status = OCPP_AUTH_CONCURRENT_TX;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (json_object_object_get_ex(idinfo, "parentIdTag", &tmp)) {
+ strncpy(idtag_info->p_idtag, json_object_get_string(tmp),
+ sizeof(idtag_info->p_idtag));
+ }
+
+ if (json_object_object_get_ex(idinfo, "expiryDate", &tmp)) {
+ strncpy(idtag_info->exptime, json_object_get_string(tmp),
+ sizeof(idtag_info->exptime));
+ }
+
+ return 0;
+}
+
+static int parse_heartbeat_msg(char *buf, struct timeval *date)
+{
+ struct json_object *root;
+ struct json_object *tmp;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ ret = json_object_object_get_ex(root, "currentTime", &tmp);
+ if (!ret) {
+ /* todo: convert civil time to epoch and update local time */
+ *date;
+ }
+
+ json_object_put(root);
+ return ret;
+}
+
+static int parse_authorize_msg(char *buf, struct ocpp_idtag_info *idtag_info)
+{
+ struct json_object *root;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ ret = parse_idtag_info(root, idtag_info);
+ json_object_put(root);
+
+ return ret;
+}
+
+static int parse_bootnotification_msg(char *buf, struct boot_notif *binfo)
+{
+ struct json_object *root;
+ struct json_object *tmp;
+ const char *str;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "status", &tmp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ str = json_object_get_string(tmp);
+ switch (*str) {
+ case 'A': /* accepted */
+ binfo->status = BOOT_ACCEPTED;
+ break;
+
+ case 'P': /* pending */
+ binfo->status = BOOT_PENDING;
+ break;
+
+ case 'R': /* rejected */
+ binfo->status = BOOT_REJECTED;
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!json_object_object_get_ex(root, "interval", &tmp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ binfo->interval = json_object_get_int(tmp);
+ if (!json_object_object_get_ex(root, "currentTime", &tmp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* todo: convert civil time to epoch and update local time */
+ binfo->date;
+
+out:
+ json_object_put(root);
+ return ret;
+}
+
+static int parse_start_txn_msg(char *buf,
+ int *idtxn,
+ struct ocpp_idtag_info *idtag_info)
+{
+ struct json_object *root;
+ struct json_object *tmp;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "transactionId", &tmp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ *idtxn = json_object_get_int(tmp);
+ ret = parse_idtag_info(root, idtag_info);
+
+out:
+ json_object_put(root);
+
+ return ret;
+}
+
+static int parse_getconfig_msg(char *buf, char *key)
+{
+ struct json_object *root;
+ struct json_object *keys;
+ struct json_object *cfg;
+ const char *str;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "key", &keys)) {
+ return -EINVAL;
+ }
+
+ cfg = json_object_array_get_idx(keys, 0);
+ if (!cfg) {
+ goto out;
+ }
+
+ str = json_object_get_string(cfg);
+ if (str) {
+ strcpy(key, str);
+ }
+
+out:
+ json_object_put(root);
+ return ret;
+}
+
+static int parse_changeconfig_msg(char *buf, char *key, char *val)
+{
+ struct json_object *root;
+ struct json_object *tmp;
+ const char *str;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "key", &tmp)) {
+ return -EINVAL;
+ }
+
+ str = json_object_get_string(tmp);
+ strncpy(key, str, CISTR50);
+
+ if (!json_object_object_get_ex(root, "value", &tmp)) {
+ return -EINVAL;
+ }
+
+ str = json_object_get_string(tmp);
+ strncpy(val, str, CISTR500);
+
+ json_object_put(root);
+ return ret;
+}
+
+static int parse_remote_start_txn_msg(char *buf,
+ int *idcon,
+ char *idtag)
+{
+ struct json_object *root;
+ struct json_object *tmp;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "idTag", &tmp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ strncpy(idtag, json_object_get_string(tmp), CISTR50);
+ if (json_object_object_get_ex(root, "connectorId", &tmp)) {
+ *idcon = json_object_get_int(tmp);
+ }
+
+out:
+ json_object_put(root);
+
+ return ret;
+}
+
+static int parse_remote_stop_txn_msg(char *buf, int *idtxn)
+{
+ struct json_object *root;
+ struct json_object *tmp;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "transactionId", &tmp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ *idtxn = json_object_get_int(tmp);
+
+out:
+ json_object_put(root);
+
+ return ret;
+}
+
+static int parse_unlock_connectormsg(char *buf,
+ int *idcon)
+{
+ struct json_object *root;
+ struct json_object *tmp;
+ int ret = 0;
+
+ root = json_tokener_parse(buf);
+ if (!root) {
+ return -EINVAL;
+ }
+
+ if (!json_object_object_get_ex(root, "connectorId", &tmp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ *idcon = json_object_get_int(tmp);
+
+out:
+ json_object_put(root);
+
+ return ret;
+}
+
+static ocpp_msg_fp_t ocpp_json_parser[PDU_MSG_END] = {
+ [PDU_BOOTNOTIFICATION] = (ocpp_msg_fp_t)parse_bootnotification_msg,
+ [PDU_AUTHORIZE] = (ocpp_msg_fp_t)parse_authorize_msg,
+ [PDU_START_TRANSACTION] = (ocpp_msg_fp_t)parse_start_txn_msg,
+ [PDU_STOP_TRANSACTION] = (ocpp_msg_fp_t)parse_authorize_msg,
+ [PDU_METER_VALUES] = NULL,
+ [PDU_HEARTBEAT] = (ocpp_msg_fp_t)parse_heartbeat_msg,
+ [PDU_GET_CONFIGURATION] = (ocpp_msg_fp_t)parse_getconfig_msg,
+ [PDU_CHANGE_CONFIGURATION] = (ocpp_msg_fp_t)parse_changeconfig_msg,
+ [PDU_REMOTE_START_TRANSACTION] = (ocpp_msg_fp_t)parse_remote_start_txn_msg,
+ [PDU_REMOTE_STOP_TRANSACTION] = (ocpp_msg_fp_t)parse_remote_stop_txn_msg,
+ [PDU_UNLOCK_CONNECTOR] = (ocpp_msg_fp_t)parse_unlock_connectormsg,
+};
+
+static ocpp_msg_fp_t ocpp_json_frame[PDU_MSG_END] = {
+ [PDU_BOOTNOTIFICATION] = (ocpp_msg_fp_t)frame_bootnotif_msg,
+ [PDU_AUTHORIZE] = (ocpp_msg_fp_t)frame_authorize_msg,
+ [PDU_START_TRANSACTION] = (ocpp_msg_fp_t)frame_start_txn_msg,
+ [PDU_STOP_TRANSACTION] = (ocpp_msg_fp_t)frame_stop_txn_msg,
+ [PDU_METER_VALUES] = (ocpp_msg_fp_t)frame_meter_val_msg,
+ [PDU_HEARTBEAT] = (ocpp_msg_fp_t)frame_heartbeat_msg,
+ [PDU_GET_CONFIGURATION] = (ocpp_msg_fp_t)frame_getconfig_msg,
+ [PDU_CHANGE_CONFIGURATION] (ocpp_msg_fp_t)frame_status_resp_msg,
+ [PDU_REMOTE_START_TRANSACTION] = (ocpp_msg_fp_t)frame_status_resp_msg,
+ [PDU_REMOTE_STOP_TRANSACTION] = (ocpp_msg_fp_t)frame_status_resp_msg,
+ [PDU_UNLOCK_CONNECTOR] = (ocpp_msg_fp_t)frame_status_resp_msg,
+};
+
+void ocpp_parser_init(ocpp_msg_fp_t **cfn, ocpp_msg_fp_t **pfn)
+{
+ *pfn = ocpp_json_parser;
+ *cfn = ocpp_json_frame;
+}
diff --git a/subsys/net/lib/ocpp/ocpp_wamp_rpc.c b/subsys/net/lib/ocpp/ocpp_wamp_rpc.c
new file mode 100644
index 0000000000000..b41484a98bb6b
--- /dev/null
+++ b/subsys/net/lib/ocpp/ocpp_wamp_rpc.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "ocpp_i.h"
+
+LOG_MODULE_REGISTER(ocpp_rpc, CONFIG_OCPP_LOG_LEVEL);
+
+#define OCPP_WAMP_RPC_TYPE_IDX 1
+
+int ocpp_send_to_server(struct ocpp_wamp_rpc_msg *snd, k_timeout_t timeout)
+{
+ int ret;
+ int sock;
+ struct ocpp_info *ctx = snd->ctx;
+
+ switch (snd->msg[OCPP_WAMP_RPC_TYPE_IDX]) {
+ case OCPP_WAMP_RPC_REQ:
+ /* ocpp spec - allow only one active call at a time
+ * release lock on response received from CS or timeout
+ */
+ ret = k_mutex_lock(snd->sndlock, timeout);
+ if (ret) {
+ return ret;
+ }
+ k_poll_signal_reset(snd->rspsig);
+ break;
+
+ case OCPP_WAMP_RPC_RESP:
+ case OCPP_WAMP_RPC_ERR:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ k_mutex_lock(&ctx->ilock, K_FOREVER);
+ sock = ctx->ui.wssock;
+ k_mutex_unlock(&ctx->ilock);
+ if (sock < 0) {
+ ret = -EAGAIN;
+ goto unlock;
+ }
+
+ ret = websocket_send_msg(sock, snd->msg, snd->msg_len,
+ WEBSOCKET_OPCODE_DATA_TEXT, true,
+ true, 5000); /* fixme timeout */
+ if (ret < 0) {
+ goto unlock;
+ }
+
+ if (snd->rspsig) {
+ struct k_poll_event events[1] = {
+ K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL,
+ K_POLL_MODE_NOTIFY_ONLY,
+ snd->rspsig),
+ };
+
+ ret = k_poll(events, 1, timeout);
+ goto unlock;
+ }
+
+ return 0;
+
+unlock:
+ if (snd->sndlock) {
+ k_mutex_unlock(snd->sndlock);
+ }
+
+ return ret;
+}
+
+int ocpp_receive_from_server(struct ocpp_wamp_rpc_msg *rcv, uint32_t timeout)
+{
+ int ret;
+ int sock;
+ int msg_type;
+ uint64_t remaining = 0;
+ struct ocpp_info *ctx = rcv->ctx;
+
+ k_mutex_lock(&ctx->ilock, K_MSEC(timeout));
+ sock = ctx->ui.wssock;
+ k_mutex_unlock(&ctx->ilock);
+ if (sock < 0) {
+ return -EAGAIN;
+ }
+
+ ret = websocket_recv_msg(sock, rcv->msg,
+ rcv->msg_len,
+ &msg_type,
+ &remaining,
+ timeout);
+
+ return ret;
+}
diff --git a/tests/net/lib/ocpp/CMakeLists.txt b/tests/net/lib/ocpp/CMakeLists.txt
new file mode 100644
index 0000000000000..50a0f7dd9670e
--- /dev/null
+++ b/tests/net/lib/ocpp/CMakeLists.txt
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(ocpp_test)
+
+target_include_directories(app PRIVATE
+ ${ZEPHYR_BASE}/subsys/net/ip
+ )
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/tests/net/lib/ocpp/prj.conf b/tests/net/lib/ocpp/prj.conf
new file mode 100644
index 0000000000000..b2537b400278b
--- /dev/null
+++ b/tests/net/lib/ocpp/prj.conf
@@ -0,0 +1,43 @@
+# HTTP & Websocket
+CONFIG_HTTP_CLIENT=y
+CONFIG_WEBSOCKET_CLIENT=y
+
+# Networking config
+CONFIG_NETWORKING=y
+CONFIG_NET_IPV4=y
+CONFIG_NET_IPV6=n
+CONFIG_NET_TCP=y
+CONFIG_NET_ARP=y
+CONFIG_NET_UDP=y
+CONFIG_NET_SOCKETS=y
+CONFIG_NET_SOCKETS_POLL_MAX=4
+CONFIG_NET_L2_ETHERNET=y
+CONFIG_ETH_DRIVER=y
+CONFIG_LOG=y
+CONFIG_NET_LOG=y
+CONFIG_LOG_DEFAULT_LEVEL=3
+CONFIG_NET_DHCPV4=y
+
+# OCPP
+CONFIG_OCPP=y
+CONFIG_TEST_EXTRA_STACK_SIZE=2048
+CONFIG_MAIN_STACK_SIZE=2048
+CONFIG_HEAP_MEM_POOL_SIZE=15000
+
+CONFIG_JSONC=y
+CONFIG_PICOLIBC=y
+
+CONFIG_NET_TX_STACK_SIZE=2048
+CONFIG_NET_RX_STACK_SIZE=2048
+CONFIG_NET_PKT_RX_COUNT=28
+CONFIG_NET_BUF_RX_COUNT=60
+CONFIG_NET_MGMT=y
+CONFIG_NET_MGMT_EVENT=y
+
+CONFIG_ENTROPY_GENERATOR=y
+CONFIG_TEST_RANDOM_GENERATOR=y
+
+CONFIG_POSIX_CLOCK=y
+CONFIG_POSIX_API=y
+
+CONFIG_ZTEST=y
diff --git a/tests/net/lib/ocpp/src/main.c b/tests/net/lib/ocpp/src/main.c
new file mode 100644
index 0000000000000..65df05cb45a23
--- /dev/null
+++ b/tests/net/lib/ocpp/src/main.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2024 Linumiz
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+#include
+#include
+#include
+
+static void test_ocpp_charge_cycle(ocpp_session_handle_t hndl)
+{
+ int ret;
+ int retry = 3;
+ enum ocpp_auth_status status;
+ const uint32_t timeout_ms = 500;
+
+ while (retry--) {
+ ret = ocpp_authorize(hndl, "ZepId00", &status, timeout_ms);
+
+ TC_PRINT("auth req ret %d status %d", ret, status);
+ k_sleep(K_SECONDS(1));
+
+ if (!ret) {
+ break;
+ }
+ }
+ zassert_equal(ret, 0, "CP authorize fail %d");
+ zassert_equal(status, OCPP_AUTH_ACCEPTED, "idtag not authorized");
+
+ ret = ocpp_start_transaction(hndl, sys_rand32_get(), 1, timeout_ms);
+ zassert_equal(ret, 0, "start transaction fail");
+
+ /* Active charging session */
+ k_sleep(K_SECONDS(20));
+ ret = ocpp_stop_transaction(hndl, sys_rand32_get(), timeout_ms);
+ zassert_equal(ret, 0, "stop transaction fail");
+}
+
+static int test_ocpp_user_notify_cb(enum ocpp_notify_reason reason,
+ union ocpp_io_value *io,
+ void *user_data)
+{
+ switch (reason) {
+ case OCPP_USR_GET_METER_VALUE:
+ if (OCPP_OMM_ACTIVE_ENERGY_TO_EV == io->meter_val.mes) {
+ snprintf(io->meter_val.val, CISTR50, "%u",
+ sys_rand32_get());
+
+ TC_PRINT("mtr reading val %s con %d",
+ io->meter_val.val,
+ io->meter_val.id_con);
+ return 0;
+ }
+ break;
+
+ case OCPP_USR_START_CHARGING:
+ TC_PRINT("start charging idtag %s connector %d\n",
+ io->start_charge.idtag,
+ io->stop_charge.id_con);
+ return 0;
+
+ case OCPP_USR_STOP_CHARGING:
+ TC_PRINT("stop charging connector %d\n", io->stop_charge.id_con);
+ return 0;
+
+ case OCPP_USR_UNLOCK_CONNECTOR:
+ TC_PRINT("unlock connector %d\n", io->unlock_con.id_con);
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+int test_ocpp_init(void)
+{
+ int ret;
+
+ struct ocpp_cp_info cpi = { "basic", "zephyr", .num_of_con = 1 };
+ struct ocpp_cs_info csi = { "122.165.245.213", /* ssh.linumiz.com */
+ "/steve/websocket/CentralSystemService/zephyr",
+ 8180,
+ AF_INET };
+
+ net_dhcpv4_start(net_if_get_default());
+
+ /* wait for device dhcp ip recive */
+ k_sleep(K_SECONDS(3));
+
+ ret = ocpp_init(&cpi,
+ &csi,
+ test_ocpp_user_notify_cb,
+ NULL);
+ if (ret) {
+ TC_PRINT("ocpp init failed %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+ZTEST(net_ocpp, test_ocpp_chargepoint)
+{
+ int ret;
+ ocpp_session_handle_t hndl = NULL;
+
+ ret = test_ocpp_init();
+ zassert_equal(ret, 0, "ocpp init failed %d", ret);
+
+ ret = ocpp_session_open(&hndl);
+ zassert_equal(ret, 0, "session open failed %d", ret);
+
+ k_sleep(K_SECONDS(2));
+ test_ocpp_charge_cycle(hndl);
+
+ ocpp_session_close(hndl);
+}
+
+ZTEST_SUITE(net_ocpp, NULL, NULL, NULL, NULL, NULL);
diff --git a/tests/net/lib/ocpp/testcase.yaml b/tests/net/lib/ocpp/testcase.yaml
new file mode 100644
index 0000000000000..5447b2cd75d41
--- /dev/null
+++ b/tests/net/lib/ocpp/testcase.yaml
@@ -0,0 +1,7 @@
+tests:
+ net.ocpp:
+ depends_on: netif
+ min_ram: 16
+ tags:
+ - net
+ - ocpp
diff --git a/west.yml b/west.yml
index 11cc8064bb0b7..d7746868d4bec 100644
--- a/west.yml
+++ b/west.yml
@@ -23,6 +23,8 @@ manifest:
url-base: https://github.com/zephyrproject-rtos
- name: babblesim
url-base: https://github.com/BabbleSim
+ - name: linumiz
+ url-base: https://github.com/linumiz
group-filter: [-babblesim, -optional]
@@ -337,6 +339,10 @@ manifest:
- name: zcbor
revision: 75d088037eb237b18e7ec1f47c9ce494b9b95aab
path: modules/lib/zcbor
+ - name: json-c
+ remote: linumiz
+ path: modules/lib/json-c
+ revision: dev/zephyr
self:
path: zephyr