Skip to content

Fix: Calculate real-time Session Energy for phased MeterValues#1920

Closed
ric866 wants to merge 13 commits intolbbrhzn:mainfrom
ric866:fix-session-energy-multiphased-chargers
Closed

Fix: Calculate real-time Session Energy for phased MeterValues#1920
ric866 wants to merge 13 commits intolbbrhzn:mainfrom
ric866:fix-session-energy-multiphased-chargers

Conversation

@ric866
Copy link
Copy Markdown
Contributor

@ric866 ric866 commented Mar 17, 2026

This PR fixes a bug where the Session Energy sensor fails to update during an active charging session for chargers that send phase-specific energy readings (e.g., Energy.Active.Import.Register.L1).

For these chargers, the Session Energy sensor currently remains at 0 for the duration of the charge and only populates with the final value when the StopTransaction payload is received.

Root Cause :
In on_meter_values, the real-time session energy math (Current - Start) is currently trapped inside the if phase is None: block.

If a charger sends a phase-tagged main energy register, it bypasses this block and gets passed to process_phases() via the unprocessed list. While process_phases() correctly aggregates the phases and updates the main lifetime energy sensor, it lacked the logic to simultaneously update the active Session Energy sensor.

Fix :
Added the standard session energy calculation directly to the end of process_phases().

  • Refactored the final unit assignment to use a final_value variable.
  • Added a check to verify an active transaction.
  • If active, it derives the real-time session energy (final_value - meter_start) and updates the metric so the HA dashboard graph updates continuously during the charge.

Testing :

  • Verified that single-phase chargers sending L1 energy registers now correctly increment the Session Energy sensor in real-time upon receiving periodic MeterValues.
  • Verified this does not interfere with chargers that natively report session energy (_charger_reports_session_energy).

Summary by CodeRabbit

  • Bug Fixes

    • Unified metric handling to ensure measurements use a single finalized value and unit before updating stored metrics.
    • Improved session-energy reporting for active transactions: initializes missing meter-start baselines and computes session energy as a unit-consistent delta when chargers omit direct session totals.
    • Fixed a metric-lookup issue affecting transaction session derivation.
  • Tests

    • Added tests verifying session-energy baseline initialization and real-time delta calculation from meter readings.

ric866 added 2 commits March 17, 2026 16:06
Refactor metric unit handling to compute final_value/final_unit once instead of repeating assignments for power and energy conversions. Add logic to amend session energy when the charger doesn't report it directly: on Energy.Active.Import.Register updates (DEFAULT_MEASURAND) and when not _charger_reports_session_energy, detect an active transaction, use meter_start and session_energy metrics to initialize baseline if missing, and compute session energy as (current_total - start_total) with unit parity checks and rounding.
@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 16:16 — with GitHub Actions Inactive
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors per-phase metric assignment to compute a consolidated final value/unit, fixes a meter-start metric lookup bug, and adds session-energy derivation (or meter_start initialization) during phase aggregation when the charger does not report session energy directly.

Changes

Cohort / File(s) Summary
Core logic
custom_components/ocpp/chargepoint.py
Reformatted phase-average/sum calculations, refactored metric assignment to compute final_value/final_unit once, fixed meter-start lookup key for EAIR session derivation, and added session-energy derivation (uses csess.meter_start baseline or initializes it when None) during process_phases.
Tests
tests/test_charge_point_core.py
Added two tests: test_process_phases_calculates_session_energy (derives session energy from existing meter_start baseline + EAIR) and test_process_phases_initializes_session_energy_baseline (initializes meter_start and leaves session_energy at 0.0 when baseline missing).

Sequence Diagram(s)

sequenceDiagram
    participant Charger as Charger
    participant Processor as ChargePoint.process_phases
    participant Metrics as MetricsStore
    participant Session as CSess

    Charger->>Processor: send MeasurandValue(s) (EAIR / phases)
    Processor->>Metrics: lookup per-connector metrics (meter_start, session_energy, etc.)
    Processor->>Processor: compute per-phase final_value / final_unit
    alt csess has transaction and meter_start exists
        Processor->>Session: calculate session_energy = final_value - meter_start
        Processor->>Metrics: write updated `session_energy` and main register
    else meter_start is None
        Processor->>Metrics: set meter_start = final_value
        Processor->>Metrics: set session_energy = 0.0
    end
    Processor->>Metrics: persist consolidated metric (target_cid, metric)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • drc38

Poem

"I nibbled on code by soft moonlight,
Phases and meters dancing bright.
Baselines set and lookups made true,
Session energy hops into view.
A merry rabbit cheers—metrics renew! 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix: Calculate real-time Session Energy for phased MeterValues' accurately summarizes the main change: enabling real-time session energy calculation for chargers that send phase-specific energy readings, which is the core bug fix described in the PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 16:17 — with GitHub Actions Inactive
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 94.90132% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.95%. Comparing base (7ceb78f) to head (43654ad).

