SPAN Smart Panel
The span_panel driver polls a SPAN Smart Panel over its on-LAN REST API. It auto-discovers circuits, mirrors them as zones under the power subsystem, and exposes panel-level telemetry — main relay state, grid/feedthrough power, run config, DSM grid state, islanding state, and battery state-of-energy — as device attributes.
All traffic stays on the LAN. There is no SPAN cloud round-trip at runtime.
Status
Built against the SPAN documented REST API. Onboarding uses /api/v2/auth/register to exchange a passphrase for a JWT bearer; telemetry and control use /api/v1/*. The /api/v1 surface is documented to deprecate 2026-12-31 — when SPAN extends /api/v2 to cover telemetry, or the integration moves to MQTT/eBus, the transport swaps internally with no change to how you configure the device.
The panel's built-in MQTT/Homie 5.0 broker is intentionally not used.
Prerequisites
- SPAN panel reachable from the GEM controller on TCP/443 (HTTPS, self-signed cert accepted).
- A panel passphrase. There are two ways to mint one:
- SPAN Home app — issues a passphrase tied to your account.
- Door switch — press the panel door switch for proof-of-proximity, then complete the registration flow within the timeout window.
Setup
- System → Devices: add a device with driver
span_panel. Fill in:ip— panel LAN IPpassphrase— proof-of-proximity passphrase (secure)
- Save and enable. The driver:
- calls
/api/v2/auth/register(the only v2 call it makes) and caches the returned JWT as theaccess_tokendevice attribute, - reads
/api/v1/statusto populateserial_number,firmware_version,door_state, - calls
/api/v1/circuitsto enumerate circuits — each one is auto-created as a zone under thepowersubsystem (created on demand) withzone.address= the circuit UUID andcontrol=power_default, - starts polling
/api/v1/panel,/api/v1/circuits,/api/v1/storage/soe,/api/v1/islanding-stateevery 5 s, and re-reads/api/v1/status(serial / firmware / door) about once a minute (every 12 poll cycles).
- calls
Attributes
Device
| Attribute | Required | Type | Notes |
|---|---|---|---|
ip | yes | string | Panel LAN IP. |
passphrase | yes | string (secure) | Proof-of-proximity passphrase from the SPAN Home app or door-switch flow. |
port | no | int | Override the panel port (default 443 for https, 80 for http). Useful when proxying. |
protocol | no | string | https (default) or http. Use http only behind a plaintext proxy. |
poll_interval | no | int | Telemetry poll interval in seconds. Default 5, range 1..300. |
access_token | no | string (secure, readonly) | JWT cached after registration. The driver auto-refreshes by re-registering on 401/403. |
Runtime-populated (read-only):
| Attribute | Type | Source |
|---|---|---|
serial_number | string | registration response, refreshed from /api/v1/status (system.serial) |
firmware_version | string | /api/v1/status (software.firmwareVersion) |
main_relay_state | string | /api/v1/panel.mainRelayState |
grid_power_w | float | /api/v1/panel.instantGridPowerW |
feedthrough_power_w | float | /api/v1/panel.feedthroughPowerW |
run_config | string | /api/v1/panel.currentRunConfig |
dsm_grid_state | string | /api/v1/panel.dsmGridState |
islanding_state | string | /api/v1/islanding-state.islanding_state |
battery_soe_percent | float | /api/v1/storage/soe (soe.percentage) |
door_state | string | /api/v1/status (system.doorState) |
discovered_circuits | string (json) | Last circuits snapshot for diagnostics. |
last_error | string | Most recent failure reason (registration error, panel unreachable, etc.) — written whenever a poll or connect fails. |
Telemetry from /api/v1/panel, /api/v1/circuits, /api/v1/storage/soe, and /api/v1/islanding-state refreshes every poll (5 s by default). Identity fields read from /api/v1/status — serial_number, firmware_version, door_state — refresh only at connect and then about once a minute, so the door reading is not real-time.
Zone
Zone address is the circuit UUID from /api/v1/circuits. Per-circuit attributes populated by the poller:
| Attribute | Type |
|---|---|
state | string — on (relay closed) or off (relay open) |
relay_state | string — raw CLOSED / OPEN |
instant_power_w | float |
consumed_energy_wh | float |
produced_energy_wh | float |
priority | string — MUST_HAVE / NICE_TO_HAVE / NON_ESSENTIAL |
is_user_controllable | bool |
is_sheddable | bool |
is_never_backup | bool |
tabs | string (CSV) |
Per-circuit power feeds the Power Management dashboard: instant_power_w carries the power.circuit metric role (Top Circuits widget) and the two energy counters carry energy.consumed / energy.produced (Energy Today widget). instant_power_w is signed — positive is consumption, negative is backfeed/export. The device-level battery_soe_percent carries the battery.soc role, so a SPAN with attached storage shows up on the dashboard's battery readouts.
Configurable
| Attribute | Type | Default | Notes |
|---|---|---|---|
ct_clamp_reversed | bool | false | Correct a single physically-reversed CT clamp on a pure-load circuit — e.g. a fridge that reports a steady negative instant_power_w. When enabled, GEM negates instant_power_w and swaps the consumed_energy_wh / produced_energy_wh counters for that circuit. |
ct_clamp_reversed is a retrofit escape hatch — the proper fix is recommissioning the CT in SPAN. Do not enable it to paper over a tab-mapping swap (two circuits crossed at the panel), which would only hide the partner circuit's error instead of fixing it.
Commands
| Command | Args | Purpose |
|---|---|---|
on | address | Set the circuit's relay to CLOSED (zone on). |
off | address | Set the circuit's relay to OPEN (zone off). |
reboot | address, delay | Open the relay, wait delay seconds (default 5), close it again. Useful for power-cycling a non-essential load. |
All three relay commands are refused with circuit … is not user-controllable when SPAN marks the circuit non-controllable (e.g. the mains feed or never-backup loads). The driver checks the last cached circuit snapshot before sending, so the refusal is immediate.
Quirks
- SPAN's API serializes float32 telemetry as full float64, leaking artifacts like
1557.4998779296875for what was1557.5. The driver rounds to 1 decimal place — only ~7 significant digits are real. - The panel rejects relay writes for circuits where
isUserControllable=false. The driver checks the cached snapshot before sending and returns an error early. - After auth-related 401/403 errors the driver re-registers automatically using the stored passphrase. If the passphrase was rotated externally, registration will fail until the device row is updated.
Troubleshooting
| Symptom | Check |
|---|---|
registration returned no access token on first connect | Wrong passphrase, or the proof-of-proximity window expired. Mint a fresh passphrase from the SPAN Home app and retry. |
Repeated re-register failed warnings | Passphrase has been invalidated (e.g. rotated). Update the device's passphrase attribute with a freshly minted value. |
Circuit relay command returns circuit ... is not user-controllable | The circuit is locked at the panel level (e.g. main breakers, never-backup loads). Cannot be controlled remotely. |
| No new zones appear after wiring change | Reload the device (System → Devices → the Reload action on the device row) to re-call /api/v1/circuits. The poller does not auto-create new zones — it only refreshes telemetry on circuits discovered at connect. |
| Device shows offline or telemetry stops updating | Check the device's last_error attribute for the most recent failure reason. A reachable panel that fails every endpoint marks the device disconnected; any one endpoint succeeding keeps it connected. |