Skip to content

Stop Infotainment polling from keeping the car awake - #161

Merged
dzid26 merged 9 commits into
mainfrom
work/infotainment-backoff-v2
Oct 9, 2026
Merged

dzid26 merged 9 commits into
mainfrom
work/infotainment-backoff-v2

Conversation

@dzid26

@dzid26 dzid26 commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part of #112 (real-car verification pending).

Any Infotainment request restarts the car's own sleep countdown, so reading charge and drive state every 10 s whenever VCSEC says "awake" can keep a parked car from ever sleeping. A new pure policy in core decides when the read is due; the controller asks it from the VCSEC status handler. VCSEC status stays at 10 s.

Infotainment is read every 10 s while either of these holds:

  • Active: charging (Charging/Starting) or driving (D/R/N). When activity ends, these reads stop right away. This is the extension point (isActive()): sentry and climate join it in a later PR.
  • Within the hold after the last status change: 10 min (HOLD_PRESENT_MS) if the latest status shows user presence (someone in the car), otherwise 1 min (HOLD_ABSENT_MS). Presence is re-evaluated on every status, so when the person leaves, the remaining hold drops to 1 min after the last change.

Status changes that start or restart the hold:

  • asleep to awake;
  • any lock-state change (locking too, which catches driving away and locking);
  • any closure change between open and not open (AJAR counts as open, UNKNOWN does not);
  • a fresh start (the first READY link since the app or tracking started, or a completed pairing);
  • the refresh button (user request).

A change in user presence alone is not a change, so it never starts a hold and cannot keep resetting the car's sleep countdown.

Idle safety read: while the car is awake, not active and not in a hold, one read once 20 min (IDLE_SAFETY_INTERVAL_MS) have passed since the last read. It catches an event the status misses (remote climate, Sentry, a remote wake) when the car stays awake anyway. 20 min is deliberately longer than the car's own sleep countdown (about 10-15 min), so a car that would sleep is already asleep when the read comes due and its sleep is never extended. It does not start a hold. The value is tuned from the real-car check.

Asleep: no reads, and the hold is dropped. Never wakes the car (awake VCSEC status and a session are still required). A reconnect after a dropped link is not a change, and the transition memory (asleep, locked, closures) survives it, so a wake, lock change or door change during an outage still starts the hold on the first status after.

Changes:

  • core/.../protocol/InfotainmentPollPolicy.kt plus InfotainmentPollPolicyTest (every rule, change and non-change, and the safety read: due at 20 min, not before, none while asleep, no hold started).
  • TeslaBleController.kt: one policy per VehicleLink; the status handler asks onStatus instead of requesting the charge state unconditionally; charge and drive replies feed the readings; the refresh button counts as a user request and still sends at once; the first READY (onLinkReady) and a completed pairing are fresh starts.
  • ADR-0009 (with a "Considered and deferred" list), docs/research/infotainment-polling-and-sleep.md (evidence and citations), README note, master-plan lines (including unchecked items for sentry/climate as active, activity recognition, and an opt-in "stay awake while the phone is near and moving").

Note: the wake button does not call onUserRequest. It sends no Infotainment read (the car is asleep); the resulting asleep to awake step is itself a change.

Type

  • Bug fix
  • Feature
  • Protocol / crypto change
  • Docs / CI / tooling
  • Refactor (no behavior change)

Testing

  • ./gradlew :core:test :app:assembleDebug :app:testDebugUnitTest :app:lintDebug ktlintCheck detekt passes locally.
  • Not verified on a real car. Real-car check for the owner:
    • Leave the car idle and watch the status log. With nobody present it should show asleep roughly 11-12 min after the last change (1 min hold plus the car's own ~10 min countdown). If the car reports presence, the hold is 10 min, so expect about 20-25 min.
    • While charging or driving, readings still log every 10 s.
    • Opening a door or unlocking gives 10 s readings for the hold (1 min, or 10 min with presence), then they stop.
    • A car that stays awake on its own (climate on, Sentry) gets one reading every 20 min; one that would have slept is never touched by it. Tune IDLE_SAFETY_INTERVAL_MS from what you see.
    • Please record the result in docs/research/infotainment-polling-and-sleep.md.

Checklist

  • Tests added or updated
  • README / docs updated
  • ADR added (ADR-0009)
  • No secrets, VINs or keys in logs

🤖 Generated with Claude Code

https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX


Generated by Claude Code

claude added 6 commits October 8, 2026 23:38
Decides when an Infotainment read is due: 10 s while charging or driving,
30 s for a 660 s window after activity or a trigger, then every 660 s;
one read on wake; none while asleep. Modelled on yoziru/esphome-tesla-ble
PRs #198 and #213. Not wired into the controller yet (waits for #158).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
Any Infotainment request restarts the car's sleep countdown, so a 30 s
window or idle reads keep it awake. Poll every 10 s only while charging or
driving; otherwise read once on connect, wake, unlock, a closure change or
a user request, plus one follow-up 60 s later.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
A reconnect after a dropped link no longer reads or schedules a follow-up,
and the transition memory survives it so a wake, unlock or closure change
during the outage still counts. A closure that is neither closed nor
unknown, ajar included, counts as open.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
The status handler asks the per-link policy instead of requesting the
charge state on every awake status. The charge and drive replies feed it,
the refresh button counts as a user request, and the first READY link and
a completed pairing are fresh starts. Evidence moves to docs/research;
README and master plan updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Screenshots

App screenshots: light and dark

Captured from the demo build at 3fec744.

Debug APK: zip from Android CI (GitHub sign-in needed)

@dzid26 dzid26 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

check

}
historyStore.record(bleName, charge, acquiredAt, latestRssi(address))
infotainmentPolicy.onChargeReading(charge.chargingStateKind)
if (previous?.battery_level != charge.battery_level ||

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

why are we checking battery level

* [onFreshStart]), VCSEC asleep to awake, locked to unlocked, any closure changing between open and
* not open (a closure that is neither CLOSED nor UNKNOWN counts as open, so ajar is open), and an
* explicit user request. A plain reconnect after a dropped link is not an event. Each event also
* schedules one follow-up read [FOLLOW_UP_DELAY_MS] later, to catch a shift into D or charging

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

what happens if driver shifts to D after 60s ?

}

/** The latest drive reading. Shift state D, R or N is activity. */
fun onDriveReading(shiftState: ShiftStateKind?) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

https://github.com/yoziru/esphome-tesla-ble/blob/0932a1c04f0f7e853db688bd80b02e28df28a9d4/components/tesla_ble_vehicle/tesla_ble_vehicle.cpp#L206
sentry mode also is on. Maybe keep AC on as well? - Or at least make staying awake non-issue due to their energy sue magnitude.

Per the owner's review on #161: read every 10 s while charging or driving
and for a hold after each status change (asleep to awake, any lock change,
any closure change, fresh start, user request): 1 min, or 10 min while the
car reports presence. A presence change alone is not a change. The single
read plus 60 s follow-up is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Screenshots

App screenshots: light and dark

Captured from the demo build at 22bc763.

Debug APK: zip from Android CI (GitHub sign-in needed)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Screenshots

App screenshots: light and dark

Captured from the demo build at ca2396a.

Debug APK: zip from Android CI (GitHub sign-in needed)

Catches an event the status misses (remote climate, Sentry, a remote
wake). 20 minutes is longer than the car's own sleep countdown, so a car
that would sleep is already asleep and is never kept awake by it. It does
not start a hold.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Screenshots

App screenshots: light and dark

Captured from the demo build at 640f278.

Debug APK: zip from Android CI (GitHub sign-in needed)

@dzid26
dzid26 merged commit 36bd224 into main Oct 9, 2026
5 checks passed
@dzid26
dzid26 deleted the work/infotainment-backoff-v2 branch October 9, 2026 01:39
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