Files with missing lines Patch % Lines
custom_components/ocpp/chargepoint.py 94.90% 31 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1920      +/-   ##
==========================================
- Coverage   94.99%   94.95%   -0.04%     
==========================================
  Files          12       12              
  Lines        2975     2992      +17     
==========================================
+ Hits         2826     2841      +15     
- Misses        149      151       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 16:40 — with GitHub Actions Inactive
@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 16:43 — with GitHub Actions Inactive
@ric866 ric866 marked this pull request as ready for review March 17, 2026 16:48
@ric866
Copy link
Copy Markdown
Contributor Author

ric866 commented Mar 17, 2026

While looking through the process_phases neutral voltage overwrite that my SyncEV gets itself into, I noticed why Energy Session doesn't update during the charge (values are currently only processed into Session values if they are Phase : None).

This is my attempt at a fix inside the phase processing function.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@custom_components/ocpp/chargepoint.py`:
- Line 836: The lookup for the stored metric uses the wrong key type: change the
_metrics.get call to use the Enum's string value so the stored metric is found;
specifically, when retrieving ms_metric use csess.meter_start.value (same
pattern as csess.session_energy.value) so
_ConnectorAwareMetrics.get((target_cid, csess.meter_start.value)) returns the
stored metric and the session energy calculation runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b917d8e-0254-4645-8319-b304e9d0e615

📥 Commits

Reviewing files that changed from the base of the PR and between bd0aa3d and 5bc76f7.

📒 Files selected for processing (2)
  • custom_components/ocpp/chargepoint.py
  • tests/test_charge_point_core.py

@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 16:55 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/test_charge_point_core.py (1)

367-393: Add one test for the inactive-transaction guard path.

These additions cover the two positive branches well, but the new guard (active transaction required) is not exercised yet. A no-transaction test would lock in that session_energy is not derived when transaction state is missing/false.

✅ Suggested test addition
+def test_process_phases_does_not_derive_session_energy_without_active_transaction(hass):
+    cp = _mk_cp(hass)
+    target_cid = 1
+
+    cp._metrics[(target_cid, csess.meter_start.value)].value = 100.0
+    cp._metrics[(target_cid, csess.meter_start.value)].unit = HA_ENERGY_UNIT
+    cp._metrics[(target_cid, csess.session_energy.value)].value = 0.0
+    cp._metrics[(target_cid, csess.session_energy.value)].unit = HA_ENERGY_UNIT
+    cp._metrics[(target_cid, csess.transaction_id.value)].value = None  # inactive
+
+    bucket = [
+        _mv("Energy.Active.Import.Register", 105.0, phase="L1", unit=HA_ENERGY_UNIT)
+    ]
+    cp.process_phases(bucket, connector_id=target_cid)
+
+    assert cp._metrics[(target_cid, csess.session_energy.value)].value == 0.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_charge_point_core.py` around lines 367 - 393, Add a new unit test
that exercises the "active transaction required" guard by creating a ChargePoint
via _mk_cp, ensuring the transaction flag/metric (csess.transaction_id) is
absent or falsy, setting meter_start to None and session_energy to None, then
calling cp.process_phases with a single L1 Energy.Active.Import.Register reading
(use _mv and HA_ENERGY_UNIT) and asserting that meter_start and session_energy
remain None (or that session_energy is not derived). Reference
cp.process_phases, cp._metrics, csess.transaction_id, csess.meter_start,
csess.session_energy, _mk_cp, _mv and HA_ENERGY_UNIT so the new test mirrors the
existing tests but with no active transaction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_charge_point_core.py`:
- Around line 367-393: Add a new unit test that exercises the "active
transaction required" guard by creating a ChargePoint via _mk_cp, ensuring the
transaction flag/metric (csess.transaction_id) is absent or falsy, setting
meter_start to None and session_energy to None, then calling cp.process_phases
with a single L1 Energy.Active.Import.Register reading (use _mv and
HA_ENERGY_UNIT) and asserting that meter_start and session_energy remain None
(or that session_energy is not derived). Reference cp.process_phases,
cp._metrics, csess.transaction_id, csess.meter_start, csess.session_energy,
_mk_cp, _mv and HA_ENERGY_UNIT so the new test mirrors the existing tests but
with no active transaction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 64356d42-2635-4b21-bb5c-5f0455b1fd7d

📥 Commits

Reviewing files that changed from the base of the PR and between 5bc76f7 and f913c06.

📒 Files selected for processing (1)
  • tests/test_charge_point_core.py

@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 17:00 — with GitHub Actions Inactive
@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 17:03 — with GitHub Actions Inactive
@ric866 ric866 force-pushed the fix-session-energy-multiphased-chargers branch from 88daaaa to 6dad97e Compare March 17, 2026 17:05
@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 17:05 — with GitHub Actions Inactive
@ric866 ric866 force-pushed the fix-session-energy-multiphased-chargers branch from 6dad97e to 22bc955 Compare March 17, 2026 17:06
@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 17:06 — with GitHub Actions Inactive
@ric866 ric866 force-pushed the fix-session-energy-multiphased-chargers branch from 22bc955 to 8e942c9 Compare March 17, 2026 17:08
@ric866 ric866 temporarily deployed to continuous-integration March 17, 2026 17:08 — with GitHub Actions Inactive
@drc38
Copy link
Copy Markdown
Collaborator

drc38 commented Mar 17, 2026

How does a 3 phase charger provide the "Energy.Active.Import.Register", does it report it for each phase or in totality? If this is only required for single phase chargers that include phase info, prior to the current logic that only processes phase None could you simply update the measurand phase to None and avoid adding all the extra logic?

@ric866
Copy link
Copy Markdown
Contributor Author

ric866 commented Mar 18, 2026

How does a 3 phase charger provide the "Energy.Active.Import.Register", does it report it for each phase or in totality? If this is only required for single phase chargers that include phase info, prior to the current logic that only processes phase None could you simply update the measurand phase to None and avoid adding all the extra logic?

Hi @drc38, I did a bit of digging into this... and I prefer your solution (simple is better for me)... But... It might be a problem for chargers taking liberties with the protocol, now, or in future, (even though as far as I can tell, it's only lazy single phase chargers that currently tag phase onto "Energy.Active.Import.Register").

Looking at the OCPP1.6 specification (ocpp-1.6 edition 2), phase is optional on seemingly all measurands, but the "Energy.Active.Import.Register" is defined in Section 7.31, as the "Numerical value read from the 'active electrical energy' (Wh or kWh) register of the (most authoritative) electrical meter measuring energy imported (from the grid supply)".

Suggesting but not stating it should not be tagged with a phase, though 3 phase chargers are capable of measuring individually.

I've raised PR #1921, but it fails one of the tests, owing to the testing sending all three phases. If that test was derived from a real world charger / scenario, IMHO we should definitely go with this extra logic to make sure all cases / weirdness is correctly handled.

Kind regards

Ric

@ric866 ric866 temporarily deployed to continuous-integration March 18, 2026 18:38 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@custom_components/ocpp/chargepoint.py`:
- Around line 825-855: In process_measurands, the dictionary lookup for the
meter-start metric uses csess.meter_start (the enum) instead of
csess.meter_start.value, causing a new Metric key to be created and breaking
baseline initialization; change the lookup that sets ms_metric from using
(target_cid, csess.meter_start) to (target_cid, csess.meter_start.value) so it
matches other usages (e.g., csess.session_energy.value) and correctly retrieves
the existing metric for baseline math.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3de367c-37e1-4594-b8ca-f48f5db37998

