Skip to content

feat(vehicles): a car says its cable is in and where it is - #182

Merged
frahlg merged 2 commits into
mainfrom
claude/vehicle-plugged-here
Oct 16, 2026
Merged

frahlg merged 2 commits into
mainfrom
claude/vehicle-plugged-here

Conversation

@frahlg

@frahlg frahlg commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What

A vehicle driver can now tell FTW that its car's cable is in and where the car is. FTW uses that to name the car at a plug-in: rule 2, "the car says it", in FTW's docs/cars.md. Until now every vehicle driver sent only the level, the limit and the charging state.

Paired with srcfl/ftw#1660, which reads these fields in Core and adds host.site_location().

The contract (spec/host-api.md, docs/WRITING-A-DRIVER.md)

All fields are optional on the vehicle emit:

Field Meaning
plugged The car's own word on its cable: true in, false out.
plugged_since_ms When the source read the cable take that state. Sent only by a source that reads the cable at least every 30 s, and never for a value seen at the first reading or redelivered by a broker. It is the read's time, not the plug-in's: Home Assistant's last_changed is when Home Assistant read it.
at_site true at this site, false elsewhere, from the source's own idea of home (a Home Assistant zone, a TeslaMate geofence).
site_distance_m The car's distance from the site. Core reads up to 300 m as here, over 5 km as elsewhere, and does not know in between: the site's position is a weather pin. at_site wins.

host.site_location() returns the site's lat, lon, or nil. FTW gives it only to a driver from the signed channel whose signed catalog entry lists vehicle. The driver measures the distance and emits only that; the car's coordinates stay in the driver. Every driver checks that the call exists, so an older FTW or Blixt L1 gets no distance.

soc: a level of 1 % or less goes as a fraction. Core reads a vehicle soc of 1 or less as a fraction, so a car at 1 % read as 100 %. Sending every level as a fraction would break FTW before 0.131 (the current stable, v0.130.4), which reads every value as a percent. A percent above 1 means the same to both, so the drivers keep percents and send only 1 % or less as a fraction: 0.01 is 1 % to a new FTW and 0.01 % to an old one.

The drivers

None adds a request or wakes a car.

  • home_assistant_vehicle 0.1.0, new. Reads one JSON message that a Home Assistant automation publishes over MQTT, and emits every 5 s. FTW holds no Home Assistant token. This brings a site-local driver into the catalog, with the same message, so a site can drop its local copy. Added: plugged from binary_sensor.<car>_charger (device class plug), and at_site from device_tracker.<car>_location_tracker in zone home. The header carries the automation for the Tesla Custom Integration. It leaves plugged_since out, and says to add it only when the integration reads the cable at least every 30 s.
  • tesla_cloud 0.2.0. The cable from conn_charge_cable (<invalid> is none), else the charging state. No cable time: the car is read minutes apart. With a site position, the same vehicle_data read asks for location_data too. A token without the optional vehicle_location scope gets HTTP 403; the driver then reads charge_state alone and asks again a day later. The manifest and devices.yaml name the scope.
  • teslamate_vehicle 0.2.0. plugged_in, dated by the box's clock only when a healthy message comes within 10 s, so a retained value redelivered after a reconnect stays undated. With geofence set to this site's TeslaMate geofence, the geofence topic gives at_site; otherwise latitude and longitude give the distance.
  • vag_vehicle 0.3.0. The Data Act plug state, with no cable time: the portal writes every 15 minutes. A value other than connected or disconnected is unknown, not unplugged. The datasets carry no position.
  • tesla_vehicle 0.2.6. Only the 1 % fix.

Evidence

  • make test-driver ID=…: home_assistant_vehicle 40, tesla_cloud 38, teslamate_vehicle 40, vag_vehicle 39, tesla_vehicle 41 passed.
  • make check: 4983 passed, 936 skipped.
  • Fixtures: home_assistant_vehicle replays a message recorded from a Model Y on the Tesla Custom Integration on 15 October 2026 (VIN replaced), with the entity states behind it (binary_sensor.<car>_charger on, charging_state: Stopped, conn_charge_cable: IEC). tesla_cloud uses the same recorded cable values. Positions are public landmarks (Uppsala Cathedral, Stockholm Central).
  • New cases: a broker redelivery stays undated and a live change is dated (the test fails if the driver dates at arrival); geofence in, elsewhere and none; location_data asked again after a day; 1 % sent as 0.01 by all five.
  • The harness runs 32-bit Lua, where epoch times lose precision. feat(cars): a car says when its cable went in and how far away it is ftw#1660 commits copies of the Home Assistant, TeslaMate and Fleet API drivers as testdata and runs them in FTW's own Lua host through to the observations the cars runtime reads.

Not done

  • No car has run the cable or place fields.
  • FTW's pin moves in a separate PR after this merges. home_assistant_vehicle is not in FTW's bundled list; adding it is FTW's choice.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 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-16T06:20:53.541635Z ad7b87a 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.

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>
@frahlg
frahlg force-pushed the claude/vehicle-plugged-here branch from ad7b87a to 332750e Compare October 16, 2026 06:20

@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: ad7b87a37b

