Skip to content

[Validation only] PR #2122 synced with upstream main - #1

Draft
matiebird wants to merge 19 commits into
mainfrom
fix/reconnect-session-ownership
Draft

matiebird wants to merge 19 commits into
mainfrom
fix/reconnect-session-ownership

Conversation

@matiebird

@matiebird matiebird commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Validation only — do not merge

This draft runs the existing GitHub Actions workflows against the synchronized candidate for lbbrhzn#2122. Neither the fork's main branch nor upstream main has been changed by this validation PR.

Candidate and source identity

Results at last inspection

Fork workflow: https://github.com/matiebird/ocpp/actions/runs/35397799877

Check Observed result
Pre-commit, all files Passed (job 105770508397)
Hassfest Passed (job 105770508316)
Pull Request Docs Check Passed (run 35397800079)
Full Python 3.14 test suite Still in progress (job 105770508015); requirements installed successfully. No final test count or coverage result is claimed.
HACS Failed (job 105770508351): the fork has no valid repository topics and has issues disabled. Its integration_manifest and hacsjson validations passed.

The existing 95% coverage requirement and slow reconnect tests remain enabled. No workflow files, test thresholds, repository settings, permissions, or secrets were changed to obtain these results.

Upstream checks are separate

The new upstream Linting run (35397728191) and Pull Request Docs Check run (35397728328) report action_required. The upstream Linting run has no jobs. Fork-side results do not replace the upstream approval requirement or establish that upstream CI has passed.

This draft remains open only as the validation record while the test run is unfinished. It must not be merged into the fork's main branch.

