diff --git a/samples/net/ocpp/src/main.c b/samples/net/ocpp/src/main.c index b703428bce46d..37ba834d482fe 100644 --- a/samples/net/ocpp/src/main.c +++ b/samples/net/ocpp/src/main.c @@ -31,6 +31,16 @@ static struct k_thread tinfo[NO_OF_CONN]; static k_tid_t tid[NO_OF_CONN]; static char idtag[NO_OF_CONN][25]; +/* Track meter values per connector for consistent charging simulation */ +struct connector_meter { + int start_wh; /* Starting meter value in Wh */ + int64_t start_time_ms; /* Start time in milliseconds */ + bool charging; /* Is charging active */ +}; + +static struct connector_meter meter[NO_OF_CONN]; +static K_MUTEX_DEFINE(meter_lock); + static int ocpp_get_time_from_sntp(void) { struct sntp_ctx ctx; @@ -79,27 +89,149 @@ struct zbus_observer *obs[NO_OF_CONN] = {(struct zbus_observer *)&cp_thread0, (struct zbus_observer *)&cp_thread1}; static void ocpp_cp_entry(void *p1, void *p2, void *p3); + +/* Get current meter reading for a connector in Wh. + * Simulates realistic charging with ~7.2 kW charging power (32A at 230V): + * - Approximately 7200 W = 7200 Wh per hour = 120 Wh per minute = 2 Wh per second + */ +static int get_current_meter_value(int connector_id) +{ + int idx = connector_id - 1; + int current_wh; + + if (idx < 0 || idx >= NO_OF_CONN) { + return 0; + } + + k_mutex_lock(&meter_lock, K_FOREVER); + + if (!meter[idx].charging) { + /* Not charging, return the start value */ + current_wh = meter[idx].start_wh; + } else { + /* Calculate energy consumed based on elapsed time */ + int64_t elapsed_ms = k_uptime_get() - meter[idx].start_time_ms; + int elapsed_seconds = (int)(elapsed_ms / 1000); + /* Simulate ~2 Wh per second charging rate */ + int energy_added = elapsed_seconds * 2; + + current_wh = meter[idx].start_wh + energy_added; + } + + k_mutex_unlock(&meter_lock); + + return current_wh; +} + 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); + switch (io->meter_val.mes) { + case OCPP_OMM_ACTIVE_ENERGY_TO_EV: + /* Active energy delivered to EV in Wh */ + snprintf(io->meter_val.val, CISTR50, "%d", + get_current_meter_value(io->meter_val.id_con)); + break; - wh++; - LOG_DBG("mtr reading val %s con %d", io->meter_val.val, - io->meter_val.id_con); + case OCPP_OMM_ACTIVE_ENERGY_FROM_EV: + /* Energy from EV (e.g., V2G scenarios) - typically 0 */ + snprintf(io->meter_val.val, CISTR50, "0"); + break; - return 0; + case OCPP_OMM_CURRENT_TO_EV: + /* Charging current in A - simulate 32A charging */ + snprintf(io->meter_val.val, CISTR50, "32.0"); + break; + + case OCPP_OMM_CURRENT_FROM_EV: + /* Current from EV - typically 0 for normal charging */ + snprintf(io->meter_val.val, CISTR50, "0.0"); + break; + + case OCPP_OMM_CURRENT_MAX_OFFERED_TO_EV: + /* Maximum current available - 32A for this charger */ + snprintf(io->meter_val.val, CISTR50, "32.0"); + break; + + case OCPP_OMM_ACTIVE_POWER_TO_EV: + /* Active power in W - 7200W (32A * 230V) */ + snprintf(io->meter_val.val, CISTR50, "7200"); + break; + + case OCPP_OMM_ACTIVE_POWER_FROM_EV: + /* Power from EV - typically 0 */ + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + case OCPP_OMM_POWER_MAX_OFFERED_TO_EV: + /* Maximum power available in W */ + snprintf(io->meter_val.val, CISTR50, "7200"); + break; + + case OCPP_OMM_REACTIVE_ENERGY_TO_EV: + /* Reactive energy in varh - assume low PF, ~10% of active */ + snprintf(io->meter_val.val, CISTR50, "%d", + get_current_meter_value(io->meter_val.id_con) / 10); + break; + + case OCPP_OMM_REACTIVE_ENERGY_FROM_EV: + /* Reactive energy from EV - typically 0 */ + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + case OCPP_OMM_REACTIVE_POWER_TO_EV: + /* Reactive power in var - ~720 var (10% of 7200W) */ + snprintf(io->meter_val.val, CISTR50, "720"); + break; + + case OCPP_OMM_REACTIVE_POWER_FROM_EV: + /* Reactive power from EV - typically 0 */ + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + case OCPP_OMM_VOLTAGE_AC_RMS: + /* AC voltage in V - standard European voltage */ + snprintf(io->meter_val.val, CISTR50, "230.0"); + break; + + case OCPP_OMM_POWERLINE_FREQ: + /* Powerline frequency in Hz - 50Hz for Europe */ + snprintf(io->meter_val.val, CISTR50, "50.0"); + break; + + case OCPP_OMM_POWER_FACTOR: + /* Power factor - 0.9 is typical for EV charging */ + snprintf(io->meter_val.val, CISTR50, "0.90"); + break; + + case OCPP_OMM_TEMPERATURE: + /* Temperature in Celsius - simulate 35°C */ + snprintf(io->meter_val.val, CISTR50, "35"); + break; + + case OCPP_OMM_FAN_SPEED: + /* Fan speed in RPM - simulate 1500 RPM */ + snprintf(io->meter_val.val, CISTR50, "1500"); + break; + + case OCPP_OMM_CHARGING_PERCENT: + /* Charging percentage - not typically known by charger */ + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + default: + return -ENOTSUP; } - break; + + LOG_DBG("mtr reading %d val %s con %d", io->meter_val.mes, + io->meter_val.val, io->meter_val.id_con); + return 0; case OCPP_USR_START_CHARGING: if (io->start_charge.id_con < 0) { @@ -187,7 +319,21 @@ static void ocpp_cp_entry(void *p1, void *p2, void *p3) return; } - ret = ocpp_start_transaction(sh, sys_rand32_get(), idcon, timeout_ms); + /* Initialize meter tracking for this connector */ + int idx = idcon - 1; + int start_meter_value; + + k_mutex_lock(&meter_lock, K_FOREVER); + /* Use a sensible starting meter value based on connector ID + * (e.g., connector 1 starts at 1000 Wh, connector 2 at 2000 Wh) + */ + meter[idx].start_wh = (idcon * 1000); + meter[idx].start_time_ms = k_uptime_get(); + meter[idx].charging = true; + start_meter_value = meter[idx].start_wh; + k_mutex_unlock(&meter_lock); + + ret = ocpp_start_transaction(sh, start_meter_value, idcon, timeout_ms); if (ret == 0) { const struct zbus_channel *chan; union ocpp_io_value io; @@ -208,7 +354,14 @@ static void ocpp_cp_entry(void *p1, void *p2, void *p3) } while (1); } - ret = ocpp_stop_transaction(sh, sys_rand32_get(), timeout_ms); + /* Mark charging as stopped and get final meter value */ + k_mutex_lock(&meter_lock, K_FOREVER); + meter[idx].charging = false; + k_mutex_unlock(&meter_lock); + + int stop_meter_value = get_current_meter_value(idcon); + + ret = ocpp_stop_transaction(sh, stop_meter_value, timeout_ms); if (ret < 0) { LOG_ERR("ocpp stop txn idcon %d> %d\n", idcon, ret); return; diff --git a/tests/net/lib/ocpp/src/main.c b/tests/net/lib/ocpp/src/main.c index 969a710679dad..1e154a9d513a3 100644 --- a/tests/net/lib/ocpp/src/main.c +++ b/tests/net/lib/ocpp/src/main.c @@ -15,6 +15,8 @@ static void test_ocpp_charge_cycle(ocpp_session_handle_t hndl) int retry = 3; enum ocpp_auth_status status; const uint32_t timeout_ms = 500; + static int meter_start_wh = 1000; /* Start with 1000 Wh */ + static int64_t charge_start_time_ms; while (retry--) { ret = ocpp_authorize(hndl, "ZepId00", &status, timeout_ms); @@ -29,36 +31,132 @@ static void test_ocpp_charge_cycle(ocpp_session_handle_t hndl) 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); + charge_start_time_ms = k_uptime_get(); + ret = ocpp_start_transaction(hndl, meter_start_wh, 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); + + /* Calculate energy consumed at ~2 Wh/second for 20 seconds */ + int64_t elapsed_ms = k_uptime_get() - charge_start_time_ms; + int elapsed_seconds = (int)(elapsed_ms / 1000); + int meter_stop_wh = meter_start_wh + (elapsed_seconds * 2); + + ret = ocpp_stop_transaction(hndl, meter_stop_wh, timeout_ms); zassert_equal(ret, 0, "stop transaction fail"); + + /* Update starting value for next charge cycle */ + meter_start_wh = meter_stop_wh; } static int test_ocpp_user_notify_cb(enum ocpp_notify_reason reason, union ocpp_io_value *io, void *user_data) { + static int meter_wh = 1000; /* Track active energy meter value */ + static int64_t last_read_time_ms; + 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; + switch (io->meter_val.mes) { + case OCPP_OMM_ACTIVE_ENERGY_TO_EV: + /* Simulate ~2 Wh/second charging */ + { + int64_t current_time_ms = k_uptime_get(); + + if (last_read_time_ms > 0) { + int64_t elapsed_ms = current_time_ms - last_read_time_ms; + int elapsed_seconds = (int)(elapsed_ms / 1000); + + meter_wh += elapsed_seconds * 2; + } + last_read_time_ms = current_time_ms; + + snprintf(io->meter_val.val, CISTR50, "%d", meter_wh); + } + break; + + case OCPP_OMM_ACTIVE_ENERGY_FROM_EV: + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + case OCPP_OMM_CURRENT_TO_EV: + snprintf(io->meter_val.val, CISTR50, "32.0"); + break; + + case OCPP_OMM_CURRENT_FROM_EV: + snprintf(io->meter_val.val, CISTR50, "0.0"); + break; + + case OCPP_OMM_CURRENT_MAX_OFFERED_TO_EV: + snprintf(io->meter_val.val, CISTR50, "32.0"); + break; + + case OCPP_OMM_ACTIVE_POWER_TO_EV: + snprintf(io->meter_val.val, CISTR50, "7200"); + break; + + case OCPP_OMM_ACTIVE_POWER_FROM_EV: + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + case OCPP_OMM_POWER_MAX_OFFERED_TO_EV: + snprintf(io->meter_val.val, CISTR50, "7200"); + break; + + case OCPP_OMM_REACTIVE_ENERGY_TO_EV: + snprintf(io->meter_val.val, CISTR50, "%d", meter_wh / 10); + break; + + case OCPP_OMM_REACTIVE_ENERGY_FROM_EV: + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + case OCPP_OMM_REACTIVE_POWER_TO_EV: + snprintf(io->meter_val.val, CISTR50, "720"); + break; + + case OCPP_OMM_REACTIVE_POWER_FROM_EV: + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + case OCPP_OMM_VOLTAGE_AC_RMS: + snprintf(io->meter_val.val, CISTR50, "230.0"); + break; + + case OCPP_OMM_POWERLINE_FREQ: + snprintf(io->meter_val.val, CISTR50, "50.0"); + break; + + case OCPP_OMM_POWER_FACTOR: + snprintf(io->meter_val.val, CISTR50, "0.90"); + break; + + case OCPP_OMM_TEMPERATURE: + snprintf(io->meter_val.val, CISTR50, "35"); + break; + + case OCPP_OMM_FAN_SPEED: + snprintf(io->meter_val.val, CISTR50, "1500"); + break; + + case OCPP_OMM_CHARGING_PERCENT: + snprintf(io->meter_val.val, CISTR50, "0"); + break; + + default: + return -ENOTSUP; } - break; + + TC_PRINT("mtr reading %d val %s con %d", io->meter_val.mes, + io->meter_val.val, io->meter_val.id_con); + return 0; case OCPP_USR_START_CHARGING: TC_PRINT("start charging idtag %s connector %d\n", io->start_charge.idtag, - io->stop_charge.id_con); + io->start_charge.id_con); return 0; case OCPP_USR_STOP_CHARGING: