Tesla Powerwall (Local Gateway API)
The Tesla Powerwall driver polls a Tesla Backup Gateway over its on-LAN HTTPS API (TEG firmware) and surfaces state of charge, instantaneous site/load/solar/battery power flow, lifetime energy counters, pack capacity/degradation, online block count, grid status, and operating mode as device attributes. Per-Powerwall-block diagnostics (state-of-charge, inverter state, backup readiness) are flattened onto the device as well. It can also switch the operating mode (self-consumption / backup-only / autonomous) and adjust the backup reserve percentage. All traffic stays on the customer LAN — there is no Tesla cloud round-trip at runtime.
Status
Built against TEG 21.x / 22.x / 26.x — verified against firmware 26.10.3 on a 5-Powerwall / 3-inverter install. Read paths (/api/system_status/soe, /api/meters/aggregates, /api/system_status/grid_status, /api/operation, /api/system_status) and write paths (/api/operation for mode + reserve) are implemented. Older Backup Gateway 1 units running pre-TEG firmware used basic-auth without JWT and are not supported here.
Prerequisites
- Backup Gateway 1 or Backup Gateway 2 on TEG firmware, on the same LAN as the GEM controller, reachable on TCP/443.
- Tesla account email associated with the Powerwall installation.
- Gateway password — the last 5 characters of the gateway code printed on the QR-code sticker inside the Backup Gateway enclosure (e.g. code
HAKJGVLFCZ→ passwordVLFCZ), or a custom password set during commissioning by the installer. This is NOT the Tesla account password. You may paste the full gateway code — the driver falls back to its last 5 characters if the full value is rejected.
Setup
- Find the Backup Gateway IP.
- Tesla mobile app → Settings > Powerwall > Network.
- The router's DHCP table (look for
TeslaEnergyGateway-or the gateway hostname). arp -afrom the controller, looking for the gateway MAC.
- Locate the gateway password. Open the Backup Gateway enclosure (front cover, no power-down required) — there is a QR-code sticker on the inside that lists the gateway code. The gateway password is the last 5 characters of that code (e.g. code
HAKJGVLFCZ→ passwordVLFCZ). If you are unsure which part to enter, paste the full code — the driver tries it as entered and falls back to the last 5 characters if the full value is rejected. If the installer set a custom password during commissioning, use that instead (it is taken exactly as entered, never clipped). - Create the device. From Open Devices (System → Devices), add a device with driver
tesla_powerwall. Set:ip— the gateway LAN IP (e.g.,192.168.1.60)email— the customer's Tesla account emailpassword— the gateway password from step 2 (stored encrypted)
- Verify. From the Script Console:
You should see a JSON response withawait gem.command({device: <device_id>, action: 'get_status'});
soe,aggregates,operation,grid_status, andsystem_status. Runningget_statusalso refreshes the device attributes right away —battery_soc_percent,site_power_w,load_power_w,solar_power_w,battery_power_w,grid_export_w,grid_status, the lifetime energy counters, pack capacity, online block count, operating mode, and per-block diagnostics — and the background poller then keeps them current every ~30 s (set bypolling_interval). - (Optional) Per-Powerwall zones. If the install has multiple Powerwall battery blocks and you want per-block tracking on a Zone, run:
Note theawait gem.command({device: <device_id>, action: 'get_system_status'});
PackageSerialNumberof eachbattery_block, then create one zone per block, bound to this device, with the zone Address set to that serial. New Powerwall zones default to the Power subsystem (the zone editor pre-selects it). The driver does not create these zones for you — it only fills in ones you have already created — and they populate on the next background poll (~30 s), not from a manualget_status. Each block zone then carriesbattery_soc_percentandpower_w.
Energy dashboards
Once the device is reporting, its power and energy values flow straight onto GEM's generic energy widgets — no extra configuration, no per-site build. The driver already publishes the standard solar / grid / battery / load / state-of-charge values (and the lifetime energy counters) the dashboards look for, so a Powerwall is a first-class source for every widget on the Power Management dashboard:
- Power Flow — a live energy-flow diagram with Home in the center and Solar, Grid and Battery around it. Add it to any UI page (UIs → Pages → add widget → Power Flow) and pick this device under Power Device. Flow lines animate in the direction power is moving; the battery node fills to show state of charge and shows a bolt while charging.
- Battery Backup — state-of-charge ring, energy remaining (kWh), and estimated backup
runtime at the current load. Flips to "On Backup" when
grid_statusreports the grid is down. - Self-Sufficiency — share of the current home load being met locally (solar + battery) versus the grid.
- Energy Today — today's consumed / produced / net energy, accumulated since local midnight from the lifetime counters.
To combine several gateways — or a Powerwall plus separate solar/meter devices — into one diagram, enable Aggregate All Power Devices on the Power Flow widget instead of picking a single device; the aggregate state of charge is energy-weighted across the fleet. See Power Management for the full widget catalog and settings.
Attributes
Device
| Attribute | Required | Type | Notes |
|---|---|---|---|
ip | yes | string | Backup Gateway LAN IP. |
email | yes | string | Tesla account email tied to the install. |
password | yes | string (secure) | Gateway password — the last 5 characters of the gateway code on the QR sticker inside the enclosure, or a custom commissioning password. The full gateway code may be pasted; the driver falls back to its last 5 characters if the full value is rejected. |
port | no | int | HTTPS port. Default 443. |
polling_interval | no | int | Telemetry poll interval in ms. Default 30000, minimum 5000. |
request_timeout | no | int | HTTP request timeout in ms. Default 10000. |
Runtime-populated (read-only, written by the poller):
| Attribute | Type | Source |
|---|---|---|
battery_soc_percent | float | /api/system_status/soe |
site_power_w | int | /api/meters/aggregates (negative when exporting) |
load_power_w | int | /api/meters/aggregates |
solar_power_w | int | /api/meters/aggregates |
battery_power_w | int | /api/meters/aggregates (positive when discharging) |
grid_export_w | int | derived: -site_power_w (positive when exporting to grid) |
grid_status | string | /api/system_status/grid_status (SystemGridConnected / SystemIslandedActive / etc.) |
operating_mode | string | /api/operation (real_mode: self_consumption / backup / autonomous) |
backup_reserve_percent | int | /api/operation — reserve floor held for grid outages |
freq_shift_load_shed_soe | int | /api/operation — off-grid frequency-shift load-shed SoE threshold |
freq_shift_load_shed_delta_f | float | /api/operation — off-grid frequency-shift load-shed delta-frequency (Hz) |
solar_energy_exported_kwh | float | /api/meters/aggregates — lifetime cumulative solar production |
site_energy_imported_kwh | float | /api/meters/aggregates — lifetime cumulative grid import |
site_energy_exported_kwh | float | /api/meters/aggregates — lifetime cumulative grid export |
load_energy_imported_kwh | float | /api/meters/aggregates — lifetime cumulative home/load consumption |
energy_remaining_kwh | float | /api/system_status (nominal_energy_remaining) — energy stored right now |
full_pack_energy_kwh | float | /api/system_status (nominal_full_pack_energy) — usable pack capacity; trends down as packs age (degradation indicator) |
available_blocks | int | /api/system_status — count of online Powerwall blocks (a drop below install size means a unit went offline) |
Per-Powerwall-block diagnostics
The poller flattens the highest-signal fields from each entry of system_status.battery_blocks onto the device as battery_block_<n>_<attr>, where <n> is the 1-based block index. These are populated every poll regardless of whether per-block zones exist.
| Attribute suffix | Type | Source field | Notes |
|---|---|---|---|
serial | string | PackageSerialNumber | Block serial number. |
nominal_energy_remaining | int | nominal_energy_remaining | Energy stored in this block (Wh). |
nominal_full_pack_energy | int | nominal_full_pack_energy | Usable capacity of this block (Wh) — per-block degradation. |
p_out | int | p_out | Instantaneous block power output (W). |
pinv_state | string | pinv_state | Inverter state. |
pinv_grid_state | string | pinv_grid_state | Inverter grid-connection state. |
backup_ready | bool | backup_ready | Block is ready to carry backup load. |
wobble_detected | bool | wobble_detected | Power-quality wobble flag. |
disabled_reasons | string | disabled_reasons | Comma-joined list of reasons the block is disabled (empty when healthy). |
History is trended on the low-churn diagnostic fields (capacity, inverter/grid state, backup/wobble flags, disabled reasons); the high-churn electrical values (nominal_energy_remaining, p_out, block serial) are live-only to avoid flooding attribute history.
Zone
Zone address is optional. If set, it must match a battery_blocks[*].PackageSerialNumber from /api/system_status.
| Attribute | Type | Source |
|---|---|---|
battery_soc_percent | float | derived from nominal_energy_remaining / nominal_full_pack_energy |
power_w | int | battery_block.p_out |
state | string | convenience render of SOC% or watts |
Commands
| Name | Args | Description |
|---|---|---|
get_status | — | Snapshot of SOC + meter aggregates + operation + grid status + system status. Refreshes device attributes (including energy counters, pack capacity, online block count, and per-block diagnostics). |
get_soc | — | State-of-charge percent. |
get_power_flow | — | Site/load/solar/battery instantaneous power. |
get_operation | — | Mode + backup reserve. |
get_grid_status | — | Grid connected/islanded. |
get_system_status | — | Full system payload (battery blocks, inverters, etc.). |
set_mode | mode | Mode = self_consumption | backup | autonomous. |
set_backup_reserve | percent | 0–100. |
Known limitations
- The local API exposes raw cell SOC. The Tesla mobile app applies a 5–10% reserved-capacity offset, so the two readings will differ by design.
set_modeandset_backup_reservewrite to/api/operationbut the gateway commits the change on the next sitemaster cycle (~5 s). Reading back immediately may still show the old value.- Lifetime energy counters are surfaced at the meter level (
solar_energy_exported_kwh,site_energy_imported_kwh,site_energy_exported_kwh,load_energy_imported_kwh). Per-Powerwall-block lifetime charge/discharge throughput counters are not surfaced — only per-block live energy/capacity is. - Time-of-use scheduling (cost / rate plans) is not implemented.
Troubleshooting
| Symptom | Check |
|---|---|
login http 401 on first connect | Wrong gateway password or wrong email. The gateway password is the last 5 characters of the gateway code on the QR sticker inside the Backup Gateway (e.g. HAKJGVLFCZ → VLFCZ), NOT the Tesla account password. Entering the full code is tolerated — the driver retries with its last 5 characters automatically — so a persistent 401 means the email, the code itself, or a custom commissioning password is wrong. |
connect timeout on 443 | Gateway is on a different VLAN than the GEM controller. Add a firewall rule permitting GEM → gateway on TCP/443. |
set_mode returns http 500 | Sitemaster is not running (gateway in commissioning mode or post-firmware-update reboot). Wait 5 minutes and retry. |
| Battery SOC stays at 100% but Tesla app shows ~95% | Expected — the app applies a reserved-capacity offset. Local API exposes raw SOC. |
auth rejected (401) after a day or two | JWT auto-refresh failed. Check the device log for the re-auth attempt; if persistent, verify the gateway password has not been changed. |
| No data after a brief gateway/network outage at startup | The driver logs ... login failed, will retry on poll (or ... connect soe failed/connect error, will retry on poll) but keeps the poller running, re-authenticating on each cycle — so a momentary failure at connect time recovers on its own within one poll interval (~30 s), no reload needed. If data never appears after the gateway is reachable again, the cause is configuration (wrong IP/email/password) or reachability, not a transient hiccup. |