Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## easee_cloud 1.3.3

Omit `power_observed_at` when the power is unchanged since the last poll, so the host stamps the reading on arrival. Easee records TotalPower only when it changes, so a steady charge kept an old timestamp; FTW took it as a stale charger after three minutes and stopped the car every few minutes all night ([srcfl/ftw#1417](https://github.com/srcfl/ftw/pull/1417)). A new value still carries Easee's source time, and an offline charger (op_mode 0) still emits no sample.

## easee_cloud 1.3.2

Restore the current session after restart even when Easee fills sessionEnd during a pause. Keep the ID when the car stops drawing; revoke it on a real unplug. Retain each measurement's source time and omit an old no-current reason while fresh charging power flows. Cloud offline state no longer reports a cable unplug.
Expand Down
4 changes: 2 additions & 2 deletions SUPPORT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | no |
| easee | 1.0.4 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| easee | 1.0.4 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| easee_cloud | 1.3.2 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| easee_cloud | 1.3.2 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| easee_cloud | 1.3.3 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| easee_cloud | 1.3.3 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| esphome-dsmr | 1.0.3 | ftw-core | not_assessed | 1.0.2 | — | not_recorded | — | not_assessed | no |
| esphome-dsmr | 1.0.3 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| esphome_dsmr | 1.0.3 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
Expand Down
2 changes: 1 addition & 1 deletion devices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ manufacturers:
protocols:
- protocol: http
driver: "easee_cloud"
version: "1.3.2"
version: "1.3.3"
ders: [ev]
control: true
firmware_versions: ""
Expand Down
20 changes: 16 additions & 4 deletions drivers/lua/easee_cloud.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ DRIVER = {
id = "easee-cloud",
name = "Easee Cloud",
manufacturer = "Easee",
version = "1.3.2",
version = "1.3.3",
protocols = { "http" },
capabilities = { "ev" },
description = "Easee Home/Charge via Cloud REST API. No local protocol needed.",
Expand Down Expand Up @@ -282,6 +282,7 @@ local OBS_SESSION_START = 223

local OBS_IDS = "48,96,103,109,120,121,124,183,194,223"

local last_power_observed_at = nil
local validated_session_id = nil
local observed_session_id = nil
local departed_session_id = nil
Expand Down Expand Up @@ -599,6 +600,19 @@ function driver_poll()
-- responsive even when no car is plugged in.
local is_online = (op_mode ~= 0)

-- Easee records an observation only when its value changes, so a steady
-- charge keeps an old TotalPower timestamp; on hardware it stayed
-- unchanged for over five minutes. A new value carries its source time.
-- An unchanged one was confirmed by this poll of a charger the cloud
-- still hears from (get_observations rejects op_mode 0), so omit the
-- source time and let the host stamp the reading when it arrives.
local power_observed_at = timestamps[OBS_TOTAL_POWER]
if power_observed_at ~= nil and power_observed_at == last_power_observed_at then
power_observed_at = nil
else
last_power_observed_at = power_observed_at
end

local reason_code = obs[OBS_REASON_NO_CUR]
-- ReasonForNoCurrent describes a blocked offer. An older reason is not
-- a current fault while the charger reports both charging and power.
Expand Down Expand Up @@ -673,9 +687,7 @@ function driver_poll()
charging = charging,
request_active = request_active,
session_wh = session_wh,
power_observed_at = timestamps[OBS_TOTAL_POWER],
-- Two-minute source updates were observed on hardware. One minute
-- of margin bounds the power estimate without refreshing its time.
power_observed_at = power_observed_at,
power_max_age_s = 180,
energy_observed_at = timestamps[OBS_SESSION_ENERGY],
state_observed_at = timestamps[OBS_OP_MODE],
Expand Down
36 changes: 36 additions & 0 deletions drivers/tests/lua_harness/test_easee_cloud_power_time.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
dofile("drivers/tests/lua_harness/host_mock.lua")
-- Easee records TotalPower only when it changes. A steady charge must not
-- keep reporting that old change time as when the power was observed.
local driver = "drivers/lua/easee_cloud.lua"
host.reset()
host._http_responses["/accounts/login"] = '{"accessToken":"test","expiresIn":3600}'
host._http_responses["/config"] = '{}'
host._http_responses["/sessions/ongoing"] = '{}'
dofile(driver)
driver_init({email="test@example.invalid",password="test",serial="TEST123"})

local function poll(mode, power_kw, power_time)
host._http_responses["/observations?ids="] = host.json_encode({
{id=109,value=mode,timestamp="2026-09-25T00:21:00Z"},
{id=120,value=power_kw,timestamp=power_time},
{id=121,value=12.5,timestamp=power_time},
})
driver_poll()
local rows = host._emitted.ev
assert(rows and #rows > 0, "no EV sample")
return rows[#rows], #rows
end

local first = poll(3, 4.92, "2026-09-25T00:21:20Z")
assert(first.power_observed_at == "2026-09-25T00:21:20Z", "a new value lost its source time")
local steady = poll(3, 4.92, "2026-09-25T00:21:20Z")
assert(steady.power_observed_at == nil,
"an unchanged value kept its old change time: " .. tostring(steady.power_observed_at))
local changed = poll(3, 6.30, "2026-09-25T00:29:20Z")
assert(changed.power_observed_at == "2026-09-25T00:29:20Z", "a changed value lost its source time")

-- An offline charger (op_mode 0) emits no sample, so nothing looks fresh.
local _, before = poll(3, 6.30, "2026-09-25T00:29:20Z")
local _, after = poll(0, 6.30, "2026-09-25T00:29:20Z")
assert(after == before, "an offline charger emitted a sample")
print("Easee power time: passed")
12 changes: 12 additions & 0 deletions drivers/tests/test_easee_cloud_power_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""A steady Easee charge must not look like a stale power reading."""
from pathlib import Path
import subprocess


def test_easee_cloud_reports_poll_time_for_steady_power():
root = Path(__file__).resolve().parents[2]
result = subprocess.run(
[str(root / "lua55"), "drivers/tests/lua_harness/test_easee_cloud_power_time.lua"],
cwd=root, text=True, capture_output=True, check=False,
)
assert result.returncode == 0, result.stdout + result.stderr
6 changes: 3 additions & 3 deletions index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,15 @@ drivers:
size_bytes: 4054
sha256: "4a2cd1efb4a5583468ce897ebd88a000e348917295c40210e86ecf834c0ba543"
- name: "easee_cloud"
version: "1.3.2"
version: "1.3.3"
tier: core
protocol: http
connectivity: cloud
setup: [vendor_portal]
ders: [ev]
control: true
size_bytes: 38746
sha256: "c0b1988b8aed68cf4c67034e0247f23133c33f4cba491e98d92131ce2332d578"
size_bytes: 39308
sha256: "1ae0b702197553df0075a649a161ecce57b2a3afe69322afa78edd585f112232"
- name: "esphome-dsmr"
version: "1.0.3"
tier: community
Expand Down
6 changes: 3 additions & 3 deletions manifests/easee_cloud.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "easee_cloud"
version: "1.3.2"
version: "1.3.3"
tier: core
author: "Sourceful Labs AB"
protocol: http
Expand All @@ -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: 38746
size_bytes: 39308
dkb_id: "easee_cloud"
sha256: "c0b1988b8aed68cf4c67034e0247f23133c33f4cba491e98d92131ce2332d578"
sha256: "1ae0b702197553df0075a649a161ecce57b2a3afe69322afa78edd585f112232"
signature: ""

bytecode_sha256: ""
Expand Down
2 changes: 1 addition & 1 deletion support-status.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@
},
{
"catalog_source": true,
"catalog_version": "1.3.2",
"catalog_version": "1.3.3",
"driver_id": "easee_cloud",
"package_id": null,
"targets": {
Expand Down
Loading