Skip to content

fix(easee_cloud): reject out-of-order power observations - #183

Draft
segran2 wants to merge 6 commits into
srcfl:easee-load-balancer-reasonfrom
segran2:fix/easee-observation-order
Draft

segran2 wants to merge 6 commits into
srcfl:easee-load-balancer-reasonfrom
segran2:fix/easee-observation-order

Conversation

@segran2

@segran2 segran2 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

An older Easee cloud TotalPower observation can replace a newer one because get_observations accepts each reply without checking power source order. A synthetic 10.242 kW → older 0 W sequence reproduces this on the previous code. This is a verified driver behaviour, not proof that cloud reordering caused every incident in srcfl/ftw#1674.

While connected, reject backward power source times, conflicting values at the same physical instant, and missing/malformed power timestamps. Compare RFC3339 instants across offsets and fractional precision. Keep confirming identical valid change-only values; accept a newer measured zero. An explicit disconnect resets session ordering. Rejected replies emit no fresh power and use the existing poll-error path; no stale value is re-emitted as a new measurement.

This changes no charging commands, phase policy, limits, retry settings or Core safety logic. An unknown reading stays unknown. It does not synchronize Easee with the site meter or repair historical records; genuine cloud gaps and cross-device timing skew remain possible.

Stacks on #172 (easee-load-balancer-reason, 3c0bc2a), which has right of way on driver_poll. The diff contains only this follow-up; rebase onto main after #172 merges. Draft #159 touches command/phase-flip handling and is not included. Driver version 1.3.7 → 1.3.8, with generated catalog/hash updates.

Validation: the new regression fails before the fix and passes after it. make test-driver ID=easee_cloud: 41 passed, 15 skipped. Full make check: 4903 passed, 923 skipped, including sandbox/manifest/catalog/public-boundary checks. Existing session/load-balancer fixtures now provide advancing source times for changing power, preserving their original assertions. Driver history integrity was rechecked after excluding unrelated generated history additions.

Hardware: not tested with this change; no production driver installed and no channel signed/published. Keep draft pending review and hardware validation. After merge, FTW must also advance its bundled-driver pin; merging here alone does not reach an offline gateway.

Vendor API: https://developer.easee.com/reference/getobservations and https://developer.easee.com/docs/charger-observation-ids. The endpoint returns last reported observations; it does not provide a synchronized site energy balance.

frahlg and others added 6 commits October 14, 2026 08:17
* feat(sungrow): read live values once a second (1.6.0)

driver_poll returns 1000 ms. Power, SoC, per-phase values and the EMS
readback are read on every poll; counters, nameplate, temperature,
frequency and fault bits keep the five-second rate and ride along on
the polls between, only when that poll's own power read succeeded.

A read FTW refused during a reconnect wait no longer counts toward
treating a register as absent.

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

* fix(sdm630): stay at five seconds until seen at one second (2.0.3)

driver_poll returned 1000 ms, which FTW ignored until srcfl/ftw#1616.
An SDM630 often shares an RS485 bus with an inverter, and its one
76-register read takes about 0.18 s at 9600 baud. Return 5000 until
hardware shows one second is safe.

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

* fix(pixii_pv): stay at five seconds until seen at one second (0.3.3)

driver_poll returned 1000 ms, which FTW ignored until srcfl/ftw#1616.
Hold it at the five seconds it has run at until hardware shows one
second works. It reads MQTT, so the hold is about unproven behaviour,
not bus load.

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

* Revert "fix(pixii_pv): stay at five seconds until seen at one second (0.3.3)"

pixii_pv reads Core's local MQTT queue, so the bus-load reason for the
hold does not apply. The owner chose to let it run at one second like
ferroamp.

This reverts commit b5af185.

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

* Revert "fix(sdm630): stay at five seconds until seen at one second (2.0.3)"

The owner says an SDM630 easily reads at one second, so it runs at the
1000 ms its driver asks for.

This reverts commit 48cae6c.

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

---------

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A poll where every read fails no longer counts toward retiring a
register. The next poll that gets an answer clears the failure counts,
and a register that stays silent while the device is up is tried again
every five minutes. Register 40288 is still left alone between retries.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
* feat(vehicles): a car says its cable is in and where it is

Vehicle emits gain optional plugged, plugged_since_ms, at_site and
site_distance_m, so FTW can tell which car was plugged in (FTW
docs/cars.md, rule 2). host.site_location() gives a vehicle driver the
site's position; the driver emits only the distance.

- home_assistant_vehicle 0.1.0: new. Reads the JSON a Home Assistant
  automation publishes over MQTT: level, limit, charging state, and the
  cable (binary_sensor.<car>_charger, last_changed) and place (tracker in
  zone home). Levels as fractions.
- tesla_cloud 0.2.0: cable from conn_charge_cable; location_data in the
  same read when the site has a position, charge_state alone after a 403.
- teslamate_vehicle 0.2.0: plugged_in, latitude and longitude.
- vag_vehicle 0.3.0: the dataset's plug state and its point time.

None adds a request or wakes a car.

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

* fix(vehicles): date only what the source saw, and send 1 % as 0.01

From the review of srcfl#182:

- tesla_cloud and vag_vehicle send no plugged_since_ms: they read the car
  minutes apart, so a read that shows the cable in says nothing about when
  it went in. FTW counts a cable time only within a minute of a plug-in.
- home_assistant_vehicle emits every 5 s, and its header says to send
  plugged_since only when the integration reads the cable at least every
  30 s: last_changed is when Home Assistant read the change.
- teslamate_vehicle dates a plugged_in change only when a healthy message
  comes within 10 s. A retained value redelivered after a reconnect has
  none. With geofence set to the site's TeslaMate geofence, the geofence
  topic gives at_site instead of the distance from the weather pin.
- tesla_cloud asks for location_data again a day after a 403.
- FTW reads a vehicle soc of 1 as 100 %. All five vehicle drivers send a
  level of 1 % or less as a fraction and keep percents above that, which
  FTW before 0.131 still reads as percents. tesla_vehicle 0.2.6.
- The spec, the guide and the manifests say the 30 s rule, the 5 km band,
  the signed catalog gate on host.site_location and the optional
  vehicle_location scope.

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

---------

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Segran <goran@andersson-stehag.se>
…ix/easee-observation-order

Signed-off-by: Segran <goran@andersson-stehag.se>

Conflicts:
	CHANGELOG.md

segran2 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

@frahlg Could you please update #172 (easee-load-balancer-reason) with current main, or merge #172 so this PR can be retargeted to main?

I merged current main into this PR branch in 4f434e3. The signed-channel version check now passes, as do the driver tests, public-boundary check and history-secret scan. Local make check: 4985 passed, 936 skipped.

The remaining CI failure is DCO: it reports missing Signed-off-by on upstream Pixii commit 4a17397a128d3471be44ecc8da75e59e55bb1dd5 (#180). That commit is already on main, but it is included in this PR's checked commit range because the stacked base branch is behind main. Updating the base branch should remove that upstream commit from the range without rewriting or adding a sign-off to someone else's commit.

CI run: https://github.com/srcfl/device-drivers/actions/runs/39050615490

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.

2 participants