Skip to content

fix(energy): raise SignedCommandRequired from cloud-only island mode commands#84

Merged
Bre77 merged 3 commits into
mainfrom
fm/tfa-island-cloudnoop-fix
Jul 20, 2026
Merged

fix(energy): raise SignedCommandRequired from cloud-only island mode commands#84
Bre77 merged 3 commits into
mainfrom
fm/tfa-island-cloudnoop-fix

Conversation

@Bre77

@Bre77 Bre77 commented Jul 20, 2026

Copy link
Copy Markdown
Member

Intent

Cloud-only EnergySite.set_island_mode()/go_off_grid()/reconnect_grid() send an unsigned grpc_command that gateways can acknowledge without actuating the grid contactor - they shipped as silent no-ops with success-shaped responses (see PR 78, which added honest docstrings pointing to the router/local signed path but left the cloud methods as no-ops). Captain decision 2026-07-20 explicitly chose 'raise a clear error from the cloud-only methods' over a docs-only fix and over auto-routing to the local path. Implemented: a new SignedCommandRequired(TeslaFleetError) exception in exceptions.py; set_island_mode() now unconditionally raises it (go_off_grid/reconnect_grid inherit the raise since they delegate to set_island_mode - no new raise machinery, no capability probing). The router/local/signed EnergySite paths (router/, aiopowerwall local backend) are untouched - only the cloud-only no-op path changed. This is a deliberate BREAKING behavior change for any caller that relied on these methods as silent no-ops: nothing they returned ever actually actuated the contactor, so surfacing that as an error rather than fixing the underlying no-op is the intended outcome, not an oversight. Added tests/test_energysite_island_mode.py asserting each of the three methods raises SignedCommandRequired and never calls the underlying _request. Updated AGENTS.md, docs/energy_local_control.md, and docs/fleet_api_energy_sites.md to describe the new hard-stop behavior instead of the old silent-no-op wording, including how it interacts with EnergySiteRouter failover (local primary still tried first; on total failure the cloud secondary now raises SignedCommandRequired instead of returning a false success).

What Changed

  • Added SignedCommandRequired (TeslaFleetError subclass) in tesla_fleet_api/exceptions.py.
  • EnergySite.set_island_mode() in tesla_fleet_api/tesla/energysite.py now unconditionally raises SignedCommandRequired instead of sending an unsigned grpc_command that gateways can ack without actuating the contactor; go_off_grid()/reconnect_grid() inherit the raise since they delegate to set_island_mode(). Fixed a stale docstring left over from the prior no-op behavior.
  • Updated AGENTS.md, docs/energy_local_control.md, and docs/fleet_api_energy_sites.md to document the hard-stop behavior and how it interacts with EnergySiteRouter failover (local primary still tried first; cloud secondary now raises instead of returning a false success).
  • Added tests/test_energysite_island_mode.py asserting each of the three methods raises SignedCommandRequired and never calls _request.

Risk Assessment

✅ Low: Small, self-contained change: a new exception type plus three methods that now unconditionally raise it, matching the explicit captain decision; tests directly cover the new behavior and assert no underlying request is sent, docs are updated consistently, and the EnergySiteRouter/Router failover interaction requires no code change since Router's existing exception handling already surfaces SignedCommandRequired correctly.

Testing

All existing/new automated tests pass (3 new island-mode tests plus the full 365-test suite), and a manual end-to-end script exercising the EnergySiteRouter local-primary-fails -> cloud-secondary-raises scenario described in the user intent confirms SignedCommandRequired propagates correctly with the documented message and that no network call is made before the raise; no issues found.

Evidence: E2E router failover script (local primary fails, cloud secondary raises SignedCommandRequired)

OK: router raised SignedCommandRequired as expected exception message: This command cannot actuate via the unsigned cloud API - gateways acknowledge it without operating the contactor. Pair a key with add_authorized_client and issue it through a signed local control path (EnergySiteRouter) instead. OK: cloud EnergySite._request was never invoked (raise happens pre-network-call)

