Skip to content

fix(easee_cloud): report Easee's load balancing below FTW's order (1.4.1) - #194

Open
frahlg wants to merge 4 commits into
mainfrom
easee-allocated-limit
Open

frahlg wants to merge 4 commits into
mainfrom
easee-allocated-limit

Conversation

@frahlg

@frahlg frahlg commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Problem

Göran (@segran2) traced an Easee Home on 23 October 2026 with every FTW driver POST logged (srcfl/ftw#1763). FTW ordered 15 A and the charger read back 15 A (observation 48). Easee's circuit allocation (observations 70–72) and the current it signalled to the car (114, the pilot) were 12 A at the start, then 14 A and 13 A while the home battery charged. The car took 8.2–8.9 kW instead of 10.4 kW. FTW sent no lower order, yet it said "Not following". ReasonForNoCurrent (96) read 77, "limited by charger max current", with a time older than the cuts.

Change

easee_cloud 1.4.0 → 1.4.1. This takes the work in #172 and #183 onto 1.4.0 and its SignalR stream (#192).

  • Read 70–72 and 114, by REST and from the stream.
  • Emit current_limit_a: the signalled current, while the car charges, the charger reads back FTW's last order (48 equals the last write), and the signalled current is more than half an amp below it. A value counts only when it answers that order: Easee recorded it at or after the order, or the order has stood 30 s. Easee records a value when it changes, so an unchanged pilot keeps an older time.
  • Emit current_limited_by = "load_balancer" only when the allocation is below the order too and no higher than the signalled current, so it explains the limit, and the charger's own static limit (device_limit_a) does not. A three-phase charger uses the lowest allocation of the phases in use (a phase with none is not in use); a one-phase charger the highest, since the driver does not know its phase, and never an allocation of 0. Otherwise the cause stays nil.
  • When Easee lets the car take the order again, keep the last limit for up to 20 s while the car still draws below the order. The cloud reports the car's new draw a few seconds later; without this Core saw a car below the order with no limit and said "Not following" in between.
  • Do not use ReasonForNoCurrent as a cause. fix(easee_cloud): report a load-balancer limit while the car charges #172 kept an old load-balancing reason while the measured current fell 2 A short; in Göran's trace the reason was older than the cuts and named the charger's own limit, so a reason is not proof. fix(easee_cloud): report a load-balancer limit while the car charges #172's label fixes for reasons 6, 8–11, 25–30 and 77 are kept; they match Easee's table.
  • Port fix(easee_cloud): reject out-of-order power observations #183's REST ordering check (Göran's code and tests): a REST reply is not used when its power time goes back from the last reply used or gives another power value at the same instant, or, while a car is connected, has no readable power time. Times compare as instants across UTC offsets; Göran's parser replaces the UTC-only one, so the stream reads any offset too. A disconnect starts the order again.
  • Changes to fix(easee_cloud): reject out-of-order power observations #183: replies held back one after another for 30 s mean the time itself is wrong, so an older time then starts the order again and unreadable times are used as they come until a readable one arrives. Without that, one stamp from the future, or a format the parser does not know, would keep the driver silent until the car unplugs. A late "disconnected" reply is held to the same order. A gap of more than 30 s between held-back replies, such as an outage, starts the count again (a single failed poll does not), and a held-back reply does not drop the session proof (each drop cost one of Easee's ten session checks an hour). The 1.4.0 stream test that guards future stamps now expects the late reply to be held for 30 s, not used at once.
  • Add both fields to the EV keys in spec/host-api-profile.json and to the control feedback section of docs/WRITING-A-DRIVER.md. Add Easee's enumeration page to upstream_docs.

Tests

Each new test failed on main first:

  • test_easee_cloud_allocated_limit.lua (new): Göran's trace at 12, 15, 14 and 13 A; a limit without a shown cause (allocation at the order, missing, above the pilot, old reason 29, reason 77); lowest and highest phase; at or within half an amp of the order, not charging, zero pilot, no readback of the order; an older pilot right after a raise, counted at 30 s and not at 29 s, and a pilot recorded after the order counted at once; pushed allocation and pilot on the stream. On main: start at 12 A: got nil/nil, want 12/load_balancer.
  • test_easee_cloud_observation_order.lua: fix(easee_cloud): reject out-of-order power observations #183's cases unchanged, then the additions: an impossible first date, unreadable times used after 30 s, a late disconnected reply, a disconnect restarting the order, a good reply or a failed poll ending a run, and held-back replies costing no session check. On main: older cloud zero replaced a newer power observation. The additions fail on 3e0af59, before the review fixes.
  • test_easee_cloud_start_boost.lua cases 18–19 and the stamped session fixtures (from fix(easee_cloud): reject out-of-order power observations #183). On main: invalid first observation enabled boost.
  • test_easee_cloud_allocated_limit.lua also covers, after the review: a readback still at the old order, the charger's own 10 A limit, unused phases, a one-phase charger whose phase is unknown, a finished car and a pilot recorded in the same second as the order.
  • An independent review (Sonnet, two passes) found the unreadable-time lockup, the late disconnect, the session-check cost, the outage timer, the guards above, a one-phase charger with no allocation, failed polls stopping the escape, and the flicker when Easee releases the car. 4297ee6, d869571 and 34347fb fix them; each fix has a test that fails without it. The reviewer agreed to leave two misses: a ramp after an order rise can name the load balancer for a few seconds (Core waits two minutes before any verdict), and an order that changes faster than every 30 s while the pilot stays put reports nothing.
  • make test-driver ID=easee_cloud: 43 passed, 15 skipped.
  • make check: 4991 passed, 936 skipped.

Not yet run against Easee's servers or on hardware. Göran's trace is the evidence for 70–72 and 114; the 30 s settle time and the half-amp margin are judgement.

Pairing

srcfl/ftw#1537 reads current_limit_a and current_limited_by and says "Easee limits the charger to 12 A (its load balancing)". After this merges, a separate FTW PR moves drivers/BUNDLED_SOURCE.json to the merge commit. #172 and #183 can close once this lands; I have not closed them.

Co-authored-by: Segran goran@andersson-stehag.se

🤖 Generated with Claude Code

…4.1)

A tester's trace (srcfl/ftw#1763, 23 Oct 2026) showed FTW ordering 15 A
and the charger reading back 15 A, while Easee's circuit allocation
(observations 70-72) and the current it signalled to the car (114) fell
to 12, 14 and 13 A. FTW said "Not following".

Read 70-72 and 114. Emit current_limit_a, the signalled current, while
the car charges and it is more than half an amp below the order the
charger holds (48), and only when it answers that order: recorded at or
after it, or unchanged for 30 s since. Emit current_limited_by =
"load_balancer" only when the allocation is below the order too and no
higher than the signalled current. ReasonForNoCurrent is not used as a
cause. Reason labels follow Easee's table (from #172).

Port the REST ordering check from #183: while a car is connected, a
reply whose power time goes back, conflicts at one instant or cannot be
read is not used. Times compare as instants across offsets. The order
starts again on disconnect or after 30 s of replies that go back, so a
stamp from the future cannot hold the driver silent.

Co-authored-by: Segran <goran@andersson-stehag.se>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-25T18:14:03.560201Z 3e0af59 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e0af590a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

end
-- A three-phase car gets the lowest phase's share. A one-phase car
-- uses one phase, which the driver does not know, so the highest.
local allocated_a = (phases == 1) and high or (n == 3 and low or nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require complete phase allocations before attributing a one-phase limit

For a one-phase charger, if any phase allocation is missing or rejected by answering() because its timestamp predates the order, high only covers the remaining phases. Since the driver does not know which phase the car uses, that partial maximum cannot establish that its allocation explains the pilot limit. For example, with a 15 A order, a 12 A pilot, and only L1's 10 A allocation accepted, this emits current_limited_by = "load_balancer" even though the car could use L2 with a 15 A allocation and be limited for another reason. Require all three allocations before using their maximum, or identify the active phase; otherwise leave the cause nil.

Useful? React with 👍 / 👎.

- An unreadable power time no longer silences the driver for good:
  after 30 s of such replies they are used as they come, as before,
  until a readable time arrives.
- A late "disconnected" reply is held to the same order as any other.
- A failed poll ends a run of held-back replies, so a rejection before
  an outage does not count toward the 30 s.
- A held-back reply does not drop the session proof, which cost one of
  Easee's ten session checks an hour each time.
- Report a limit only while the charger reads back FTW's last order;
  do not name the load balancer when the charger's own static limit
  explains the pilot; a phase with no allocation is not one in use.
- Tests for each, all failing on the previous commit, plus the
  one-phase highest-allocation rule, a finished car, a pilot recorded
  in the same second as the order, and an impossible first date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
frahlg and others added 2 commits October 25, 2026 19:51
- A failed poll no longer restarts the 30 s count; a gap of more than
  30 s between held-back replies does. Occasional HTTP errors among
  unreadable replies kept the driver silent.
- Skip dropping the session proof only on a poll whose reply was held
  back, not whenever the count is running.
- A one-phase charger with no allocation on any phase does not name the
  load balancer.
- Tests: both cases, and no limit before FTW's first order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
When Easee lets the car take the order again, the car needs a few
seconds to draw it and the cloud to report that. Core then saw no limit
and a car still below the order, and said "Not following" until the new
draw arrived. Keep the last limit for up to 20 s while the car charges
below the order. Test fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

This branch has not been deployed

No deployments
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.

1 participant