@@ -60,6 +60,8 @@ char RequiredEVCCID[32] = ""; // R
6060#include < MicroOcppMongooseClient.h>
6161#include < MicroOcpp/Core/Configuration.h>
6262#include < MicroOcpp/Core/Context.h>
63+ #include " ocpp_logic.h"
64+ #include " ocpp_telemetry.h"
6365#endif // ENABLE_OCPP
6466
6567#if SMARTEVSE_VERSION >= 40
@@ -354,6 +356,8 @@ extern uint8_t OcppTrackCPvoltage;
354356extern MicroOcpp::MOcppMongooseClient *OcppWsClient;
355357
356358extern float OcppCurrentLimit;
359+ extern bool OcppWasStandalone;
360+ extern ocpp_telemetry_t OcppTelemetry;
357361
358362extern unsigned long OcppStopReadingSyncTime; // Stop value synchronization: delay StopTransaction by a few seconds so it reports an accurate energy reading
359363
@@ -1151,6 +1155,18 @@ void mqttPublishData() {
11511155#if ENABLE_OCPP && defined(SMARTEVSE_VERSION) // run OCPP only on ESP32
11521156 mqtt_pub_str (MQTT_SLOT_OCPP, " /OCPP" , OcppMode ? " Enabled" : " Disabled" , true , now_s);
11531157 mqtt_pub_str (MQTT_SLOT_OCPP_CONNECTION, " /OCPPConnection" , (OcppWsClient && OcppWsClient->isConnected ()) ? " Connected" : " Disconnected" , false , now_s);
1158+ mqtt_pub_str (MQTT_SLOT_OCPP_TX_ACTIVE, " /OCPPTxActive" , OcppTelemetry.tx_active ? " true" : " false" , false , now_s);
1159+ {
1160+ char ocpp_limit_buf[16 ];
1161+ if (OcppCurrentLimit >= 0 .0f ) {
1162+ snprintf (ocpp_limit_buf, sizeof (ocpp_limit_buf), " %.1f" , (double )OcppCurrentLimit);
1163+ } else {
1164+ snprintf (ocpp_limit_buf, sizeof (ocpp_limit_buf), " none" );
1165+ }
1166+ mqtt_pub_str (MQTT_SLOT_OCPP_CURRENT_LIMIT, " /OCPPCurrentLimit" , ocpp_limit_buf, false , now_s);
1167+ }
1168+ mqtt_pub_str (MQTT_SLOT_OCPP_SMART_CHARGING, " /OCPPSmartCharging" ,
1169+ OcppTelemetry.lb_conflict ? " Conflict" : (!LoadBl ? " Active" : " Inactive" ), false , now_s);
11541170#endif // ENABLE_OCPP
11551171 { // LED color topics — build string in buffer
11561172 char color_buf[16 ];
@@ -1697,6 +1713,8 @@ bool ocppLockingTxDefined() {
16971713
16981714void ocppInit () {
16991715
1716+ ocpp_telemetry_init (&OcppTelemetry);
1717+
17001718 // load OCPP library modules: Mongoose WS adapter and Core OCPP library
17011719
17021720 auto filesystem = MicroOcpp::makeDefaultFilesystemAdapter (
@@ -1814,6 +1832,9 @@ void ocppInit() {
18141832 return nullptr ;
18151833 });
18161834
1835+ // Track LoadBl state at init time for runtime exclusivity checks
1836+ OcppWasStandalone = !LoadBl;
1837+
18171838 // If SmartEVSE load balancer is turned off, then enable OCPP Smart Charging
18181839 // This means after toggling LB, OCPP must be disabled and enabled for changes to become effective
18191840 if (!LoadBl) {
@@ -1866,6 +1887,21 @@ void ocppInit() {
18661887 OcppDefinedTxNotification = true ;
18671888 OcppTrackTxNotification = event;
18681889 OcppLastTxNotification = millis ();
1890+
1891+ // Update telemetry counters
1892+ if (event == MicroOcpp::TxNotification::StartTx) {
1893+ ocpp_telemetry_tx_started (&OcppTelemetry);
1894+ } else if (event == MicroOcpp::TxNotification::StopTx) {
1895+ ocpp_telemetry_tx_stopped (&OcppTelemetry);
1896+ } else if (event == MicroOcpp::TxNotification::Authorized ||
1897+ event == MicroOcpp::TxNotification::RemoteStart) {
1898+ ocpp_telemetry_auth_accepted (&OcppTelemetry);
1899+ } else if (event == MicroOcpp::TxNotification::AuthorizationRejected ||
1900+ event == MicroOcpp::TxNotification::DeAuthorized) {
1901+ ocpp_telemetry_auth_rejected (&OcppTelemetry);
1902+ } else if (event == MicroOcpp::TxNotification::AuthorizationTimeout) {
1903+ ocpp_telemetry_auth_timeout (&OcppTelemetry);
1904+ }
18691905 });
18701906
18711907 // Declare custom "ConfigureMaxCurrent" key
@@ -1908,6 +1944,7 @@ void ocppDeinit() {
19081944 OcppTrackAccessBit = false ;
19091945 OcppTrackCPvoltage = PILOT_NOK;
19101946 OcppCurrentLimit = -1 .f ;
1947+ OcppWasStandalone = false ;
19111948
19121949 mocpp_deinitialize ();
19131950
@@ -1923,6 +1960,26 @@ void ocppLoop() {
19231960
19241961 mocpp_loop ();
19251962
1963+ // Check OCPP / LoadBl mutual exclusivity at runtime
1964+ ocpp_lb_status_t lb_status = ocpp_check_lb_exclusivity (LoadBl, OcppMode, OcppWasStandalone);
1965+ OcppTelemetry.lb_conflict = (lb_status != OCPP_LB_OK);
1966+ if (lb_status == OCPP_LB_CONFLICT) {
1967+ // LoadBl changed to non-zero while OCPP is active — Smart Charging limits
1968+ // are silently ignored by the state machine. Neutralize the limit and warn.
1969+ if (OcppCurrentLimit >= 0 .0f ) {
1970+ _LOG_W (" OCPP: LoadBl=%u conflicts with Smart Charging, disabling OCPP current limit\n " , LoadBl);
1971+ OcppCurrentLimit = -1 .0f ;
1972+ }
1973+ } else if (lb_status == OCPP_LB_NEEDS_REINIT) {
1974+ // LoadBl changed from non-zero to 0 — Smart Charging callback was never
1975+ // registered. User needs to disable/enable OCPP for it to take effect.
1976+ static bool ocpp_reinit_warned = false ;
1977+ if (!ocpp_reinit_warned) {
1978+ _LOG_W (" OCPP: LoadBl changed to standalone but Smart Charging not registered. Disable/enable OCPP to activate.\n " );
1979+ ocpp_reinit_warned = true ;
1980+ }
1981+ }
1982+
19261983 // handle Configuration updates
19271984
19281985 auto config = MicroOcpp::getConfigurationPublic (" ConfigureMaxCurrent" );
@@ -1973,13 +2030,22 @@ void ocppLoop() {
19732030 if (RFIDReader == 6 || RFIDReader == 0 ) {
19742031 // RFID reader in OCPP mode or RFID fully disabled - OCPP controls Access_bit
19752032 if (!OcppTrackPermitsCharge && ocppPermitsCharge ()) {
1976- _LOG_A (" OCPP set Access_bit\n " );
1977- setAccess (ON);
2033+ // Guard: defer Access_bit if mode/delay conflicts (FreeVend + Solar, ChargeDelay)
2034+ if (ocpp_should_defer_access (Mode, ChargeDelay, ErrorFlags)) {
2035+ // Don't update OcppTrackPermitsCharge — retry rising edge on next loop
2036+ _LOG_D (" OCPP: deferring Access_bit (Mode=%u ChargeDelay=%u ErrorFlags=0x%04X)\n " , Mode, ChargeDelay, ErrorFlags);
2037+ } else {
2038+ _LOG_A (" OCPP set Access_bit\n " );
2039+ setAccess (ON);
2040+ OcppTrackPermitsCharge = true ;
2041+ }
19782042 } else if (AccessStatus == ON && !ocppPermitsCharge ()) {
19792043 _LOG_A (" OCPP unset Access_bit\n " );
19802044 setAccess (OFF);
2045+ OcppTrackPermitsCharge = false ;
2046+ } else {
2047+ OcppTrackPermitsCharge = ocppPermitsCharge ();
19812048 }
1982- OcppTrackPermitsCharge = ocppPermitsCharge ();
19832049
19842050 // Check if OCPP charge permission has been revoked by other module
19852051 if (OcppTrackPermitsCharge && // OCPP has set Acess_bit and still allows charge
0 commit comments