ℹ️ 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".

Comment thread drivers/lua/home_assistant_vehicle.lua Outdated
local stale = now >= expires_ms
host.emit("vehicle", {
soc = last.soc,
charge_limit = last.charge_limit,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Emit the charge limit using the contracted field and units

For every Home Assistant message containing a charge limit, this emits the unrecognized charge_limit key as a 0–1 fraction. The vehicle host contract and all existing vehicle drivers use charge_limit_pct with a 0–100 value, so FTW can silently drop this field or interpret 80% as 0.8%. Emit the validated incoming percentage as charge_limit_pct instead.

AGENTS.md reference: AGENTS.md:L17-L23

Useful? React with 👍 / 👎.

firmware_versions: ""
notes: "Read-only vehicle level, charge limit, charging state, charge cable and place from a JSON message a Home Assistant automation publishes. Tested with recorded messages from a Model Y on the Tesla Custom Integration; the cable and place fields have not run on a car. Does not wake or command the car."
min_driver_version: "0.1.0"
min_host_version: "2.0.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the new driver's upstream documentation

The new driver depends on Home Assistant and Tesla Custom Integration entity names, attributes, and timestamp semantics, but its manifest has no upstream_docs. This prevents the repository's watcher from detecting when those source contracts change or disappear; record the durable documentation URLs used to derive the automation and mappings.

AGENTS.md reference: AGENTS.md:L76-L82

Useful? React with 👍 / 👎.

From the review of #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>
frahlg added a commit to srcfl/ftw that referenced this pull request Oct 16, 2026
- Rule 2 gives no cable answer when another charger saw a plug-in within
  the same minute: the car could be on either. Rule 2 is per car: one
  source that says the car is elsewhere rules it out.
- A change the box saw between readings more than a minute apart has no
  time. A car read elsewhere after the plug-in is no rule 3 candidate.
- A tap over the box's own visitor verdict is final.
- site_distance_m reads as elsewhere only beyond 5 km: the site is a
  forecast pin.
- host.site_location answers only a driver whose signed catalog entry
  lists vehicle; RuntimePolicy carries the signed capabilities.
- Home Assistant's vehicle_json leaves out at_site, site_distance_m and
  plugged_since_ms.
- The driver card shows the stored level x100 and reads charge_limit as a
  fraction.
- testdata copies of the Home Assistant, TeslaMate and Fleet API drivers
  from srcfl/device-drivers#182 run end to end in this host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit 5cc7bb1 into main Oct 16, 2026
5 checks passed
@frahlg
frahlg deleted the claude/vehicle-plugged-here branch October 16, 2026 09:20
frahlg added a commit to srcfl/ftw that referenced this pull request Oct 16, 2026
…1660)

* feat(cars): a car says when its cable went in and how far away it is

Vehicle drivers may add plugged_since_ms, the time their source saw the
cable take its state, and site_distance_m, the car's distance from the
site. Core reads up to 300 m as here and over 1 km as elsewhere, with no
answer between. Rule 2 uses the car's own time, so a source that reports
minutes after the plug-in, or the first report after a restart, still
names its car when the cable went in within the minute.

host.site_location() gives the weather pin to vehicle drivers only. The
pin nobody moved and 0,0 are no position: a car measured against central
Stockholm would turn the only car into a visitor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cars): a reading from before the plug-in does not make the only car a visitor

Rule 3's "not here" took the time a report arrived. A source that
replays a reading from before the plug-in, or one that has not read the
car since it came home, would then say "cable out" or "elsewhere"
within the minute, and the only car would become a visitor for the whole
session. No driver emitted plugged before; the Home Assistant, Tesla,
TeslaMate and VAG drivers now do. The reading's own time counts instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cars): the review of the car's own word

- Rule 2 gives no cable answer when another charger saw a plug-in within
  the same minute: the car could be on either. Rule 2 is per car: one
  source that says the car is elsewhere rules it out.
- A change the box saw between readings more than a minute apart has no
  time. A car read elsewhere after the plug-in is no rule 3 candidate.
- A tap over the box's own visitor verdict is final.
- site_distance_m reads as elsewhere only beyond 5 km: the site is a
  forecast pin.
- host.site_location answers only a driver whose signed catalog entry
  lists vehicle; RuntimePolicy carries the signed capabilities.
- Home Assistant's vehicle_json leaves out at_site, site_distance_m and
  plugged_since_ms.
- The driver card shows the stored level x100 and reads charge_limit as a
  fraction.
- testdata copies of the Home Assistant, TeslaMate and Fleet API drivers
  from srcfl/device-drivers#182 run end to end in this host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit to srcfl/ftw that referenced this pull request Oct 16, 2026
…ged in (#1665)

Move the device-drivers pin from 76ddd58 to 5cc7bb1a (srcfl/device-drivers#182)
and bundle home_assistant_vehicle, tesla_cloud, teslamate_vehicle and
vag_vehicle beside tesla_vehicle. Between the two pins only those five
drivers changed. fleetping's shipped list is regenerated from the pin.

The end-to-end vehicle test now runs the bundled copies, and the testdata
copies from #1660 go.

Supersedes #1449.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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