📥 Commits

Reviewing files that changed from the base of the PR and between f913c06 and e43b40b.

📒 Files selected for processing (2)
  • custom_components/ocpp/chargepoint.py
  • tests/test_charge_point_core.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_charge_point_core.py

@ric866 ric866 temporarily deployed to continuous-integration March 19, 2026 09:00 — with GitHub Actions Inactive
@ric866 ric866 force-pushed the fix-session-energy-multiphased-chargers branch from c8927e5 to 829249c Compare March 19, 2026 09:02
@ric866 ric866 temporarily deployed to continuous-integration March 19, 2026 09:02 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@custom_components/ocpp/chargepoint.py`:
- Around line 823-856: process_measurands() currently normalizes measurand,
unit, value and context into local vars but still appends the original raw
sampled_value to unprocessed, which causes phase-tagged
Energy.Active.Import.Register (EAIR) entries to be dropped or aggregated with
wrong units in process_phases(); instead, construct and append a normalized
MeasurandValue(measurand, value, phase, unit, context, location) (with
DEFAULT_MEASURAND, converted numeric value and resolved unit when applicable)
into unprocessed so downstream process_phases() and the session-energy logic
(which checks DEFAULT_MEASURAND and _charger_reports_session_energy) receive the
normalized entry and update ms_metric/se_metric correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6ad0722-c1ea-475e-9482-3eedfefb8ff2

📥 Commits

Reviewing files that changed from the base of the PR and between c8927e5 and 829249c.

📒 Files selected for processing (1)
  • custom_components/ocpp/chargepoint.py

@ric866 ric866 temporarily deployed to continuous-integration March 19, 2026 15:19 — with GitHub Actions Inactive
@ric866 ric866 temporarily deployed to continuous-integration March 19, 2026 18:06 — with GitHub Actions Inactive
ric866 added a commit to ric866/hassio-ocpp that referenced this pull request Mar 20, 2026
PR lbbrhzn#1920 has got into a mess. This is the cleaned up version.
@ric866 ric866 closed this Mar 20, 2026
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.

2 participants