Skip to content

[charger] Add support for OCPP chargers#47

Open
dirkgroenen wants to merge 9 commits into
mainfrom
44-ocpp-integration-support
Open

[charger] Add support for OCPP chargers#47
dirkgroenen wants to merge 9 commits into
mainfrom
44-ocpp-integration-support

Conversation

@dirkgroenen
Copy link
Copy Markdown
Owner

@dirkgroenen dirkgroenen commented Jul 26, 2025

Closes #44 by adding support for OCPP chargers.
As the requester of the charger @kljakobsen, please can you have a look at this branch and test it out locally?

Apart from hoping that the correct entities will be used, I'm also particularly interested to know whether there's a way for me to retrieve the charger's configured maximum limit? Can you browse around and see if there's anything that does not expose the current configured current, but instead the maximum current of the installation?

Right now I've just used a value of 32A as a max - but obviously it would be way more ideal to retrieve the right value.

Let me know if it works 🙏

You can update (if done via HACS) using:

action: update.install
data:
  version: 44-ocpp-integration-support
target:
  entity_id: update.evse_load_balancer_update

@dirkgroenen dirkgroenen requested a review from Copilot July 26, 2025 19:41
@dirkgroenen dirkgroenen linked an issue Jul 26, 2025 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for OCPP (Open Charge Point Protocol) chargers to the EVSE load balancer by implementing a new OcppCharger class. The implementation enables the load balancer to work with OCPP-compliant charging stations through the existing OCPP Home Assistant integration.

Key changes include:

  • Added complete OCPP charger implementation with status monitoring and current limit control
  • Integrated OCPP charger into the factory pattern for automatic device detection
  • Added comprehensive test coverage for all OCPP charger functionality

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
custom_components/evse_load_balancer/chargers/ocpp_charger.py Main implementation of OCPP charger with status monitoring, current limit management, and OCPP-specific entity mappings
tests/chargers/test_ocpp_charger.py Comprehensive test suite covering all OCPP charger methods and edge cases
custom_components/evse_load_balancer/const.py Added OCPP domain constant for service calls
custom_components/evse_load_balancer/config_flow.py Integrated OCPP as supported charger type in configuration flow
custom_components/evse_load_balancer/chargers/__init__.py Added OCPP charger to factory registration
README.md Updated documentation to include OCPP charger support
Comments suppressed due to low confidence (2)

tests/chargers/test_ocpp_charger.py:164

  • The test references OcppEntityMap.PowerOffered and implements power-based max current calculation, but this functionality is not implemented in the actual OcppCharger class. Either implement this feature or remove the test.
        if key == OcppEntityMap.PowerOffered:

Comment thread custom_components/evse_load_balancer/chargers/ocpp_charger.py
@kljakobsen
Copy link
Copy Markdown

Seems to recognize the charger through the ocpp integration. However I cannot test more than that, since my 2 meters isnt supported and in adv. config i cannot choose correct entity for power consumption.
The entity for current offered (the hard coded 32A) is sensor.charger_current_offered - where charger is the name of OCPP charging device in the OCPP integration.

@kljakobsen
Copy link
Copy Markdown

kljakobsen commented Jul 28, 2025

hi @dirkgroenen

Did a local merge of #44 (ocpp) and #45 (tibber), so i was able to complete the install of EVSE, however there seems to be something wrong. The state of the evse load balancing state becomes unavailable and throws quite a few errors in the log.

evse_aftermerge_state2
evse_aftermerge_state

This error originated from a custom integration.

Logger: custom_components.evse_load_balancer.coordinator
Source: custom_components/evse_load_balancer/coordinator.py:164
integration: EVSE Load Balancer (documentation, issues)
First occurred: 13:18:37 (8 occurrences)
Last logged: 13:18:45

Available current for phase 'l1' is None.Cannot proceed with balancing cycle.

and this one ..

This error originated from a custom integration.

Logger: homeassistant
Source: custom_components/evse_load_balancer/ha_device.py:84
integration: EVSE Load Balancer (documentation, issues)
First occurred: 13:18:37 (156 occurrences)
Last logged: 13:21:14

Error doing job: Exception in callback _TrackTimeInterval._interval_listener() (None)
Traceback (most recent call last):
File "/usr/local/lib/python3.13/asyncio/events.py", line 89, in _run
self._context.run(self._callback, *self._args)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 1664, in _interval_listener
self.hass.async_run_hass_job(self._run_job, dt_util.utcnow(), background=True)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/core.py", line 935, in async_run_hass_job
hassjob.target(*args)
~~~~~~~~~~~~~~^^^^^^^
File "/config/custom_components/evse_load_balancer/coordinator.py", line 198, in _execute_update_cycle
self._async_update_sensors()
~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/config/custom_components/evse_load_balancer/coordinator.py", line 252, in _async_update_sensors
sensor.async_write_ha_state()
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1018, in async_write_ha_state
self._async_write_ha_state()
~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1140, in _async_write_ha_state
self.__async_calculate_state()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1079, in __async_calculate_state
available = self.available # only call self.available once per update cycle
^^^^^^^^^^^^^^
File "/config/custom_components/evse_load_balancer/load_balancer_sensor.py", line 54, in available
return self.state is not None
^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/sensor/init.py", line 546, in state
value = self.native_value
^^^^^^^^^^^^^^^^^
File "/config/custom_components/evse_load_balancer/load_balancer_sensor.py", line 49, in native_value
return self._get_value_from_coordinator()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/config/custom_components/evse_load_balancer/load_balancer_sensor.py", line 58, in _get_value_from_coordinator
return getattr(self._coordinator, self.entity_description.key, None)
File "/config/custom_components/evse_load_balancer/coordinator.py", line 183, in get_load_balancing_state
if self._should_check_charger():
~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/config/custom_components/evse_load_balancer/coordinator.py", line 256, in _should_check_charger
return self._power_allocator.should_monitor()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/config/custom_components/evse_load_balancer/power_allocator.py", line 146, in should_monitor
return len(self._active_chargers) > 0
^^^^^^^^^^^^^^^^^^^^^
File "/config/custom_components/evse_load_balancer/power_allocator.py", line 141, in _active_chargers
if state.charger.can_charge()
~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/config/custom_components/evse_load_balancer/chargers/ocpp_charger.py", line 184, in can_charge
status = self._get_status()
File "/config/custom_components/evse_load_balancer/chargers/ocpp_charger.py", line 156, in _get_status
status = self._get_entity_state_by_key(
OcppEntityMap.StatusConnector,
)
File "/config/custom_components/evse_load_balancer/ha_device.py", line 148, in _get_entity_state_by_key
entity_id = self._get_entity_id_by_key(entity_key)
File "/config/custom_components/evse_load_balancer/ha_device.py", line 84, in _get_entity_id_by_key
raise ValueError(msg)
ValueError: Entity with unique_id ending with 'Status.Connector' not found

@dirkgroenen
Copy link
Copy Markdown
Owner Author

Thanks @kljakobsen! Just pushed a change to the branch which should fix the ValueError: Entity with unique_id ending with 'Status.Connector' not found issue.