"""
End-to-end evidence: EnergySiteRouter(local_primary, cloud_secondary).set_island_mode()
when the local (signed LAN) primary is unavailable.

Simulates the real-world scenario from the captain decision (2026-07-20):
a caller composes a local aiopowerwall-shaped primary with a cloud
TeslemetryEnergySite-shaped secondary via EnergySiteRouter, and calls
go_off_grid(). Before this change, total router failure could look like a
success (the cloud no-op returned an OK-shaped dict). After this change,
total failure now raises SignedCommandRequired -- a caller can no longer
mistake "nothing happened" for "it worked".
"""

import asyncio
from unittest.mock import AsyncMock

from tesla_fleet_api.const import EnergyIslandMode
from tesla_fleet_api.exceptions import SignedCommandRequired
from tesla_fleet_api.router import EnergySiteRouter
from tesla_fleet_api.tesla.energysite import EnergySite


class FailingLocalEnergySite:
    """Stands in for an unreachable aiopowerwall PowerwallEnergySite."""

    async def go_off_grid(self):
        raise ConnectionError("local gateway unreachable (simulated LAN failure)")


async def main():
    local_primary = FailingLocalEnergySite()

    cloud_parent = AsyncMock()
    cloud_secondary = EnergySite(cloud_parent, 12345)

    router = EnergySiteRouter(local_primary, cloud_secondary)

    print("Calling EnergySiteRouter(local_primary, cloud_secondary).go_off_grid()")
    print("  local_primary: simulated unreachable LAN gateway (raises ConnectionError)")
    print("  cloud_secondary: real EnergySite cloud no-op path")
    try:
        result = await router.go_off_grid()
        print(f"UNEXPECTED: router call returned success-shaped result: {result!r}")
        raise SystemExit(1)
    except SignedCommandRequired as e:
        print(f"OK: router raised SignedCommandRequired as expected")
        print(f"  exception message: {e}")
    except Exception as e:
        print(f"UNEXPECTED exception type {type(e).__name__}: {e}")
        raise SystemExit(1)

    # Confirm the cloud secondary's underlying _request was never called --
    # the raise happens before any network call, per the intent.
    assert cloud_parent._request.await_count == 0, "cloud _request should never be called"
    print("OK: cloud EnergySite._request was never invoked (raise happens pre-network-call)")

    # Also directly exercise set_island_mode's message content for the docs claim.
    print()
    print("Direct message content of SignedCommandRequired:")
    print(f"  {SignedCommandRequired.message}")


asyncio.run(main())

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • uv run pytest tests/test_energysite_island_mode.py -v
  • uv run pytest tests -q (full suite)
  • Manual e2e script: EnergySiteRouter(FailingLocalEnergySite, EnergySite).go_off_grid() with a failing local primary -> confirms SignedCommandRequired is raised through router failover instead of a false-success no-op, and that the cloud _request mock is never called
  • uv run python -c check: SignedCommandRequired is a TeslaFleetError subclass, not a bare Exception subclass
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

firstmate crewmate added 3 commits July 20, 2026 09:56
…commands

set_island_mode/go_off_grid/reconnect_grid sent an unsigned grpc_command
that gateways can acknowledge without actuating the grid contactor,
shipping as a silent no-op with a success-shaped response. Raise a
specific TeslaFleetError instead of returning that false success, and
point callers at the signed local control path (add_authorized_client +
EnergySiteRouter).
Docs still described the cloud-only island mode methods as silent
no-ops; update AGENTS.md, energy_local_control.md, and
fleet_api_energy_sites.md to reflect that they now raise
SignedCommandRequired instead.
@Bre77
Bre77 merged commit b588404 into main Jul 20, 2026
5 checks passed
@Bre77 Bre77 mentioned this pull request Jul 20, 2026
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