Skip to content

Add AppSync support for 24MM vehicles - #182

Open
efahrenholz wants to merge 3 commits into
widewing:masterfrom
efahrenholz:codex/24mm-2026-support
Open

Add AppSync support for 24MM vehicles#182
efahrenholz wants to merge 3 commits into
widewing:masterfrom
efahrenholz:codex/24mm-2026-support

Conversation

@efahrenholz

@efahrenholz efahrenholz commented Jul 18, 2026

Copy link
Copy Markdown

Summary

Add a generation-aware AppSync path for 24MM vehicles (including 2025/2026 models) while leaving the existing REST behavior unchanged for 17CY, 17CYPLUS, and 21MM vehicles.

This addresses the two separate failure modes seen on newer vehicles:

  • remote actions were sent to v1/global/remote/command, which 24MM vehicles reject with errors such as ONE-GLOBAL-RS-40009;
  • initial lock/opening and electric state depended on legacy REST responses or a WebSocket update that might never replay the current state, leaving entities unknown or absent.

Addresses #162, #164, and the newer lock-state representation related to #167.

Corrective action

1. Route 24MM remote commands through AppSync

For 24MM only, remote commands now use the current Toyota app sequence:

  1. open the authenticated AppSync WebSocket for the vehicle VIN;
  2. subscribe to onPostRemoteCallback(vin:);
  3. wait for the subscription start_ack;
  4. call the executeRemoteCommand GraphQL mutation;
  5. require the mutation correlation ID, then wait for Toyota's completed callback (or surface its error/timeout).

Older generations continue to use their existing REST routes.

One important clarification for #162: the current 24MM client still sends the wire command engine-start. Toyota presents that action as climate conditioning for PHEVs, but climate-start is not the command sent by the current client. The generation-specific AppSync transport and callback flow are the material API changes.

2. Query current 24MM status directly

Add the current GetVehicleStatus document and call it during the initial/polled 24MM update. The status subscription remains active for later pushes, but is no longer treated as the only source of opening/lock/electric state because subscriptions do not guarantee a replay of the current value.

The status subscription document is also expanded to include the electric battery, range, connector, and charging fields.

3. Parse the current response vocabulary

Normalize both classic and current values:

  • opening: close/closed, open/opened;
  • lock: lock/locked, unlock/unlocked.

Unknown values are left unknown instead of being converted to open/unlocked. This prevents a 24MM lock + close response from appearing as an unlocked or unknown vehicle.

The 24MM electric document now populates battery percentage, EV range (with and without A/C), remaining charge time, plug/connector status, charge type, and charging state. Battery percentage uses Toyota's current preference order: stateOfChargeDisplay, then plugInEnergy, then chargeRemainingAmount.

4. Recognize current capabilities during discovery

Connected Services detection now accepts the shapes used by newer vehicle-list responses:

  • remoteSubscriptionStatus or subscriptionStatus is active/subscribed;
  • remoteSubscriptionExists is true;
  • remote capability flags such as doorLockUnlockCapable or remoteEngineStartStop are true.

EV/PHEV detection also accepts fuelType E or I, and the actual vehicle generation and backdoor type are preserved for later requests. This allows Home Assistant to create the subscription- and electric-dependent entities for newer vehicles.

5. Expose controllable features with native Home Assistant domains

The integration now maps Toyota remote commands to controllable entities instead of read-only sensors:

  • remote climate/start: switch, using engine-start / engine-stop;
  • hazard lights: switch, using hazard-on / hazard-off;
  • power windows: cover with device_class: window, using power-window-open / power-window-close;
  • immediate charging: switch for subscribed 24MM EV/PHEV vehicles, using immediate-charge / charge-stop;
  • horn and warning buzzer: momentary button entities, using sound-horn and buzzer-warning.

The entity domains match Home Assistant's Alexa and Google integrations: open/close is represented as a cover, stateful start/stop features as switches, and stateless one-shot commands as buttons. Hazard state is optimistic because Toyota does not report its current state. Voice users should expose only the controls they intend to make available because non-lock entities may not receive lock-style PIN protection.

6. Parse current 24MM tire pressure data

The AppSync status query and subscription now include frontLeft, frontRight, rearLeft, rearRight, and spare tire pressure data. The parser accepts scalar values and {value, unit} objects in psi, kPa, or bar, and exposes them as pressure sensors using SensorDeviceClass.PRESSURE.

Tire pressure remains a sensor rather than a controllable entity. Alexa and Google do not currently expose pressure sensors, so these entities are intended for Home Assistant dashboards, history, and automations.

Validation

  • python3 -m unittest discover -s tests -v (15 tests)
  • python3 -m compileall -q custom_components/toyota_na tests
  • git diff --check

The 15 tests cover the AppSync callback flow, 24MM status and tire-pressure parsing, capability normalization, remote start/stop, hazard and charging switch commands, window cover behavior, horn and buzzer buttons, error recovery, and generation gating.

The tests use a sanitized synthetic 24MM fixture and mocked AppSync socket/mutation flow; they do not contact a vehicle. Separately, the protocol and lock/close response shape were validated with a 2026 RAV4 XSE PHEV, including successful physical lock and unlock commands. The additional window, horn, buzzer, hazard, and charging commands use the command strings implemented by the Toyota client and remain subject to per-vehicle physical verification.

@llamahunter

Copy link
Copy Markdown

This seems like a very important/useful PR. Why is it not yet merged and released?

bhaggs added a commit to bhaggs/ha-toyota-na that referenced this pull request Aug 11, 2026
Remote start is stateful: the vehicle reports whether it is running and for how
much longer, and there is already a RemoteStartStatus feature carrying that. Two
buttons threw it away. A switch reads the state back and exposes start time, end
time, minutes remaining, and total runtime as attributes.

Taken from widewing#182, which makes the general point that Home
Assistant's Alexa and Google integrations expect stateful start/stop features to
be switches and one-shot commands to be buttons. Only that idea is adopted here,
not the PR's wider 24MM AppSync work, which this fork has no evidence of needing:
remote commands over REST are confirmed working on a 2026 Solterra.

Optimistic state is cleared on the next poll rather than retained. If a command
silently failed the switch should say so rather than keep showing what was asked
for - the same reasoning that led to dropping the Hazards Off button.

The engine_start and engine_stop services are untouched, so automations built on
them keep working. Their buttons are gone, and will show as orphaned entities
until deleted from the entity registry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bhaggs added a commit to bhaggs/ha-toyota-na that referenced this pull request Aug 11, 2026
light-on returns HTTP 400, so the guess was wrong and the buttons are removed
rather than left broken. The naming pattern they were extrapolated from is
itself unreliable: PR widewing#182 shows the documented vocabulary is wrong about
power-window-on/off, which are really power-window-open/close.

Guessing one candidate per release is the wrong shape for this. The new
toyota_na.send_command service sends an arbitrary command string to the vehicle,
so candidates can be tried from Developer Tools in one sitting - headlight-on,
lights-on, and so on - and a rejection comes back as a UI error naming the
backend response. Useful well beyond this question, since the vocabulary is
undocumented and the backend keeps changing.

The LightsOn/LightsOff enum members and command map entries stay, so buttons can
come back as a two-line change once the real strings are known.

Hazards is renamed from "Hazards On" now that it stands alone. That changes its
unique_id, so the old entity orphans alongside the two Lights buttons.

Co-Authored-By: Claude Opus 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.

2 participants