In v1r (wired LAN RSA) mode, per-device queries to follower Powerwalls
return the leader's data. The gateway ignores the follower DIN in
the inner protobuf recipient.din and processes the query against its
own components. This causes get_pw3_vitals() to produce duplicate
PVAC/PVS/TEPINV/TEPOD entries — 3 copies of the leader's data instead
of distinct per-device data.
Current workaround: Skip follower battery blocks when self.v1r is
set. System-level aggregates (SOE, meters, battery_level) are unaffected
since they come from the leader's gateway-level data. WiFi mode is
completely unaffected.
| Role | IP | DIN | Type |
|---|---|---|---|
| Leader | 10.42.1.x | 1707000-xx-K--TGxxxxxxxxxx | Powerwall3 |
| Follower 1 | 10.42.1.y | 1707000-xx-K--TGyyyyyyyyyy | Powerwall3Follower |
| Follower 2 | 10.42.1.z | 1707000-xx-K--TGzzzzzzzzzz | Powerwall3Follower |
Expansion pack (1807000-xx-B--TGeeeeeeeeee) is physically attached to the leader (no separate IP, shows up as additional BMS/HVP components in leader queries).
Firmware 25.42.2 on all units.
- Result: HTTP 200, returns data — but it's always leader data.
- Verified by checking PCH serial number: always the leader's serial regardless of which follower DIN is targeted.
- PV/battery values vary slightly (queries are seconds apart) but BMS data is identical across all three "different" responses.
- This is what the code was doing before the fix — the proxy showed 3 PVAC, 3 PVS, 3 TEPINV, 6 TEPOD, all duplicates.
- Result:
MESSAGEFAULT_ERROR_TIMEOUT(fault code 2). - The leader does attempt to forward — it recognizes
routing_addressas a routing instruction and tries to deliver. - But the follower never responds. Timeout after ~20 seconds.
- Tested with follower full DIN, follower serial only,
leader+follower
from_destinationcombos — all timeout.
| Variant | outer to_destination |
inner recipient |
Result |
|---|---|---|---|
| domain=7, recip=leader | domain=7 | leader | OK (leader data) |
| domain=7, recip=follower | domain=7 | follower | OK (leader data) |
| routing_address=follower DIN | follower DIN | follower | TIMEOUT |
| routing_address=follower DIN | follower DIN | leader | TIMEOUT |
| ra=follower, sign with follower DIN | follower DIN | follower | TIMEOUT |
| ra=follower, from_ra=leader | follower DIN | follower | TIMEOUT |
| domain=0 (BROADCAST) | domain=0 | follower | TIMEOUT |
| domain=8 (ENERGY_DEVICE_AUTH) | domain=8 | follower | TIMEOUT |
| ra=follower serial bytes | serial bytes | follower | TIMEOUT |
| ra=expansion DIN | expansion DIN | expansion | TIMEOUT |
Plus 8 more minor variations of the above — all either timeout or return leader data.
Tested by connecting directly to follower IPs on the wired LAN:
| Endpoint | Method | Status | Notes |
|---|---|---|---|
/api/login/Basic |
POST | 200 | Each follower accepts its own customer password |
/tedapi/din |
GET | 200 | Returns follower's own DIN (confirmed correct) |
/tedapi/info |
GET | 200 | Returns firmware version, DIN, deviceType (no auth needed) |
/tedapi/v1r |
POST | 200 | Endpoint exists and accepts POSTs |
| Endpoint | Status | Notes |
|---|---|---|
/api/meters/aggregates |
502 | Followers don't run the full gateway |
/api/system_status/soe |
502 | Same |
/api/system_status/grid_status |
502 | Same |
/tedapi/v1 |
403 | Interface-blocked (same as leader on wired LAN) |
/tedapi/device/* |
403 | Interface-blocked wildcard |
When sending RSA-signed v1r requests directly to a follower IP:
| Signing DIN | Response |
|---|---|
| Follower's own DIN | "v1r: client not authorized" (36 bytes) |
| Leader DIN | MESSAGEFAULT_ERROR_TIMEOUT |
The "client not authorized" response confirms:
- Followers DO process v1r requests (not just returning HTTP 200 blindly)
- They validate RSA signatures but our public key isn't in their keychain
- The v1r endpoint is fully functional on followers — just needs key registration
A previously undocumented endpoint works on all Powerwalls (leader + followers) with no authentication required:
GET /tedapi/info
Response:
{
"din": "1707000-21-K--TGxxxxxxxxxx",
"firmwareVersion": {
"version": "25.42.2",
"githash": "FConoYHQfw/J8aXdKkaEYY6pTNs="
},
"deviceType": 4,
"tegDeviceType": 7
}Works on wired LAN (10.42.1.x) and internal networks (192.168.90.x). Does not work on home WiFi network (returns 403).
All Powerwalls (leader + followers) expose port 8087 on the wired LAN.
- TLS 1.3 with Tesla-signed certificate
- Subject CN = device DIN
- Issuer:
Tesla Energy Product Issuing CA - Extended Key Usage includes
TLS Web Client Authentication - Custom OID:
1.3.6.1.4.1.49279.2.5.2
- Connection without client cert:
SSL alert number 116: certificate required - Accepted client CAs:
Tesla Product Access Issuing CA,Tesla China Product Access Issuing CA - Self-signed fake Tesla CA + client cert:
TLS alert number 48: unknown ca - The Powerwall validates against the real Tesla CA — no bypass possible without a legitimate Tesla-signed client certificate
This is likely the cloud ↔ device management channel. Inter-PW communication does not use this port (zero packets observed between PWs on wired LAN). Followers communicate with the leader via internal bus (CAN or power-line), not Ethernet.
PW3 firmware enforces access control based on the physical incoming network interface, not IP addresses or routing. This cannot be bypassed by IP spoofing or bridge tricks.
| Interface | Login | /tedapi/din | /tedapi/v1 | /tedapi/v1r | /tedapi/device/* |
|---|---|---|---|---|---|
| Wired LAN (10.42.1.x) | 200 | 200 | 403 | 200 | 403 |
| WiFi AP (192.168.91.1) | 200 | 200 | 200 | 200 | 200 |
| Home WiFi | 401 | 403 | 403 | 200* | 403 |
| Internal (192.168.90.x) | 200 | 200 | 403 | 200 | 403 |
* v1r returns HTTP 200 on home WiFi but all queries TIMEOUT (including leader-to-leader)
- Only Follower 1 has an IP on this subnet (192.168.90.2)
- Reachable via routing through any PW (leader, follower 1, or follower 2)
- MAC confirms it's the same device as 10.42.1.y (Follower 1)
- Access controls identical to wired LAN interface
- No other devices on this subnet (scanned full /24)
- Leader runs WiFi AP (SSID: TeslaPW_xxxxxx) on 192.168.91.1
- Only interface where
/tedapi/v1and/tedapi/device/*/v1are accessible - Requires WiFi connection to the PW's own AP
- Adding 192.168.91.200 to wired bridge + routing does not bypass interface check
Zero packets observed between Powerwalls on the wired LAN (tcpdump on bridge monitoring all 3 PW IPs). The PWs communicate via internal bus (CAN bus or power-line communication), not Ethernet. This means there's no inter-PW traffic to intercept or MITM for extracting mTLS certificates.
Comprehensive fuzzing performed on the leader's wired LAN interface:
- 577 paths with Tesla-specific wordlist (tedapi, hermes, protobuf, grpc, etc.)
- 704 paths with non-standard prefixes (/rpc/, /grpc/, /energy/, /device/, etc.)
- Multiple HTTP methods (GET, POST, PUT, DELETE, PATCH, OPTIONS)
- Various auth combinations (none, Bearer, Basic, cookies)
Result: Zero new endpoints discovered. Only the 5 known endpoints + the
/tedapi/device/* wildcard 403 catchall exist on port 443.
During the Fleet API key registration process
(fleet_register.py / python -m pypowerwall register), we only
toggled the leader Powerwall's breaker to authorize the RSA key
pairing. The followers were never power-cycled or individually
registered.
The registration flow:
- Generate RSA-4096 key pair
- Upload public key to Tesla Fleet API
- Send
ENERGY_DEVICE_AUTHcommand via Fleet API to register key on-device - Toggle Powerwall breaker (physical authorization)
- Wait for
state=3(key accepted)
We only did steps 4-5 for the leader. The followers were connected but their breakers were never toggled.
When using routing_address to forward to a follower:
- Layer 1 (RSA) passes on the leader — our key is registered
- The leader forwards the message internally to the follower
- Layer 1 on the follower fails — our key isn't registered there
- The follower silently drops the message -> timeout
When connecting directly to a follower's /tedapi/v1r:
- The follower explicitly responds
"v1r: client not authorized" - Confirms the RSA key is not in the follower's keychain
POST /api/1/energy_sites/{site_id}/commandusesidentifier_type: 1which targets the gateway (leader) only- No device-level command endpoints exist (tested:
/devices/{id}/command-> 404) - Different
identifier_typevalues (0, 2, 3) return 500 errors - Record updates via Fleet API only support description changes
- Per-device key registration via Fleet API does not appear possible
Understanding why follower queries fail requires understanding the two independent auth layers:
The outer RoutableMessage contains an RSA signature (SignatureData)
that authenticates the request. This is verified by the Powerwall's
firmware against its registered key list.
- Signing uses PKCS1v15 + SHA512 over a TLV-encoded metadata block
- TLV includes: signature type (RSA=7), domain, personalization (DIN), epoch, expiry, and the inner protobuf bytes
- Personalization must be the leader DIN — signing with a follower DIN returns fault or "client not authorized"
- HTTP auth (Bearer token) is completely ignored by
/tedapi/v1r
The inner TEDAPI message contains pb.message.payload.send.code — a
pre-signed ECDSA signature over the exact query text. This is
hard-coded in pypowerwall and was presumably generated by Tesla.
- If you modify the query text (add/remove fields, change whitespace), the inner response contains "invalid signature"
- The RSA v1r signature IS accepted (fault=0), but the inner ECDSA check fails independently
- Must use the exact original query text from pypowerwall source
- CRITICAL FINDING: The ECDSA signs the query TEXT only — NOT
the variables (
pb.message.payload.send.b.value). Variables are freely modifiable without breaking the signature.
| Protobuf Field | Signed by ECDSA? | Notes |
|---|---|---|
payload.send.payload.text (query) |
YES | Cannot modify a single character |
payload.send.b.value (variables) |
NO | Freely changeable |
payload.send.code (ECDSA sig) |
N/A | Must match the query text |
This means we can change:
- Component type filters (
"types": [...]) - Signal name lists (
*SignalNames: [...]) - Any other GraphQL variable parameter
pypowerwall contains 3 distinct (query_text, ecdsa_code) pairs:
-
DeviceControllerQuery v1 — Large formatted query with
esCan.bus,batteryBlocks,neurio,phaseDetection,inverterSelfTests. Variables:"{}"(none). -
DeviceControllerQuery v2 — Minified query adding
teslaRemoteMeter,ieee20305, and acomponents { msa: components(filter: $msaComp) }section. Variables:$msaComp(component type filter),$msaSignals(signal name list). -
ComponentsQuery — PW3-specific, 5 independent filter slots:
$pchComponentsFilter,$bmsComponentsFilter,$hvpComponentsFilter,$pwsComponentsFilter,$baggrComponentsFilter, each with a corresponding*SignalNameslist.
Enumerated by testing each type individually via ComponentsQuery:
| Type | Entries | Has Live Data | Notes |
|---|---|---|---|
| PCH | 1 | YES | Leader inverter. pn=1758000-03-J sn=FTIxxxxxxxxxxxxx |
| PW3BMS | 4 | 2 live + 2 ghost | Battery management. Leader + expansion live; 2 ghosts from Dec 2025 |
| PW3HVP | 4 | 2 live + 2 ghost | High voltage packs. Leader (1800420-11-F/TGxxxxxxxxxxxxx) + expansion (1807000-20-B/TGyyyyyyyyyyyyy) |
| PW3SAF | 1 | YES | Safety module. pn=1758000-03-J sn=FTIxxxxxxxxxxxxx |
| BAGGR | 1 | YES | Battery aggregator. NumBatteriesConnected=2, Expected=2 |
| TEMSA | 2 | 1 live | System meter. pn=1841000-01-C sn=GFxxxxxxxxxxxxx |
| STSTSM | 1 | Alerts only | System manager. pn=1707000-21-K sn=TGzzzzzzzzzzzzz (leader DIN) |
| TESYNC | 1 | NO | Empty (no serial, no data) |
| TEPINV | 10 | NO | PW2 stub. Empty placeholder slots |
| TEPOD | 10 | NO | PW2 stub. Empty placeholder slots |
| TETHC | 10 | NO | PW2 stub. Empty placeholder slots |
| PVAC | 8 | NO | PW2 stub. Empty placeholder slots |
| PVS | 8 | NO | PW2 stub. Empty placeholder slots |
Total: 61 component entries (13 with data from PW3 types, 48 empty PW2 stubs)
The GraphQL ComponentFilter input type accepts multiple fields beyond
just types:
| Filter Field | Syntax | Works? | Effect |
|---|---|---|---|
types |
["PCH", "PW3BMS"] |
YES | Filters by component type |
dins |
["1707000-xx-K--TG..."] |
YES | Filters by component DIN. Returns 0 for follower DINs |
serialNumbers |
["TG..."] |
Accepted | Does not error, but doesn't filter differently |
parentDins |
["1707000-xx-K--TG..."] |
Accepted | Appears to be ignored (returns all) |
parentDin |
"1707000-xx-K--TG..." |
Accepted | Appears to be ignored (returns all) |
devices |
["1707000-xx-K--TG..."] |
Accepted | Does not error, but doesn't filter differently |
componentIds |
["TG..."] |
Accepted | Does not error, but doesn't filter differently |
partNumbers |
["1758000-03-J"] |
Accepted | Does not error, but doesn't filter differently |
Key finding: dins genuinely filters and returns 0 entries when no
component matches the given DIN. This proves the follower data does not
exist in the leader's component database — there's nothing to filter down to.
BMS[2-3] and HVP[2-3] appear in ComponentsQuery but are stale ghosts:
| Field | BMS[0-1] (live) | BMS[2-3] (ghost) |
|---|---|---|
| Timestamp | 2026-03-01 (current) | 2025-12-17 (2.5 months old) |
| Energy remaining | 3-5 kWh (varies) | 0.2 kWh (static) |
| Full pack energy | 14.25 kWh | 14.47 kWh |
| HVP serial | Present | Empty |
| HVP state | HVP_ACTIVE | null |
| Alerts | Normal | NVRAM_Config_Error, HVP_HVI_Comms, Nvram_Filecache |
| BAGGR status | CONNECTED | NOT_TARGETED_NOT_PRESENT |
The BAGGR confirms: NumBatteriesConnected=2, NumBatteriesExpected=2,
slots 2-3 = NOT_TARGETED_NOT_PRESENT. The leader firmware only manages
its own 2 packs (leader + expansion). Ghost entries are likely from
initial commissioning in December 2025.
DeviceControllerQuery returns esCan.bus with pre-allocated arrays:
- PINV: 10 entries, ALL
isMIA=true, state=PINV_Off, all zeros - POD: 10 entries, ALL
isMIA=true, energy=0 - PVAC: 8 entries, ALL
isMIA=true, state=PVAC_Init - PVS: 8 entries, ALL
isMIA=true - THC: 10 entries, ALL
isMIA=true - MSA: null
- SYNC: empty (no serial, no measurements)
The PW2-era CAN bus interface is non-functional on PW3. All data comes
through the PW3 components system instead.
| Component | Part Number | Serial Number | Role |
|---|---|---|---|
| PCH (inverter) | 1758000-03-J | FTIxxxxxxxxxxxxx | Leader inverter |
| PW3SAF (safety) | 1758000-03-J | FTIxxxxxxxxxxxxx | Same physical unit as PCH |
| PW3HVP (battery) | 1800420-11-F | TGxxxxxxxxxxxxx | Leader battery pack |
| PW3HVP (expansion) | 1807000-20-B | TGyyyyyyyyyyyyy | Expansion battery pack |
| TEMSA (meter) | 1841000-01-C | GFxxxxxxxxxxxxx | System power meter |
| STSTSM (system) | 1707000-21-K | TGzzzzzzzzzzzzz | System manager (= leader DIN) |
PCH serial FTIxxxxxxxxxxxxx is encoded on the CAN bus as hex ASCII:
PCH_packageSerialNumber_1_7 = "3142ITF" (hex) ("3142ITF"),
PCH_packageSerialNumber_8_14 = "7210000" (hex) ("7210000").
Read in reverse byte order per chunk to recover the original serial.
Full scan performed on the leader's wired LAN IP.
| Endpoint | Method | Auth | Notes |
|---|---|---|---|
/api/login/Basic |
POST | None | Returns token + cookies |
/api/meters/aggregates |
GET | Bearer | System-level power data |
/api/system_status/soe |
GET | Bearer | Battery percentage |
/api/system_status/grid_status |
GET | Bearer | Grid connected/islanded |
/tedapi/din |
GET | Bearer | DIN plain text |
/tedapi/info |
GET | None | Firmware version, DIN, device type |
/tedapi/v1r |
POST | RSA only | Ignores HTTP auth entirely |
/tedapi/v1— WiFi-only (192.168.91.1)/tedapi/device/*— wildcard catch-all, returns 403 for ANY path under it, even garbage paths like/tedapi/device/GARBAGE/anything- Tested 7 HTTP methods x 5 auth types x 3 body variants = ALL 403
- This is firmware-level interface blocking, not an auth issue
Tested on all 3 Powerwalls (leader + both followers), with and without auth:
/summary?mode=kiosk-> 404/api/site_info-> 404/api/status-> 404/api/powerwalls-> 404/api/devices/vitals-> 404
These are PW2/Gateway endpoints that don't exist on PW3 firmware.
Via v1r, queried all 7 FileStore API domains on the leader:
| Domain | Value | Files Found |
|---|---|---|
| CONFIG_JSON | 1 | config.json |
| GRID_CODE_REGIONS | 6 | grid_code_regions.csv |
| All others | 0,2,3,4,5 | Empty / no files |
No authorization, device registry, or key management files discoverable through FileStore.
- v1r via leader routing — Leader ignores follower DIN (returns own data)
or times out when using
routing_address - v1r direct to followers — RSA key not registered on followers, and Fleet API provides no per-device registration mechanism
- Standard API on followers — Returns 502 (no gateway service running)
- Port 8087 mTLS — Requires Tesla-signed client certificate (no bypass)
- Interface spoofing — PW3 checks physical NIC, not IP addresses
- Home WiFi network — Even more locked down (login 401, everything 403, v1r all TIMEOUT including leader queries)
- Internal 192.168.90.x — Same access controls as wired LAN
- API fuzzing — No hidden endpoints (1,281 paths tested)
- Inter-PW traffic — Zero Ethernet traffic between PWs (internal bus only)
- ECDSA variable manipulation — Can freely change component filters and signal names, but the leader's database simply doesn't contain follower component data. 13 valid types enumerated, all empty PW2 stubs or leader-only PW3 data.
- ComponentFilter
dinsfield — Genuinely filters components, returns 0 for follower DINs. Proves follower data is absent, not hidden. - esCan bus — All 48 PW2-style entries (PINV, POD, PVAC, PVS, THC) are isMIA=true with zero values. PW2 CAN bus is dead on PW3.
- PW2 endpoints —
/summary,/api/site_info,/api/status,/api/powerwalls,/api/devices/vitalsall return 404 on PW3.
WiFi TEDapi (/tedapi/device/{din}/v1 on 192.168.91.1) via the leader's
WiFi AP. This uses HTTP Basic Auth (no RSA) and supports per-device queries.
Requires maintaining a WiFi connection to the Powerwall's AP.
- Can toggling each follower's breaker during Fleet API registration register the RSA key on followers individually?
- Does the Fleet API
commandendpoint support any undocumented per-device targeting parameters? - Will future firmware updates add follower routing support to v1r?
- Could a Tesla-internal tool (like the installer app) register keys on followers?
Silly question: Why do you need the FleetAPI to register a key? I thought you send the commands to the powerwalls locally through the wifi connection to do that, and all you need do is power toggle to get the RSA key accepted?
https://github.com/Nexarian/tesla-solar-reporter/blob/main/pvi_status/register_local_client.py