The other one (Available current for phase 'l1' is None.Cannot proceed with balancing cycle.) seems to only be logged a few times at around the same time. This gives me the impression it happens around startup, when not all integrations (like Tibber's) are fully ready and exposing the right entities.

Would you mind checking if it works? If not let me know and I'll dive into the Tibber integration branch to try and find the issue there.

@dirkgroenen
Copy link
Copy Markdown
Owner Author

If you have some time to spare, may I ask you @kljakobsen to have another look at this one? 🙏

@dirkgroenen
Copy link
Copy Markdown
Owner Author

Hi @kljakobsen - in case the previous message got lost; would you mind having another test with this such we can continue merging?

@juliusvaart
Copy link
Copy Markdown

I'd love to help testing with our OCPP charger and the DSMR-integration.

With the changes from 5d0a8e9 added (that's the only change?), instead of ValueError: Entity with unique_id ending with 'Status.Connector' not found i'm getting:

ValueError: Entity with unique_id ending with 'Status' not found

This is from the log:

Logger: homeassistant
Bron: custom_components/evse_load_balancer/ha_device.py:84
integratie: EVSE Load Balancer ([documentatie](https://github.com/dirkgroenen/hass-evse-load-balancer), [problemen](https://github.com/dirkgroenen/hass-evse-load-balancer/issues))
Eerst voorgekomen: 09:16:36 (582 gebeurtenissen)
Laatst gelogd: 09:26:04
Error doing job: Exception in callback _TrackTimeInterval._interval_listener() (None)

Traceback (most recent call last):
  File "/usr/local/lib/python3.13/asyncio/events.py", line 89, in _run
    self._context.run(self._callback, *self._args)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 1669, in _interval_listener
    self.hass.async_run_hass_job(self._run_job, dt_util.utcnow(), background=True)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 937, in async_run_hass_job
    hassjob.target(*args)
    ~~~~~~~~~~~~~~^^^^^^^
  File "/config/custom_components/evse_load_balancer/coordinator.py", line 198, in _execute_update_cycle
    self._async_update_sensors()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/config/custom_components/evse_load_balancer/coordinator.py", line 252, in _async_update_sensors
    sensor.async_write_ha_state()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1026, in async_write_ha_state
    self._async_write_ha_state()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1151, in _async_write_ha_state
    self.__async_calculate_state()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1087, in __async_calculate_state
    available = self.available  # only call self.available once per update cycle
                ^^^^^^^^^^^^^^
  File "/config/custom_components/evse_load_balancer/load_balancer_sensor.py", line 54, in available
    return self.state is not None
           ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/sensor/__init__.py", line 560, in state
    value = self.native_value
            ^^^^^^^^^^^^^^^^^
  File "/config/custom_components/evse_load_balancer/load_balancer_sensor.py", line 49, in native_value
    return self._get_value_from_coordinator()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/config/custom_components/evse_load_balancer/load_balancer_sensor.py", line 58, in _get_value_from_coordinator
    return getattr(self._coordinator, self.entity_description.key, None)
  File "/config/custom_components/evse_load_balancer/coordinator.py", line 183, in get_load_balancing_state
    if self._should_check_charger():
       ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/config/custom_components/evse_load_balancer/coordinator.py", line 256, in _should_check_charger
    return self._power_allocator.should_monitor()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/config/custom_components/evse_load_balancer/power_allocator.py", line 146, in should_monitor
    return len(self._active_chargers) > 0
               ^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/evse_load_balancer/power_allocator.py", line 141, in _active_chargers
    if state.charger.can_charge()
       ~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/config/custom_components/evse_load_balancer/chargers/ocpp_charger.py", line 193, in can_charge
    status = self._get_status()
  File "/config/custom_components/evse_load_balancer/chargers/ocpp_charger.py", line 170, in _get_status
    status = self._get_entity_state_by_key(
        OcppEntityMap.Status,
    )
  File "/config/custom_components/evse_load_balancer/ha_device.py", line 148, in _get_entity_state_by_key
    entity_id = self._get_entity_id_by_key(entity_key)
  File "/config/custom_components/evse_load_balancer/ha_device.py", line 84, in _get_entity_id_by_key
    raise ValueError(msg)
ValueError: Entity with unique_id ending with 'Status' not found
Scherm­afbeelding 2025-09-21 om 09 45 51

@juliusvaart
Copy link
Copy Markdown

So, I did some rewriting (with some AI help) and seems to be functioning now. Made a pull request in the ocpp branch: #67

dirkgroenen and others added 3 commits October 18, 2025 12:21
* Use the right status and status_connector

* Improvements for ocpp charger

* Fix for is_charging error in v1.0.5

* Rewrite set current to use number.set_value instead of ocpp service

* Revert ha_device.py

* Added transaction_id OcppEntityMap

* Removed unneeded code for devid

* Used the code from @dirkgroenen to use ChargePointMaxProfile instead of TxProfile

* Change charging profile and add spec

---------

Co-authored-by: Dirk Groenen <dirk@bitlabs.nl>
@dirkgroenen
Copy link
Copy Markdown
Owner Author

Alright @juliusvaart, as mentioned in #67 I merged your changes, fixed the tests and made sure we're in a "green" state again. Reading the other thread it seems like the last thing we were checking was why the "Load Balancer Status" wasn't switching to Monitoring Loads

Asking for the device's entities you returned the following:

Because with the latest code in my fork i get this when calling the device_entities() in dev-tools:

[
  "sensor.evse_load_balancer_load_balancing_state",
  "sensor.evse_load_balancer_available_current_l1",
  "sensor.evse_load_balancer_available_current_l2",
  "sensor.evse_load_balancer_available_current_l3"
]

But, those are the Load Balancer Device entities. I was actually referring to the OCPP charger one so we can see which entities are exposed by the OCPP Charger.

Reason for asking: the load balancer should be looking for entities ending with _status and _status_connector to set the right Load Balancer status. If these are not available, or if the value of these doesn't match with the statuses defined in can_charge() it could explain the problem.

So what we can do next:

  • Upgrade to the latest version of this branch by running the update.install action with the latest commit of this branch
  • restart home assistant
  • plug in your car and check if Load Balancer switces to Monitoring Loads
  • if not: check the _status_ and _status_connector values and see if they match with the ones defined in can_charge().

In the meantime check the logs for any issues.

Hopefully we're only a small step away from having this fixed and working 🙌

@juliusvaart
Copy link
Copy Markdown

juliusvaart commented Oct 19, 2025

With the branch in your repo it also remains on "Awaiting charger".

The entities from OCPP Charger are:

  "sensor.charger_status",
  "sensor.charger_error_code",
  "sensor.charger_status_firmware",
  "sensor.charger_heartbeat",
  "sensor.charger_id_tag",
  "sensor.charger_latency_ping",
  "sensor.charger_latency_pong",
  "sensor.charger_reconnects",
  "sensor.charger_id",
  "sensor.charger_vendor",
  "sensor.charger_model",
  "sensor.charger_serial",
  "sensor.charger_version_firmware",
  "sensor.charger_features",
  "sensor.charger_connectors",
  "sensor.charger_timestamp_config_response",
  "sensor.charger_timestamp_data_response",
  "sensor.charger_timestamp_data_transfer",
  "sensor.charger_current_import",
  "sensor.charger_current_offered",
  "sensor.charger_energy_active_import_register",
  "sensor.charger_power_offered",
  "sensor.charger_temperature",
  "sensor.charger_voltage",
  "sensor.charger_status_connector",
  "sensor.charger_error_code_connector",
  "sensor.charger_stop_reason",
  "sensor.charger_transaction_id",
  "sensor.charger_time_session",
  "sensor.charger_energy_session",
  "sensor.charger_energy_meter_start",
  "switch.charger_charge_control",
  "switch.charger_availability",
  "number.charger_maximum_current",
  "button.charger_reset",
  "button.charger_unlock"

Nothing in the logs, maybe we could add some extra logs in the ocpp_charger.py?

"sensor.charger_status_connector" is "Charging"
"sensor.charger_status" is "Available" (but we only need the connector)


def can_charge(self) -> bool:
        """Return whether the car is connected and charging or accepting charge."""
        status = self._get_status()
        return status in [
            OcppStatusMap.Preparing,
            OcppStatusMap.Charging,
            OcppStatusMap.SuspendedEV,
        ]

_"Preparing", "Charging" and "SuspendedEV" are good statusses, but all statusses when a car is connected are:

  • 'Preparing'
  • 'Charging'
  • 'SuspendedEV'
  • 'SuspendedEVSE'
  • 'Finishing'_

Edit: see that all the right statusses are in car_connected().

@juliusvaart
Copy link
Copy Markdown

Oke, found out that EVSE Load Balancer is not finding the entities. With this _get_status() (from ChatGPT) it works:

from homeassistant.helpers import entity_registry as er

    def _get_status(self) -> str | None:
        """Get the current status of the OCPP charger (connector), with diagnostics."""
        name_or_id = self.device_entry.name or self.device_entry.id

        # 1) Fast path: use your normal key-based lookup (unique_id suffix)
        try:
            val = self._get_entity_state_by_key(OcppEntityMap.StatusConnector)
            _LOGGER.debug("[OCPP %s] Fast-path '%s' → %s",
                          name_or_id, OcppEntityMap.StatusConnector, val)
            return val
        except ValueError:
            _LOGGER.debug("[OCPP %s] Fast-path '%s' not found; scanning registry…",
                          name_or_id, OcppEntityMap.StatusConnector)
        except Exception as e:
            _LOGGER.error("[OCPP %s] Unexpected error reading '%s': %s",
                          name_or_id, OcppEntityMap.StatusConnector, e)

        # 2) Fallback: scan Entity Registry (unique_id OR entity_id)
        try:
            ent_reg = er.async_get(self.hass)
            device_id = self.device_entry.id
            all_on_device = [e for e in ent_reg.entities.values() if e.device_id == device_id]

            # a) unique_id contains "status_connector"
            uid_candidates = [
                e for e in all_on_device
                if "status_connector" in (e.unique_id or "").lower()
            ]

            # b) entity_id ends with "_status_connector" (matches your list: sensor.charger_status_connector)
            eid_candidates = [
                e for e in all_on_device
                if (e.entity_id or "").lower().endswith("_status_connector")
            ]

            candidates = uid_candidates or eid_candidates
            if not candidates:
                _LOGGER.warning(
                    "[OCPP %s] No connector-status entity found by uid or entity_id. "
                    "Entities on device: %s",
                    name_or_id, [e.entity_id for e in all_on_device],
                )
                return None

            # Prefer exact entity_id ending, then uid matches
            def sort_key(e):
                eid = (e.entity_id or "").lower()
                uid = (e.unique_id or "").lower()
                return (
                    0 if eid.endswith("_status_connector") else 1,
                    0 if "status_connector" in uid else 1,
                    eid,
                )

            chosen = sorted(candidates, key=sort_key)[0]
            state_obj = self.hass.states.get(chosen.entity_id)
            state_val = None if state_obj is None else state_obj.state

            _LOGGER.debug(
                "[OCPP %s] Using connector status from %s (uid=%s) → %s",
                name_or_id, chosen.entity_id, chosen.unique_id, state_val,
            )
            return state_val
        except Exception as e:
            _LOGGER.error("[OCPP %s] Error scanning registry for connector status: %s",
                          name_or_id, e)
            return None

Same issue with get_current_limit(). This ChatGPT thing works (also with from homeassistant.helpers import entity_registry as er):

    def get_current_limit(self) -> dict[Phase, int] | None:
        """Get the current limit of the charger in amps (safe 6A fallback)."""
        name_or_id = self.device_entry.name or self.device_entry.id

        # 1) Try the standard unique_id-based lookup
        try:
            state = self._get_entity_state_by_key(OcppEntityMap.MaximumCurrent)
            source = f"unique_id key '{OcppEntityMap.MaximumCurrent}'"
        except ValueError:
            # 2) Fallback: look for a number.*_maximum_current entity on this device
            try:
                ent_reg = er.async_get(self.hass)
                dev_id = self.device_entry.id
                candidates = [
                    e for e in ent_reg.entities.values()
                    if e.device_id == dev_id
                    and (e.entity_id or "").startswith("number.")
                    and (e.entity_id or "").lower().endswith("_maximum_current")
                ]
                if not candidates:
                    _LOGGER.warning(
                        "[OCPP %s] Maximum current entity not found. "
                        "Using safe fallback 6A.",
                        name_or_id,
                    )
                    return dict.fromkeys(Phase, 6)

                chosen = sorted(candidates, key=lambda e: e.entity_id)[0]
                state_obj = self.hass.states.get(chosen.entity_id)
                state = None if state_obj is None else state_obj.state
                source = f"entity_id scan '{chosen.entity_id}'"
                _LOGGER.debug("[OCPP %s] Fallback picked %s with state=%s",
                              name_or_id, chosen.entity_id, state)
            except Exception as e:
                _LOGGER.warning(
                    "[OCPP %s] Error scanning for maximum current entity (%s). "
                    "Using safe fallback 6A.",
                    name_or_id, e
                )
                return dict.fromkeys(Phase, 6)

        # 3) Parse numeric value
        try:
            current_value = int(float(state))
            _LOGGER.debug("[OCPP %s] maximum_current=%s A (source=%s)",
                          name_or_id, current_value, source)
            return dict.fromkeys(Phase, current_value)
        except (ValueError, TypeError) as e:
            _LOGGER.warning(
                "[OCPP %s] Failed to parse maximum_current value '%s' (%s). "
                "Using safe fallback 6A.",
                name_or_id, state, e
            )
            return dict.fromkeys(Phase, 6)

Only issue is with the TransactionID, it defaults to 0 when restarting HA and there is an issue with TxDefaultProfile vs TxProfile.

@dirkgroenen
Copy link
Copy Markdown
Owner Author

Sorry for the late reply @juliusvaart - it's been very bust recently so had to park the side for a bit.

Oke, found out that EVSE Load Balancer is not finding the entities. With this _get_status() (from ChatGPT) it works:

Did you found out because of the log messages telling there entity wasn't found? Cause the code should throw exceptions if entities doen't exist.

I've pushed a change to the ha_device.py logic to print all available entities during startup. Your GPT generated code is in essence not doing much different, apart from a lookup through the entire registry upon each invocation. That makes me wonder if the entities are not available during startup, which we should be able to find with the new log.

Can you update your code, enable the component's debug logging and restart home assistant? It should print a log line starting with Refreshed entities: [xxx] and a whole bunch of entities. Please provide me with that snippet (or just the whole debug logs after running for a few minutes).

Thanks!

@juliusvaart
Copy link
Copy Markdown

Hello @dirkgroenen, sorry for the late reply. I'm too busy to put a lot of effort in getting this right. My Python skills are lacking (and AI sucks if your skills are lacking ;-).

When i've got time i'll dive into this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CHARGER] OCPP integration support

4 participants