kwilson9 and others added 19 commits September 4, 2026 07:49
…energy (lbbrhzn#2115)

* Switch to session-energy mode instead of publishing negative session energy

Some chargers report a lifetime register in StartTransaction but
session-relative energy in the MeterValues that follow. The
`meter_start == 0` detection never fires for them, so the else-branch
derives `session = EAIR - meter_start` and publishes a large negative
value to a total_increasing sensor. Nothing is logged when it happens.

A transaction-bound EAIR sample below meter_start cannot be a lifetime
register reading, so treat it as proof the charger reports session
energy: set _charger_reports_session_energy and use the sample as-is.
Chargers that do report a lifetime register are unaffected, since
`derived` is never negative for them.

Not scoped at the candidate pre-selector deliberately: rejecting
below-baseline EAIR globally would break the existing
_charger_reports_session_energy path, where a session legitimately
starts at 0 and counts up.

Adds a regression test covering a lifetime meter_start followed by
session-relative MeterValues. It fails on main with
"session energy went negative: -4076.334" and passes with this change.
The existing test_eair_monotonic_increments_single_connector does not
catch this: its fixture is monotonically increasing, so the derived
value is never negative and the branch is never reached.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Move the mode switch ahead of skip_eair

Addresses CodeRabbit review on lbbrhzn#2115.

skip_eair is evaluated before the session-handling block, so on the first
below-baseline sample _charger_reports_session_energy was still False and
the session-relative value was written to the lifetime
Energy.Active.Import.Register metric before the mode switch ran.

Detect the below-baseline sample earlier, immediately before skip_eair is
computed. The existing session-energy branch then handles the sample, so
the derivation branch needs no change at all: this commit is a pure
addition and modifies no existing line.

The regression test now also asserts the lifetime register is not
overwritten with the session value. Note the check is made only after the
sample is processed: _metrics is a defaultdict, so reading a key
beforehand creates it and alters the behaviour under test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: kwilson9 <190990796+kwilson9@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…bbrhzn#2071)

* build(deps): update setuptools requirement from >=83.0.0 to >=84.0.0

Updates the requirements on [setuptools](https://github.com/pypa/setuptools) to permit the latest version.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](pypa/setuptools@v83.0.0...v84.0.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-version: 84.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* update hacs version

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: drc38 <20024196+drc38@users.noreply.github.com>
* Allow removing a charge point device via the UI

Implement async_remove_config_entry_device so that deleting a charger's
device in Settings -> Devices removes the charge point from the entry's
CONF_CPIDS. The update listener reloads the integration, so the device is
not re-created on setup; a charger reconnecting later goes through the
normal discovery flow instead of being silently re-added.

The central system device (and anything matching no configured charge
point) is refused, so it can never be removed by accident.

Adds tests for the happy path (sibling charge point survives, no
re-creation), the central-system refusal, and removing the last charge
point (chargerless entry still loads/unloads), plus a user-guide section.

Refs lbbrhzn#1885

* Reject central-system identifier collisions in device removal

When CONF_CSID equals a charge point's cp_id or cpid, setup registers
both devices under the same identifier and Home Assistant merges them
into a single device.  The removal hook would then match it as a charge
point and silently drop the entry from CONF_CPIDS.

Refuse to remove any device carrying the central system identifier, and
add a regression test for the collision.

* test: cover unknown device refusal in async_remove_config_entry_device

* test: snapshot full config_entry.data on removal refusal

* test: migrate device registry usage and fix deepcopy of config_entry.data

* test: add entity rebuild assertion and ruff format async_get_devices

* test: probe the surviving sibling's entity for the reload, not the removed one

* style: apply ruff format to async_get_devices assertions
…bbrhzn#2129)

Time.Session has no device class, so its fallback unit is None, and the
unit getter replaced a known unit with that fallback whenever the live
metric had no unit yet: before the charger connected, while it was
offline, and on every restart or reload before a transaction ran. Home
Assistant saw a measurement sensor's unit change from min to blank and
raised a units_changed repair each time.

Both protocol handlers compute the session time in minutes, so the sensor
now reports minutes unconditionally. The debugging guide explains the
one-time statistics repair for history recorded with a blank unit.

Closes lbbrhzn#1938

Co-authored-by: KingHavok <shane@shanewilliams.com.au>
Bumps [pytest-homeassistant-custom-component](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component) from 0.13.361 to 0.13.363.
- [Release notes](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/releases)
- [Changelog](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/blob/master/CHANGELOG.md)
- [Commits](MatthewFlamm/pytest-homeassistant-custom-component@0.13.361...0.13.363)

---
updated-dependencies:
- dependency-name: pytest-homeassistant-custom-component
  dependency-version: 0.13.363
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… stops (lbbrhzn#2128)

The integration allocates 1.6 transaction ids itself as int(time.time()),
and three things leaned on that: two connectors starting in the same
second shared an id, so the stop for one was applied to whichever came
first in the active map; a StopTransaction matching nothing was applied
to connector 1, ending a session that may still have been running there;
and the id doubled as the session's start epoch, so an id adopted from a
charger's MeterValues after a restart made the timer count from an
arbitrary reference.

Ids are now max(now, last + 1) per charge point, with the floor raised
by every id seen, restored or persisted. The session start is kept
separately and persisted per connector in a small HA Store with the last
allocated id, loaded before the message loop; an estimated start is
marked on the session time sensor. An unknown stop is never guessed: the
live connectors are held with their timers frozen until the charger says
something about them, a pending stop is applied only to the one connector
shown to have ended, and evidence that a candidate ended by another route
discards the stop instead. Remote stops are refused for held connectors
and the Charge Control switch is unavailable for them. Closing values
speak only for the transaction whose id they carry, are never adopted as
a live session, and no longer reset the timer.

Closes lbbrhzn#2123

Co-authored-by: KingHavok <shane@shanewilliams.com.au>
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.16.5 to 0.16.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](astral-sh/ruff@0.16.5...0.16.6)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.16.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Create one flat Maximum Current entity per charger and remove obsolete
connector entities, preserving existing flat identities and restore data.
Route the slider through a station-only API: OCPP 1.6 requires
ChargePointMaxProfile; OCPP 2.0.1 keeps its current station behavior.
The set_charge_rate action retains its existing fallback chain.

Document the breaking entity migration and removal of the 1.6 slider
fallback, including single-connector chargers. Add regression coverage
for registry cleanup, restoration, protocol errors and service behavior.

Co-authored-by: KingHavok <shane@shanewilliams.com.au>
Bumps [pytest-homeassistant-custom-component](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component) from 0.13.363 to 0.13.364.
- [Release notes](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/releases)
- [Changelog](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/blob/master/CHANGELOG.md)
- [Commits](MatthewFlamm/pytest-homeassistant-custom-component@0.13.363...0.13.364)

---
updated-dependencies:
- dependency-name: pytest-homeassistant-custom-component
  dependency-version: 0.13.364
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…brhzn#2141)

TransactionEvent (lbbrhzn#2135): keep a per-transaction seqNo guard so stale or replayed events cannot resurrect an ended transaction; resolve a missing connectorId only when the EVSE topology makes it unambiguous; apply offline events only to the transaction currently displayed; hold events that arrive before the inventory settles and replay them once the EVSE map is known, falling back to dynamic allocation when it never is; treat an online event for another transaction as current evidence, except an Ended or an Updated stamped no later than the displayed transaction's latest event, which is replayed history.

Rate unit (lbbrhzn#2134): read SmartChargingCtrlr/RateUnit from the base report and convert managed limits for W-only or A-only stations.

Foundations for a transaction-scoped control: the electrical helpers move to the base charge point, reconnect demotes held events while BootNotification resets transaction ordering, and both handlers report transaction start and end to an optional hook.

Co-authored-by: KingHavok <shane@shanewilliams.com.au>
Bumps [pytest-homeassistant-custom-component](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component) from 0.13.364 to 0.13.365.
- [Release notes](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/releases)
- [Changelog](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/blob/master/CHANGELOG.md)
- [Commits](MatthewFlamm/pytest-homeassistant-custom-component@0.13.364...0.13.365)

---
updated-dependencies:
- dependency-name: pytest-homeassistant-custom-component
  dependency-version: 0.13.365
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…without Authorize (lbbrhzn#2147)

* ocpp16: charger level id_tag sensor not updating on StartTransaction without Authorize

When an OCPP 1.6 charger performs local authorization it skips the Authorize message and sends StartTransaction directly.
The on_start_transaction handler now also sets charger-level (connector 0) id_tag and not only the connector-level id_tag

* Only set charger IdTag from StartTransaction on single-connector chargers.

A multi-connector station tag must not be overwritten by whichever plug started last.
Replace fenced terminology in timeout messages, the initial-start comment,
and lifecycle documentation. Wording only; no control-flow changes.

Addresses review feedback on lbbrhzn#2122.
Integrate lbbrhzn/ocpp main at 0a3b753:
- lbbrhzn#2146: update pytest-homeassistant-custom-component to 0.13.365.
- lbbrhzn#2147: update the charger-level ID-tag sensor for locally authorized starts.

Preserve PR lbbrhzn#2122 reconnect ownership, bounded retirement, regression tests,
and blocked terminology without rewriting the existing commits.
@matiebird
matiebird deployed to continuous-integration September 18, 2026 21:38 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
continuous-integration — e44aaf4c Deployed Sep 18, 2026 by matiebird via Run tests #1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants