Skip to content

feat(teslemetry): add energy gateway pairing wait helper - #108

Merged
Bre77 merged 3 commits into
mainfrom
fm/tfa-wait-until-paired-helper
Aug 2, 2026
Merged

feat(teslemetry): add energy gateway pairing wait helper#108
Bre77 merged 3 commits into
mainfrom
fm/tfa-wait-until-paired-helper

Conversation

@Bre77

@Bre77 Bre77 commented Aug 2, 2026

Copy link
Copy Markdown
Member

Intent

Implement the captain-approved wait_until_paired() verify-by-use helper for Tesla energy-gateway key pairing (TeslemetryEnergySite.wait_until_paired in teslemetry/energysite.py). Live hardware findings (relayed by the captain from an aiopowerwall key-testing agent) confirmed the corrected AuthorizedClientState enum from PR #107 is accurate: PENDING_VERIFICATION=1 means still waiting, PENDING_VERIFICATION_TIMEOUT=2 is a TERMINAL failure (the ~9-minute presence-proof window expired, never poll through it), VERIFIED=3 is success. The helper polls find_authorized_clients() for the entry matching the given public_key, returns immediately on VERIFIED, and raises a new AuthorizedClientPairingTimedOut exception immediately on PENDING_VERIFICATION_TIMEOUT rather than treating it as in-progress. It also enforces its own bounded overall timeout (default 600s / 10 minutes, well above the ~9-minute window but a hard ceiling) via a new AuthorizedClientWaitExpired exception, distinct from the terminal-state exception, when the registration is still merely pending when time runs out. Docstring documents the retry path explicitly: re-registering the SAME public key resets the presence-proof window without creating a duplicate record - this is deliberate, never generate a new key on timeout. An optional async verify_by_use callable (e.g. an aiopowerwall client's live_status()) lets a caller with local network access require an actual successful signed local read as confirmation on top of the cloud VERIFIED state, per the captain's original design shape: authorized-client state is the primary signal, verify-by-use is confirmation only where available (i.e. optional) - if verify_by_use is omitted, cloud VERIFIED alone is treated as success; if provided, a failing call while state is already VERIFIED is treated as 'not yet confirmed, keep polling' rather than a hard failure, since local usability can trail the cloud-reported state slightly. Only RSA keys are relevant for verify-by-use since ECC cannot authenticate the LAN TEDapi v1r protocol (documented elsewhere in the codebase already; this helper doesn't add ECC support). Implemented only on TeslemetryEnergySite (not the base EnergySite or Tessie) because it's built on the existing typed find_authorized_clients()/AuthorizedClient accessor, which is Teslemetry-specific - the base EnergySite.list_authorized_clients() returns an unparsed gRPC-shaped dict with no typed parsing today. Added 9 unit tests (tests/test_teslemetry_wait_until_paired.py) covering: immediate success on VERIFIED, polling through PENDING_VERIFICATION to VERIFIED, immediate distinct failure on PENDING_VERIFICATION_TIMEOUT, overall-timeout enforcement while still pending, verify_by_use confirming after VERIFIED, verify_by_use failing once then succeeding, verify_by_use never succeeding (raises wait-expired), no matching client entry (raises wait-expired), and accepting raw public-key bytes vs an already-base64 string. Also updated docs/energy_local_control.md's pairing-verification section to point at the new helper (keeping the existing manual polling example as a reference for callers without the typed Teslemetry accessor) and added an AGENTS.md entry documenting the helper's scope and behavior. Cancellation is handled by letting asyncio.CancelledError propagate normally, per Python/asyncio convention - no custom cancelled-state exception was added.

What Changed

  • Add TeslemetryEnergySite.wait_until_paired() with bounded polling, raw or base64 public-key matching, and optional signed local-read verification.
  • Distinguish terminal gateway pairing timeout from an expired caller wait using new AuthorizedClientPairingTimedOut and AuthorizedClientWaitExpired exceptions.
  • Document the pairing workflow and cover verified, pending, timeout, key-format, and verify-by-use scenarios with focused async tests.

Risk Assessment

✅ Low: The follow-up consistently applies the shared remaining-time deadline to both client lookup and verify-by-use, preserves terminal-state and cancellation behavior, and adds focused regression coverage.

Testing

Inspected the targeted change, ran all focused pairing-helper tests successfully, and captured an end-user-style API transcript proving verify-by-use retries after cloud verification and immediate distinct failure on the gateway’s terminal timeout state; no issues were found and the worktree remained clean.

Evidence: End-user pairing-helper demonstration

signed local read attempt 1: not usable yet signed local read attempt 2: success result: key=demo-rsa-public-key-base64, state=VERIFIED, cloud_polls=3 terminal state: AuthorizedClientPairingTimedOut; polls=1

signed local read attempt 1: not usable yet
signed local read attempt 2: success
result: key=demo-rsa-public-key-base64, state=VERIFIED, cloud_polls=3
terminal state: AuthorizedClientPairingTimedOut; polls=1

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • 🚨 tesla_fleet_api/teslemetry/energysite.py:491 - The required “bounded overall timeout … a hard ceiling” is not enforced around either awaited operation. find_authorized_clients() can hang past the deadline, and after a VERIFIED result verify_by_use() can do the same; the deadline is checked only after each await returns. Wrap the whole polling operation, or each await using the remaining deadline, at the earliest shared boundary and translate expiry to AuthorizedClientWaitExpired.

🔧 Fix: Enforce pairing timeout across awaited operations
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Inspected git diff 0cf9962a090319df9449d01ddc79b1a922cc6ecb f3c1c9df1cf823d3212bf4fa317bb6b5e6f2cd7d for the helper, exceptions, documentation, and focused tests.
  • Ran uv run pytest -q tests/test_teslemetry_wait_until_paired.py.
  • Ran an async public-API demonstration using TeslemetryEnergySite.wait_until_paired() that exercised pending → verified, a temporarily failing then successful signed local read, and immediate terminal-state failure.
  • Ran git status --short to confirm testing left no transient worktree changes.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

firstmate crewmate added 3 commits August 2, 2026 20:30
Combines authorized-client state polling with an optional verify-by-use
local read: resolves on VERIFIED, fails distinctly and immediately on the
terminal PENDING_VERIFICATION_TIMEOUT state instead of polling it out, and
enforces its own bounded overall wait as a separate, retryable timeout.

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

ℹ️ 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 on lines +502 to +505
except asyncio.TimeoutError as exc:
raise AuthorizedClientWaitExpired(
{"public_key": target, "state": last_state}
) from exc

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 Keep polling after per-attempt timeouts

When find_authorized_clients() raises its own TimeoutError before the overall deadline—for example, due to a transient HTTP timeout—asyncio.wait_for() propagates that same exception into this handler, which incorrectly reports that the entire pairing wait expired instead of retrying. The identical issue affects verify_by_use(), where a transient signed-read timeout is explicitly supposed to mean “not yet confirmed.” Distinguish expiry of the outer deadline from an inner operation timeout and continue polling while time remains.

AGENTS.md reference: AGENTS.md:L151-L151

Useful? React with 👍 / 👎.

@Bre77 Bre77 added the fm Opened by a Firstmate crewmate label Aug 2, 2026
@Bre77
Bre77 merged commit ced23f4 into main Aug 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fm Opened by a Firstmate crewmate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant