Add OCPP charger support (closes #44)#89
Open
kljakobsen wants to merge 2 commits into
Open
Conversation
Implements a charger backend for OCPP-managed charge points via the lbbrhzn/ocpp integration. Tested end-to-end on a Garo CS using a Shelly 3EM as the meter source. Design notes for anyone maintaining this: * OCPP unique-ids are dot-separated (e.g. number.ocpp.<cpid>.maximum_current), so the base HaDevice._get_entity_id_by_key helper's underscore-suffix match never works. OcppCharger ships its own dot-aware _get_ocpp_entity_id that matches by HA domain and by checking the key against the dot-separated unique-id parts. * IEC 61851 forbids charging below 6A. The upstream allocator can emit any value 0..max during overcurrent events. set_current_limit snaps any request in (0, 6) to 0A, which OCPP interprets as "pause", and the charger transitions to SuspendedEVSE. * The pause must not look like a manual override. The upstream power_allocator.detect_manual_override() compares the slider (0A after the snap) against last_applied_current (the pre-snap value like 4A), would see a mismatch and adopt 0A as the new ceiling -- permanently locking the charger off. OcppCharger tracks _last_requested and, while the charger is in SuspendedEVSE, get_current_limit reports that pre-snap value back so the override detector sees no change. * Profile replacement alone does not resume on most chargers. Once SuspendedEVSE, the contactor is de-energised and the firmware waits for a fresh RemoteStartTransaction. When set_current_limit is called with >=6A while paused, we push the new profile first, then toggle the charge_control switch off -> on to issue RemoteStop + RemoteStart and force a new transaction under the just-updated profile. * OCPP exposes one charge-point-wide current setting, so has_synced_phase_limits() is always True and set_current_limit uses the lowest of the per-phase values. * set_phase_mode is a validated no-op (OCPP 1.6 has no standardised single/three-phase toggle). Validation: * All 33 new OcppCharger tests pass. * All 138 charger tests pass. * ruff check . clean; ruff format --check . clean. * Verified end-to-end on real hardware (Garo CS via lbbrhzn/ocpp + Shelly 3EM): multiple pause/resume cycles in a single session, no "Manual override detected" log lines during pauses, contactor reliably re-energised on resume. Files changed: custom_components/evse_load_balancer/chargers/ocpp_charger.py (new) custom_components/evse_load_balancer/chargers/__init__.py (register) custom_components/evse_load_balancer/config_flow.py (picker) custom_components/evse_load_balancer/const.py (domain) tests/chargers/test_ocpp_charger.py (new) README.md (table)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements a charger backend for OCPP-managed charge points via the lbbrhzn/ocpp integration. Tested end-to-end on a Garo Entity Pro using a Shelly 3EM as the meter source (as custom meter).
Design notes for anyone maintaining this:
OCPP unique-ids are dot-separated (e.g. number.ocpp..maximum_current), so the base HaDevice._get_entity_id_by_key helper's underscore-suffix match never works. OcppCharger ships its own dot-aware _get_ocpp_entity_id that matches by HA domain and by checking the key against the dot-separated unique-id parts.
IEC 61851 forbids charging below 6A. The upstream allocator can emit any value 0..max during overcurrent events. set_current_limit snaps any request in (0, 6) to 0A, which OCPP interprets as "pause", and the charger transitions to SuspendedEVSE.
The pause must not look like a manual override. The upstream power_allocator.detect_manual_override() compares the slider (0A after the snap) against last_applied_current (the pre-snap value like 4A), would see a mismatch and adopt 0A as the new ceiling -- permanently locking the charger off. OcppCharger tracks _last_requested and, while the charger is in SuspendedEVSE, get_current_limit reports that pre-snap value back so the override detector sees no change.
Profile replacement alone does not resume on most chargers. Once SuspendedEVSE, the contactor is de-energised and the firmware waits for a fresh RemoteStartTransaction. When set_current_limit is called with >=6A while paused, we push the new profile first, then toggle the charge_control switch off -> on to issue RemoteStop + RemoteStart and force a new transaction under the just-updated profile.
OCPP exposes one charge-point-wide current setting, so has_synced_phase_limits() is always True and set_current_limit uses the lowest of the per-phase values.
set_phase_mode is a validated no-op (OCPP 1.6 has no standardised single/three-phase toggle).
Validation:
Files changed:
custom_components/evse_load_balancer/chargers/ocpp_charger.py (new)
custom_components/evse_load_balancer/chargers/init.py (register)
custom_components/evse_load_balancer/config_flow.py (picker)
custom_components/evse_load_balancer/const.py (domain)
tests/chargers/test_ocpp_charger.py (new)
README.md (table)