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_id: <device_id>, command: '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_id: <device_id>, command: '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. A value pasted with a stray leading/trailing space or newline is also tried trimmed if the value as entered 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. The current backup reserve is read back and resent unchanged; if that read fails or the reply carries no reserve, the command is refused rather than resetting the reserve. |
set_backup_reserve | percent | 0–100. The current operating mode is read back and resent unchanged; if that read fails or the reply carries no mode, the command is refused rather than switching the mode. |
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.- Each write sends both the mode and the reserve, so the driver reads the current values first and resends the one you didn't change. If that read fails the command returns an error and writes nothing, rather than resetting the other setting.
- 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.
- If the gateway refuses the login (a wrong password or email, or its lockout after too many failed attempts), the driver stops posting the login for 60 seconds, doubling after each further refusal up to 15 minutes. Editing the email or password, or reloading the device, retries straight away. While it is holding off, commands answer with the refusal and
(retrying in Ns). - If the gateway stops answering, the poller skips the rest of that cycle instead of waiting out a timeout on every endpoint, and after consecutive missed cycles it doubles the wait between polls, up to 5 minutes (or your
polling_interval, if that is longer). The first answered poll puts it back on the normal interval. - When nothing answers,
get_statusreturns anerroralongside its usualsoe/aggregates/ … fields, so a macro set to stop on error stops.
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, no reload needed: within one poll interval (~30 s) after a short outage, and within 5 minutes after a long one (the poller backs off while the gateway is unreachable). If data never appears after the gateway is reachable again, the cause is configuration (wrong IP/email/password) or reachability, not a transient hiccup. |
login http 401 (retrying in …s) | The gateway refused the login and the driver is holding off before trying again (see Known limitations). Fix the password or email; saving the change or reloading the device retries straight away. |