From 01a06355d14fee868500c323df6e3ae83d8255d3 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Fri, 23 Oct 2026 05:50:09 +0200 Subject: [PATCH] fix(easee_cloud): two review fixes to the 7 A start (1.3.8) Findings L1 and L2 from the review of #190. L1: before the first poll last_op_mode is nil, so a reloaded driver offered 7 A to a car that may already charge at 6 A. A boost now needs a poll that saw the charger not charging. L2: a pause or the stand-down cleared the boost but left 7 A in last_amps_set, so the phaseMode re-assert on the next poll wrote 7 A without the cap or the 90 s window. Clearing a running boost now sets last_amps_set to what the charger should hold: the 6 A order after a pause, 0 A after the stand-down. A pause keeps 6 A, not 0 A, because Core's contactor cycle sends ev_resume after ev_pause with no new order between. Harness cases 15-17 fail on 1.3.7 and pass here. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Fredrik Ahlgren --- CHANGELOG.md | 12 ++++++ SUPPORT_STATUS.md | 4 +- devices.yaml | 2 +- drivers/lua/easee_cloud.lua | 31 +++++++++---- .../test_easee_cloud_start_boost.lua | 43 +++++++++++++++++++ index.yaml | 6 +-- manifests/easee_cloud.yaml | 6 +-- support-status.json | 2 +- 8 files changed, 87 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b54ee27..5c9df36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## [Unreleased] +### easee_cloud 1.3.8 + +Two fixes to the 7 A start from 1.3.7, from its review. A reloaded driver +no longer offers 7 A before its first poll: the car may already charge at +6 A, so the driver now waits until a poll has seen the charger not charging. +A pause or the stand-down that clears a boost now also drops the boost's 7 A +from the current the driver re-writes after a phase change: a pause keeps +the 6 A order and the stand-down 0 A. Before, the next poll could write 7 A +again without the cap or the 90 s window. A pause keeps 6 A rather than +0 A because Core's contactor cycle sends a resume after a pause with no new +order between. Tested in the Lua harness; not yet on hardware. + ### easee_cloud 1.3.7 Start a 6 A order at 7 A. On an Easee Home with an Audi e-tron (three diff --git a/SUPPORT_STATUS.md b/SUPPORT_STATUS.md index f05acf0..309df45 100644 --- a/SUPPORT_STATUS.md +++ b/SUPPORT_STATUS.md @@ -46,8 +46,8 @@ Catalog source is not proof that a target can install or run a driver. | deye | 2.1.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | easee | 1.0.4 | ftw-core | not_assessed | — | not_recorded | not_assessed | | easee | 1.0.4 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | -| easee_cloud | 1.3.7 | ftw-core | not_assessed | — | not_recorded | not_assessed | -| easee_cloud | 1.3.7 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | +| easee_cloud | 1.3.8 | ftw-core | not_assessed | — | not_recorded | not_assessed | +| easee_cloud | 1.3.8 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | esphome_dsmr | 1.0.7 | ftw-core | not_assessed | — | not_recorded | not_assessed | | esphome_dsmr | 1.0.7 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | etrel | 1.0.3 | ftw-core | not_assessed | — | not_recorded | not_assessed | diff --git a/devices.yaml b/devices.yaml index f0a2249..adbf916 100644 --- a/devices.yaml +++ b/devices.yaml @@ -338,7 +338,7 @@ manufacturers: protocols: - protocol: http driver: "easee_cloud" - version: "1.3.7" + version: "1.3.8" ders: [ev] control: true firmware_versions: "" diff --git a/drivers/lua/easee_cloud.lua b/drivers/lua/easee_cloud.lua index 7969e1b..dfefe03 100644 --- a/drivers/lua/easee_cloud.lua +++ b/drivers/lua/easee_cloud.lua @@ -26,7 +26,7 @@ DRIVER = { id = "easee_cloud", name = "Easee Cloud", manufacturer = "Easee", - version = "1.3.7", + version = "1.3.8", protocols = { "http" }, capabilities = { "ev" }, description = "Easee Home/Charge via Cloud REST API. No local protocol needed.", @@ -96,14 +96,14 @@ local EASEE_MAX_A = 32 -- delay and clamps the dynamic current to 7 A before its start command. -- Here the boost is one amp per phase, held only until the charger reports -- charging (op_mode 3) or START_BOOST_MAX_MS passes, then 6 A. It is only --- used when 7 A fits (start_boost_fits), and at most once per start. --- `start_boost: false` disables it. +-- used when 7 A fits (start_boost_fits), after a poll has seen the charger +-- not charging, and at most once per start. `start_boost: false` disables it. local START_BOOST_A = EASEE_MIN_A + 1 local START_BOOST_MAX_MS = 90000 local start_boost_enabled = true local boost_until_ms = nil -- non-nil while 7 A is offered for a 6 A order local boost_spent = false -- one boost per start; reset by a 0 A order or a stop -local last_op_mode = nil -- op_mode from the latest successful poll +local last_op_mode = nil -- op_mode from the latest successful poll; nil before the first -- pick_phases is the driver-level phase decision: given the requested -- charging power, the per-phase fuse ceiling, the operator's mode @@ -536,6 +536,16 @@ local function end_start_boost(why) return true end +-- clear_start_boost drops the boost without a write and re-arms it. A running +-- boost left 7 A in last_amps_set; it becomes hold_a, what the charger should +-- hold, so a pending phaseMode re-assert cannot write 7 A outside the window +-- and the cap check. +local function clear_start_boost(hold_a) + if boost_until_ms ~= nil then last_amps_set = hold_a end + boost_until_ms = nil + boost_spent = false +end + function driver_init(config) host.set_make("Easee") @@ -860,8 +870,9 @@ function driver_command(action, power_w, cmd) local ok = post_command("/commands/pause_charging") if ok then paused_state = true - boost_until_ms = nil - boost_spent = false + -- Keep the 6 A order, not 0 A: Core's contactor cycle sends + -- ev_resume after ev_pause with no new order in between. + clear_start_boost(EASEE_MIN_A) end return ok elseif action == "ev_resume" then @@ -941,7 +952,10 @@ function driver_command(action, power_w, cmd) else write_a, boosting = START_BOOST_A, true end - elseif start_boost_enabled and not boost_spent and last_op_mode ~= 3 and start_boost_fits(cmd, max_a) then + elseif start_boost_enabled and not boost_spent and start_boost_fits(cmd, max_a) and + last_op_mode ~= nil and last_op_mode ~= 3 then + -- An unknown op_mode may be charging: after a driver reload + -- the car can already charge at 6 A before the first poll. write_a, boosting = START_BOOST_A, true end end @@ -1009,8 +1023,7 @@ function driver_default_mode() if post_command("/commands/pause_charging") then paused_state = true end - boost_until_ms = nil - boost_spent = false + clear_start_boost(0) local err = write_setting(charger_serial, {dynamicChargerCurrent = 0}) if err == nil then last_amps_set = 0 diff --git a/drivers/tests/lua_harness/test_easee_cloud_start_boost.lua b/drivers/tests/lua_harness/test_easee_cloud_start_boost.lua index b9ff128..38ab7c5 100644 --- a/drivers/tests/lua_harness/test_easee_cloud_start_boost.lua +++ b/drivers/tests/lua_harness/test_easee_cloud_start_boost.lua @@ -195,4 +195,47 @@ assert(order(4140)); assert(last_amps() == 7) later(91000) assert(order(4140)); assert(last_amps() == 6, "order past the window wrote 7 A") +-- 15. No boost before a poll has seen the charger not charging. After a +-- driver reload the car may already charge at 6 A. +boot() +assert(order(4140)); assert(last_amps() == 6, "boost before the first poll") +poll_fails() +assert(order(4140)); assert(last_amps() == 6, "boost after only a failed poll") +poll(3, 4.1) +assert(order(4140)); assert(last_amps() == 6, "reloaded driver boosted a running session") +poll(2) +assert(order(4140)); assert(last_amps() == 7, "no boost once a poll saw the charger stop") +-- A first poll that sees the charger waiting allows the boost. +boot() +assert(order(4140)); assert(last_amps() == 6) +poll(2) +assert(order(4140)); assert(last_amps() == 7, "no boost after a poll saw the charger waiting") + +-- 16. A pause clears the boost. The phaseMode re-assert that the first order +-- scheduled then writes the 6 A order, not the boost's 7 A. +boot() +poll(2) +assert(order(4140)); assert(last_amps() == 7) +assert(driver_command("ev_pause", 0, {})) +later(5000) +poll(2) +assert(last_amps() == 6, "re-assert after a pause wrote the cleared boost's 7 A") + +-- 17. Default mode clears the boost even when its 0 A write fails; the +-- re-assert then retries 0 A instead of writing 7 A. +boot() +poll(2) +assert(order(4140)); assert(last_amps() == 7) +local http_post = host.http_post +host.http_post = function(url, ...) + if url:find("/settings", 1, true) then return nil, "HTTP 503" end + return http_post(url, ...) +end +driver_default_mode() +host.http_post = http_post +assert(last_amps() == 7) +later(5000) +poll(2) +assert(last_amps() == 0, "re-assert after default mode wrote the cleared boost's 7 A") + print("easee_cloud start boost: ok") diff --git a/index.yaml b/index.yaml index 4923a56..36094da 100644 --- a/index.yaml +++ b/index.yaml @@ -193,15 +193,15 @@ drivers: size_bytes: 4054 sha256: "4a2cd1efb4a5583468ce897ebd88a000e348917295c40210e86ecf834c0ba543" - name: "easee_cloud" - version: "1.3.7" + version: "1.3.8" tier: core protocol: http connectivity: cloud setup: [vendor_portal] ders: [ev] control: true - size_bytes: 46180 - sha256: "26e4b146262a1416fc64f81e27cda6b7aebe0a8e1f7fb924912675c00f7f86f7" + size_bytes: 46954 + sha256: "652708cfe0d97027b97a3be407df49e20b74f1f30f429df20a24df46e9afc806" - name: "esphome_dsmr" version: "1.0.7" tier: core diff --git a/manifests/easee_cloud.yaml b/manifests/easee_cloud.yaml index 8af9af8..28b22f9 100644 --- a/manifests/easee_cloud.yaml +++ b/manifests/easee_cloud.yaml @@ -1,5 +1,5 @@ name: "easee_cloud" -version: "1.3.7" +version: "1.3.8" tier: core author: "Sourceful Labs AB" protocol: http @@ -16,9 +16,9 @@ tested_devices: notes: "Easee Home/Charge via Cloud REST API. No local protocol needed." min_driver_version: "1.0.1" min_host_version: "2.0.0" -size_bytes: 46180 +size_bytes: 46954 dkb_id: "easee_cloud" -sha256: "26e4b146262a1416fc64f81e27cda6b7aebe0a8e1f7fb924912675c00f7f86f7" +sha256: "652708cfe0d97027b97a3be407df49e20b74f1f30f429df20a24df46e9afc806" signature: "" bytecode_sha256: "" diff --git a/support-status.json b/support-status.json index c9af28f..903ad1f 100644 --- a/support-status.json +++ b/support-status.json @@ -443,7 +443,7 @@ }, { "catalog_source": true, - "catalog_version": "1.3.7", + "catalog_version": "1.3.8", "driver_id": "easee_cloud", "targets": { "blixt-l1": {