Home Assistant Integration for SPAN Panel, a smart electrical panel that provides circuit-level monitoring and control of your home's electrical system.
The software is provided as-is with no warranty or guarantee of performance or suitability to your particular setting.
IMPORTANT: This integration controls real electrical equipment. Circuit switches open and close physical relays. The GFE override button changes how the panel manages load shedding during power outages. These actions carry the same consequences as operating the panel manually — because they are. Automations can execute these actions without user presence; design them with the same care you would apply to any unattended electrical control. This integration is not a safety device and must not be relied upon for life-safety applications. Use this software at your own risk. If you cannot accept that risk, do not use this software. See LICENSE for the full warranty disclaimer.
The SPAN Client documentation has warnings regarding the use of the API (the API used by this integration) which should be heeded just as if you were using that API directly:
An API client that attempts to implement its own load-shedding decisions, grid-state detection, or other critical automation is operating outside the scope of what SPAN API was designed and engineered for. Such use is entirely at the client developer's and homeowner's own risk and may void the SPAN Panel Limited Warranty. See the SPAN API Scope & Responsibility Model in the SPAN API documentation.
This integration provides sensors and controls for understanding an installation's power consumption, energy usage, and controlling user-manageable panel circuits.
The integration includes a built-in dashboard accessible from the Home Assistant sidebar, providing real-time circuit-level power visualization, current monitoring with configurable alerts, and circuit settings for relays and load shedding. See Frontend Dashboard for details. You can optionally use the span-card Lovelace card for visualization and switch control.
The SPAN Panel PanelBench HA App lets you clone your panel's circuit layout for testing, or model an upgrade to evaluate firmware or integration changes in a sandbox before applying them to your real panel.
This integration communicates with the SPAN Panel over your local network using SPAN's official Electrification Bus (eBus) framework — an open, multi-vendor integration standard for home energy infrastructure. eBus uses the Homie Convention for MQTT topics and messages, with the panel's built-in MQTT broker delivering real-time state updates without polling.
- Home Assistant installed
- HACS installed
- SPAN Panel with firmware
spanos2/r202603/05or later - Panel passphrase (found via the SPAN app) or physical access to the panel door
- Install HACS
- Go to HACS in the left side bar of your Home Assistant installation
- Search for "Span"
- Open the repository and click "Download"
- Restart Home Assistant (you will be prompted by a repair notification)
- Go to
Settings>Devices & Services - Click
+ Add Integrationand search for "Span" - Pick your panel from the discovered list, or enter its IP address or
.localname (both shown in the mobile app) - The integration detects the panel as v2 and presents an authentication choice:
- Enter Panel Passphrase — type the passphrase found in the SPAN mobile app under On-premise settings
- Proof of Proximity — open and close the panel door 3 times, then click Submit
- Choose your entity ID naming pattern (see naming patterns below)
- Optionally adjust the snapshot update interval — 0 is real-time, up to 15 seconds based on CPU
If you encounter issues, restore from your backup or check the troubleshooting section below.
The following terms appear throughout this document and in the integration's sensors:
- Grid-forming entity (GFE) — The power source that sets the voltage and frequency reference for the home. When the utility grid is up, it is the GFE. When islanded on battery, the battery inverter becomes the GFE.
- Islanded — The home is electrically disconnected from the utility grid and running on its own power source, typically battery. Circuits may be shed to conserve battery life.
- Microgrid — When the home is islanded, the battery inverter creates a small, self-contained electrical grid for the home. This local grid functions independently of the utility — the inverter generates AC power at the correct voltage and frequency, and the home's circuits run on it just as they would on utility power.
- Microgrid Interconnect Device (MID) — A switch, part of or alongside the battery system, that disconnects the home from the utility grid during an outage. While open, the panel's sensors can only see the home side.
- Shedding — Automatically turning off lower-priority circuits to conserve battery during an outage, based on each circuit's configured shed priority.
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Current Power | Power | W | Total panel power (grid import/export) |
| Feed Through Power | Power | W | Feedthrough (non-breaker) power |
| Main Meter Produced Energy | Energy | Wh | Grid energy exported |
| Main Meter Consumed Energy | Energy | Wh | Grid energy imported |
| Main Meter Net Energy | Energy | Wh | Consumed minus produced |
| Feed Through Produced Energy | Energy | Wh | Feedthrough energy exported |
| Feed Through Consumed Energy | Energy | Wh | Feedthrough energy imported |
| Feed Through Net Energy | Energy | Wh | Feedthrough net energy |
| DSM State | — | — | dsm_on_grid (grid connected), dsm_off_grid (islanded), unknown. Derived from multiple eBus signals |
| Current Run Config | — | — | panel_on_grid (grid connected), panel_off_grid (islanded on PV/generator), panel_backup (islanded on battery), unknown |
| Grid Forming Entity | — | — | GRID, BATTERY, PV, GENERATOR, NONE, UNKNOWN. SeeGrid Forming Entity |
| Main Relay State | — | — | closed (power flowing), open (disconnected), unknown |
| Vendor Cloud | — | — | CONNECTED, UNCONNECTED, UNKNOWN |
| Software Version | — | — | Firmware version string |
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| L1 Voltage | Voltage | V | L1 leg actual voltage |
| L2 Voltage | Voltage | V | L2 leg actual voltage |
| Upstream L1 Current | Current | A | Upstream lugs L1 current |
| Upstream L2 Current | Current | A | Upstream lugs L2 current |
| Downstream L1 Current | Current | A | Downstream lugs L1 current. Off by default from 2.1.x — see below |
| Downstream L2 Current | Current | A | Downstream lugs L2 current. Off by default from 2.1.x — see below |
| Main Breaker Rating | Current | A | Main breaker amperage. Off by default |
L1/L2 Voltage and Main Breaker Rating have always been off by default; enable them from the panel's device page if you want them.
The three Feedthrough sensors and the two Downstream current sensors are off by default from 2.1.x. The eBus specification's maintainer has documented that
the panel's feedthrough (downstream lugs) figures cannot be relied on: the energy registers can decrease or go negative, the power reading is inverted relative
to every other terminal, and the downstream currents report the upstream service conductors. The defects predate r202633.
Existing installations keep all five, with their history and entity ids — Home Assistant applies the setting only when an entity is first created. If you use them somewhere, removing them is worth considering, but that is your call.
Created only when your panel publishes the shed-forecast capability, and only for the estimates it actually publishes.
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Time to Priority Shed | Duration | min | Estimated time before the next priority tier of circuits is shed |
| Backup Time Remaining | Duration | min | Estimated time before every sheddable circuit is shed (off-grid) |
Present only when the panel publishes them.
| Attribute | Type | On | Notes |
|---|---|---|---|
full_charge_time_to_priority_shed |
int | Time to Priority Shed | The same estimate assuming the battery starts full |
full_charge_total_time_remaining |
int | Backup Time Remaining | The same estimate assuming the battery starts full |
forecast_confidence |
string | both | The panel's own assessment:LOW, MEDIUM, or HIGH |
Created only when your panel publishes the pcs capability, and created whether or not the PCS is switched on — a PCS reporting a limit of 0 A is reporting a
state, not an absence.
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Import Limit | Current | A | The limit actually being enforced: the most restrictive active constraint (diagnostic) |
| Binding Constraint | Enum | — | Which constraint sets that limit: Firm Service Rating, Grid Envelope, Voltage Support, Off-Grid, Requested, Operator, None |
On Import Limit, and present only when the panel publishes them. These are the inputs the panel reconciled to produce the enforced limit above.
| Attribute | Type | Notes |
|---|---|---|
pcs_enabled |
bool | Whether the panel's PCS is enabled at all |
feed_import_limit |
float | The Firm Service Rating: the commissioned, always-on floor (A) |
operator_import_limit |
float | A cap imposed by a fleet or aggregator operator (A) |
off_grid_import_limit |
float | The import cap while islanded (A) |
requested_import_limit |
float | A voluntary limit requested by the owner or installer (A) |
<name>_enablement |
string | Per limit:UNSPECIFIED, UNCONFIGURED, DISABLED, ENABLED |
<name>_active |
bool | Per limit: whether that constraint is currently enforcing |
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Grid Power | Power | W | Grid power flow |
| Site Power | Power | W | Total site power (grid + PV + battery) |
| Battery Power | Power | W | Battery charge/discharge (+discharging, −charging). Only when BESS commissioned |
| PV Power | Power | W | PV generation (+producing). Only when PV commissioned |
From 2.1.x these live on a Solar device of their own rather than on the panel's card, alongside PV Power and PV Panel Link.
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| PV Vendor | — | — | PV inverter vendor (e.g., "Enphase", "Other") |
| PV Product | — | — | PV inverter product (e.g., "IQ8+") |
| Nameplate Capacity | Power | kW | Rated inverter capacity. Off by default |
If you upgraded, these keep their entity ids, unique ids and history — but not the panel's area, since an entity takes its area from its device and the Solar
device starts without one. Assign it an area, or anything area-scoped (dashboards, automations, voice targeting a room) stops matching them. New installations
get ids from the new device name — sensor.span_panel_solar_pv_vendor rather than sensor.span_panel_pv_vendor. Both are correct and neither changes again.
Deprecated:
| Sensor | Reason |
|---|---|
| DSM Grid State | Deprecated — still available, but users should rely onDSM State as DSM Grid State may be removed in a future version |
Your panel publishes its Microgrid Interconnect Device — the switch that disconnects your home from the utility during an outage — as a device of its own, linked to the panel. It appears automatically where the panel reports one; nothing existing moves onto it.
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Grid State | Enum | — | Health of the utility supply itself:up, down, degraded or unknown |
This is new information — the previous firmware did not report the utility supply at all. It is not DSM Grid State, which is whether your home is islanded: the grid can be down while your home runs happily off the battery.
A panel with no battery has no MID, and that is itself an answer. The specification makes backup capability structural — having a MID is what says a panel
can island — so Grid Islandable reads Off and Grid Forming Entity reads Grid rather than either going unavailable.
DSM Grid State keeps its entity id and history but is no longer inferred from the battery or the dominant power source; it now reads the islanding state the
MID actually senses.
Applies to Current Power, Feed Through Power, Battery Power, PV Power, Grid Power, and Site Power sensors.
| Attribute | Type | Notes |
|---|---|---|
voltage |
string | Nominal panel voltage ("240") |
amperage |
string | Calculated current (power / voltage) |
Grid Power carries one more, because its name is only true in some wiring:
| Attribute | Type | Notes |
|---|---|---|
at_service_entrance |
boolean | Whether this panel's upstream lugs are where the utility actually connects |
Grid Power reads the upstream lugs. That is grid flow when those lugs are the utility connection point, which is the ordinary case. Put a battery between the
utility and your main lugs, or feed this panel from another panel, and the same reading becomes this panel's supply while Grid Power Flow stays the
whole-site figure — so the two legitimately disagree. When at_service_entrance is false, use Grid Power Flow for site-level grid import and export.
| Attribute | Type | Notes |
|---|---|---|
panel_size |
int | Total breaker spaces (e.g., 32, 40) |
wifi_ssid used to appear here. It moved to the Wi-Fi Link binary sensor below; a template reading it from this sensor should be pointed there.
| Attribute | Type | Notes |
|---|---|---|
wifi_ssid |
string | Network this link is to. Absent when the panel publishes no SSID |
Created automatically when a SPAN Drive or other EVSE is commissioned on the panel. Each EVSE appears as a separate sub-device linked to the panel via
via_device. Vendor, product, serial number, and software version are surfaced as device info attributes — not separate entities.
The EVSE device name includes the panel device name prefix for collision avoidance across multi-panel installations and to support HA's bulk device rename feature. A display suffix differentiates multiple chargers on the same panel:
- Friendly names (
USE_CIRCUIT_NUMBERS=False): suffix is the fed circuit's panel name (e.g., "Garage") - Circuit numbers (
USE_CIRCUIT_NUMBERS=True): suffix is the EVSE serial number (e.g., "SN-EVSE-001") - No suffix available: the display suffix is omitted entirely (no empty parentheses)
| Naming Mode | Example Device Name | Example Entity ID |
|---|---|---|
| Friendly names | Main House SPAN Drive (Garage) |
sensor.main_house_span_drive_garage_charger_status |
| Circuit numbers | Main House SPAN Drive (SN-EVSE-001) |
sensor.main_house_span_drive_sn_evse_001_charger_status |
| No suffix | Main House SPAN Drive |
sensor.main_house_span_drive_charger_status |
The circuit that feeds a charger has its sensors shown on the charger's device. Their entity IDs name the charger alone —
sensor.main_house_span_drive_garage_power — matching the charger's other sensors. A feed sensor you already have keeps its current id until you press
Recreate entity IDs, which offers the charger-named one.
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Charger Status | Enum | — | OCPP-based states: AVAILABLE, PREPARING, CHARGING, SUSPENDED_EV, etc. Translated |
| Advertised Current | Current | A | Amps offered to the vehicle |
| Lock State | Enum | — | LOCKED, UNLOCKED, UNKNOWN. Translated |
| Part Number | — | — | Charger part number (diagnostic,off by default) |
| Sensor | Device Class | Notes |
|---|---|---|
| Charging | Battery Charging | ON when status is CHARGING |
| EV Connected | Plug | ON when status is PREPARING, CHARGING, SUSPENDED_*, or FINISHING — a vehicle is plugged in |
| EVSE Panel Link | Connectivity | Whether the panel can reach the charger. A different fact from EV Connected, and it can disagree |
EVSE Panel Link is not EV Connected. EV Connected is what the charger says about the cable in front of it; EVSE Panel Link is what the panel says about whether it can reach the charger at all. A charger part-way through a session behind a lost link reports a plugged-in vehicle and a dead link at the same time. EVSE Panel Link is a diagnostic and appears only where the circuit feeding that charger publishes the link record.
| Control | Platform | Unit | Notes |
|---|---|---|---|
| EVSE Charge Current Limit | Number | A | The charge-current ceiling you can lower. Bounded by the installer-commissioned maximum |
The maximum is read from the panel, never assumed: it is the current the charger was commissioned for, and a value above it is refused rather than clamped. The
control is created only where the panel declares the limit settable, and reports unavailable while the panel has not published the commissioned maximum that
bounds it. A change the panel has acknowledged but not yet applied appears as a charge_current_limit_target attribute while the state stays the limit the
charger is still enforcing.
| Attribute | Source |
|---|---|
| Manufacturer | vendor-name |
| Model | product-name |
| Serial Number | serial-number |
| Software Version | software-version |
When a Battery Energy Storage System (BESS) is commissioned, the integration creates a separate BESS sub-device linked to the panel via via_device. The BESS
device uses manufacturer, model, serial number, and software version from battery metadata as device info attributes.
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Battery Level | Battery | % | State of energy as percentage |
| Battery Power | Power | W | Same entity as Power Flow Battery Power, shown on BESS sub-device |
| Meter Power | Power | W | The BESS's own meter (+discharging, −charging), agreeing with Battery Power |
| Communication State | — | — | The BESS's report of its own link health (diagnostic, disabled by default) |
| BESS Vendor | — | — | Battery system vendor (diagnostic) |
| BESS Model | — | — | Battery system model (diagnostic) |
| BESS Part Number | — | — | Battery system part number (diagnostic,off by default) |
| BESS Serial Number | — | — | Battery system serial number (diagnostic) |
| BESS Firmware | — | — | Battery system firmware (diagnostic) |
| Nameplate Capacity | Energy Storage | kWh | Rated battery capacity (diagnostic,off by default) |
| Stored Energy | Energy Storage | kWh | Current stored energy (diagnostic) |
| Sensor | Device Class | Notes |
|---|---|---|
| BESS Connected | Connectivity | Whether the BESS is communicating with panel |
Applies to Main Meter and Feed Through energy sensors.
| Attribute | Type | Notes |
|---|---|---|
voltage |
string | Nominal panel voltage ("240") |
| Sensor | Device Class | Unit | Notes |
|---|---|---|---|
| Power | Power | W | Instantaneous circuit power (+producing for PV, +consuming otherwise) |
| Produced Energy | Energy | Wh | Cumulative energy produced |
| Consumed Energy | Energy | Wh | Cumulative energy consumed |
| Net Energy | Energy | Wh | Net energy (sign depends on device type — PV circuits invert) |
| Current | Current | A | Measured circuit current. Only when panel reportscurrent_a |
| Breaker Rating | Current | A | Circuit breaker amperage (diagnostic). Only when reported |
| Attribute | Type | Notes |
|---|---|---|
tabs |
string | Breaker slot position(s) |
voltage |
string | 120 or 240 (derived from tab count) |
always_on |
bool | Whether circuit is always-on |
relay_state |
string | OPEN / CLOSED / UNKNOWN |
relay_requester |
string | Who requested relay state |
shed_priority |
string | API value: NEVER / SOC_THRESHOLD / OFF_GRID / UNKNOWN |
is_sheddable |
bool | Whether circuit can be shed |
pcs_managed |
bool | Whether the panel's Power Control System manages this circuit. Present only when the circuit reports it |
pcs_priority |
int | This circuit's shed order under an active import limit — distinct fromshed_priority, which is the backup tier |
| Attribute | Type | Notes |
|---|---|---|
tabs |
string | Breaker slot position(s) |
voltage |
string | 120 or 240 (derived from tab count) |
| Sensor | Device Class | Notes |
|---|---|---|
| Door State | Tamper | Panel door open/closed |
| Ethernet Link | Connectivity | Wired network status |
| Wi-Fi Link | Connectivity | Wireless network status |
| Panel Status | Connectivity | Overall panel online/offline |
| Grid Islandable | — | Whether the panel can island from the grid. Off on a panel with no MID — see below |
| PCS Active | Running | Whether the Power Control System is limiting import right now. Only when the panel runs one |
| PV Panel Link | Connectivity | Whether the panel can reach the solar inverter. Only when the feeding circuit reports it |
Removed from binary sensors:
| Sensor | Reason |
|---|---|
| Cellular (wwan) | Replaced byVendor Cloud sensor (cloud connectivity) |
| Entity | Type | Notes |
|---|---|---|
| Breaker | Switch | On/off relay control |
| Circuit Priority | Select | Controls when the circuit is shed during off-grid (translated, see below) |
| Entity | Type | Notes |
|---|---|---|
| GFE Override: Grid Connected | Button | Tell the panel the grid is up when BESS communication interrupted |
The eBus schema is vendor-extensible, so your panel can publish a device type this integration has never modeled. Rather than ignoring it, the integration gives it a card of its own hanging off the panel, carrying whatever identity it publishes, with its readings as entities beneath it.
Everything adopted arrives disabled and filed under Diagnostics until you promote it from the dashboard Adopted tab; the new-entity
notification names the device so you can find it. A property the device accepts writes to becomes a control rather than a reading — a boolean becomes a
switch, an enumeration becomes a select, a number becomes a number entity — and those arrive switched off too.
Three things worth knowing before you build on one:
-
Nothing adopted enters long-term statistics until you say it should. Adopted entities arrive with no statistics class, because the correct one is not published on the wire. You are welcome to supply it in the Adopted tab.
-
A new property on a device this integration already models is adopted too, but as a reading on that device's existing card rather than as a device of its own.
-
What the delete button does, since it is not quite what you would expect:
- Delete a reading while your panel is still publishing that property and it comes back — switched off — at the next reload, carrying whatever you curated for it. Curation is keyed on the wire address rather than on the entity, so deleting the entity does not discard it. There is no setting to suppress the entity, because leaving it switched off is already that.
- Delete one after your panel has stopped publishing it and it stays gone, because nothing exists to recreate it from.
So deletion means "hide it until next time" for a live reading and "clear it out" for a dead one, and your panel decides which.
These entities are permanent in id, not in identity. If one of these readings is later modeled properly (delivered as an official part of the integration), that entity is a new entity with its own id and its own history — the adopted one is not renamed into it. That is why a vendor reading you have come to depend on is worth raising in an issue: being modeled is what turns it into something with a real name, a proper category and statistics out of the box.
The integration will not guess what an adopted reading means. You are not guessing — it is your device — so the built-in dashboard has an Adopted tab where you can tell the integration about the reading.
A row that arrived as a control rather than a reading — a switch, a select or a number box — offers prominence alone, because a device class and a statistics class both describe a value being read.
You are only offered choices your panel's own declaration allows. A statistics class is offered only on a reading whose declared datatype is numeric; the device classes listed are the ones that fit both what the panel says the reading is and the unit it publishes, so a text reading is never offered a class that expects a number, and the unit itself stays whatever the publisher sends.
Saving reloads the integration, and setting or clearing a statistics class asks you to confirm before it does.
Two consequences are what that confirmation is about:
total_increasingtells Home Assistant the reading is a meter that only counts up. The recorder treats a drop of more than a tenth as the meter being reset and starts a new cycle, so choosing it for a reading that legitimately falls will manufacture consumption that never happened. Choose it only for a genuine lifetime total.- Removing a statistics class stops statistics, and Home Assistant will say so. If an entity already has statistics and then loses its statistics class, Home Assistant raises a repair notice against it — a warning rather than something with a fix button — and stops compiling new statistics for it. The statistics already collected are not deleted, and the notice clears by itself if you put a statistics class back.
This section explains how the SPAN panel manages power sources and load shedding when a Battery Energy Storage System (BESS) is installed, and what the integration can and cannot tell you about grid status.
The Grid Forming Entity (GFE) sensor identifies which power source provides the voltage and frequency reference for the home — not which source is producing the most watts. When GFE is Grid, the utility grid sets the reference and all circuits remain on, even if 100% of consumption comes from solar. When GFE is Battery, the battery inverter is the reference and circuits are shed based on each circuit's configured shed priority.
| GFE Value | Meaning |
|---|---|
| GRID | Panel is grid-connected (includes generator power, see deep dive) |
| BATTERY | Panel is islanded, running on battery |
| PV | Panel is islanded, running on solar (future) |
| GENERATOR | Panel is islanded, running on generator (future) |
| NONE | Panel is islanded with no power source |
| UNKNOWN | State not yet determined or fault condition |
When a BESS is installed, the panel relies on the BESS to determine whether the grid is online and to set the GFE accordingly. If BESS communication is lost while the panel is islanded, the GFE value becomes stale — it may show Battery when the grid has actually been restored, causing unnecessary shedding to continue.
On a panel with no battery, GFE is Grid, on both firmware generations. The newer firmware moves this value onto the Microgrid Interconnect Device, which is part of a battery system — so a panel without one has nothing publishing it. The answer is still settled, by what cannot be there: Battery needs a BESS (which brings a MID), PV cannot form a grid on its own (anything that can is a grid-forming inverter, which is a MID), and None describes a panel supplying nothing, which is a panel that is not reporting at all. What remains is a generator, and SPAN with no generator interface treats one as the grid. So Grid is what a battery-less panel reports, which is what the older firmware reported too.
Grid loss — The panel independently detects grid loss via its own voltage monitoring, even if BESS communication is already lost. The MID is still closed at this point, so the panel's sensors see the real voltage drop and respond immediately.
Grid restoration while islanded — Not detectable by the panel. While the MID is open, the panel's sensors are on the home side and measure only battery-supplied power. Grid restoration on the utility side of the open MID is invisible to any panel-side measurement. This is a physical limitation, not a software gap. A utility-side sensor — such as a current clamp (e.g., Emporia Vue), ATS/MTS contact closure, or any device that can see the grid side of the MID — integrated into Home Assistant as a binary sensor can provide this signal.
The integration's DSM State sensor combines multiple panel signals to provide defense-in-depth for grid status detection. It corroborates the Grid Forming
Entity with BESS grid state and power measurements, which adds confidence during transient inconsistencies and detects some edge cases — for example, when BESS
communication is lost while on-grid and the grid subsequently drops, the panel self-corrects via voltage detection and the corroborating signals confirm it.
However, when the panel is islanded and the MID is open, all of the panel's signals measure the home side. No combination of panel-sourced data can detect grid restoration in this state. Only an external signal (utility-side sensor) or manual confirmation via the GFE Override button can resolve it.
The GFE Override: Grid Connected button tells the panel that the grid is back and shedding can stop. When the BESS restores communication, it automatically reclaims control and the override is superseded — no manual undo is needed.
Risk asymmetry — Telling the panel to shed (conservative direction) is low-risk; worst case is unnecessary circuit disruption. Telling the panel the grid is back when it is not means unmanaged battery drain and reduced runtime, which could affect critical equipment. The battery protects itself by disconnecting when depleted, so there is no overload risk, but runtime will be reduced. Use the override button only with confidence that the grid has actually been restored — via a utility-side sensor or manual confirmation.
WARNING — Do not automate the GFE override button based on DSM State — it inherits the same MID blind spot described above and will read dsm_off_grid
even after the grid is restored. Manual confirmation or an external sensor is required before pressing the button.
When bess_connected returns to on, no action is needed — firmware resumes normal GFE management automatically.
For a detailed discussion of failure scenarios, the MID topology, generator and non-integrated BESS behavior, and /set risk analysis, see
BESS & Grid Management Deep Dive.
Controls how often the integration rebuilds the panel snapshot from incoming MQTT data. The SPAN panel publishes high-frequency MQTT messages (~100/second), but each individual message is a cheap dictionary write. The expensive operation — rebuilding the full snapshot and dispatching entity updates — is rate-limited by this timer.
- Default: 1 second
- Range: 0–15 seconds
- Set to 0 for no debounce (every MQTT message triggers a snapshot rebuild)
- Increase on low-power hardware (e.g., Raspberry Pi) to reduce CPU usage
Configure via Settings > Devices & Services > SPAN Panel > Configure > General Options.
The integration provides flexible entity naming patterns, configured during initial setup:
-
Friendly Names (Recommended for new installations)
- Entity IDs use descriptive circuit names from your SPAN panel
- Example:
sensor.span_panel_kitchen_outlets_power - Renaming a circuit in the SPAN app updates the displayed name automatically — the integration reloads itself to pick the new name up, since it travels as the entity's original name and only a rebuild refreshes it; the entity ID changes only if you accept the offer from Recreate entity IDs
- More intuitive for automations and scripts
-
Circuit Numbers (Stable entity IDs)
- Entity IDs use generic circuit numbers
- Example:
sensor.span_panel_circuit_15_power - Entity IDs stay stable when circuits are renamed
- Friendly names still sync from SPAN panel for display
The integration supplies only the circuit half of the ID shown above — Kitchen Outlets Power or Circuit 15 Power. Home Assistant composes the rest from your
own entity ID settings (entity_id_parts, Home Assistant 2026.8 and newer), which decide whether the device name and the area are prefixed.
For an entity that already exists, the suffix wording in that half is read back from the ID the entity has rather than reworded. Circuit energy IDs have shipped
in two spellings — consumed_energy and energy_consumed — so an installation is offered whichever one it is already using, and never the other; only new
sensors get the consumed_energy word order that the panel-level sensors use. Because your entity ID settings decide the rest of the ID, an installation whose
circuits were named before those settings existed — a SPAN Drive feed circuit, a second panel, or an installation predating the device prefix — may be offered
an ID with a different device half. Entities you already have keep the IDs they have until you press Recreate entity IDs and accept the offer.
SPAN panels occasionally report lower energy readings for cumulative energy sensors after firmware updates or resets. Home Assistant's statistics engine interprets any decrease as a counter reset, creating negative spikes in the energy dashboard.
When enabled, the integration automatically detects these dips and maintains a cumulative offset per sensor so Home Assistant always sees a monotonically increasing value.
- Default for new installs: ON
- Default for existing installs: OFF (enable via General Options)
- Threshold: 1.0 Wh minimum to avoid false triggers from float precision noise
- Disabling: Clears all accumulated offsets (starts fresh if re-enabled)
A dip is compensated as soon as it is seen, but not believed straight away. A counter reset is permanent — the counter restarts low and counts up from there — so a reading that drops and then returns to where it was is a transport artifact rather than a reset, and its offset is taken back. The offset stays provisional until a later reading either disproves it (the counter comes back) or corroborates it (the counter climbs from the new, lower base).
The persistent notification therefore lists a dip once it settles — when that window closes — and a dip that is disproved produces no notification at all — the sensor was compensated the whole time and nothing needs your attention. Reporting on corroboration alone left a notice standing for an event the next reading undid, which a persistent notification cannot take back the way the offset can. Seeing no notification after a momentary dip is the feature working, not failing.
Diagnostic attributes (visible when compensation is active):
| Attribute | Description |
|---|---|
energy_offset |
Cumulative Wh compensation applied (when > 0) |
last_dip_delta |
Size of the most recent dip in Wh |
Configure via Settings > Devices & Services > SPAN Panel > Configure > General Options.
The power sensors report with the exact precision from the SPAN panel, which may be more decimal places than you need. By default, sensors display with
precision 2 (e.g., 0.00), except battery percentage which uses precision 0 (e.g., 39).
You can change the display precision for any entity via Settings > Devices & Services > Entities tab. Find the entity, click on it, click the gear wheel,
and select your preferred precision from the "Display Precision" menu.
The integration cannot enforce much on its own. Anything holding the eBus broker password — including another integration in the same Home Assistant process — talks to the panel directly, outside Home Assistant's permission model.
What pinning the panel's certificate authority buys. The authority is fetched over your network on a connection with nothing to verify itself against, because it is the anchor everything else is checked against:
- A listener cannot read your passphrase or the credentials the panel returns for it.
- A device actively in the path at that first fetch can answer with an authority of its own and read both. Pinning alone cannot detect this.
Comparing the fingerprint against another source closes the second case. It is in diagnostics under panel_ca, in the setup log, and reported identically by
another install of this integration on the same panel. After the first pin, any change stops the integration and raises a repair — see Troubleshooting.
| Situation | What happens |
|---|---|
| Setting up by hostname | Verified, never relaxed. A domain joins the certificate's SAN only after you authenticate; everything before that runs against an address the certificate already names. |
| Panel announces a new address, or you re-add it | The entry moves only if the candidate serves a certificate its own anchor validates. Otherwise the move is refused and logged atWARNING. |
| The panel really has moved | UseReconfigure. A host that does not chain is refused, one that does not answer is reported unreachable, and one that chains but is not named is your panel — move it to an FQDN (the panel regenerates its certificate around that name) or the panel's .local name (already covered). A bare new IP the certificate does not name is refused. |
| A panel announced by anadd-on | The add-on's ports are taken as published, because add-ons reallocate their own. The address you configured is kept while it still answers for that panel, and replaced only when it has stopped answering. |
| The entry has no anchor, or a stored one won't load | Reauthenticate acquires one before either sign-in method is offered. |
Entries from before pinning pin at the first startup that reaches the panel, logged at WARNING with the fingerprint. Until that succeeds the authority is
re-fetched over plaintext on every connection and whatever answers is trusted. If the panel is unreachable the integration starts anyway and retries, because
refusing to start would remove the integration without making the credential any safer.
Proxies cut both ways. One terminating the broker port with a certificate of its own leaves the entry unpinned, warning at every start with no repair raised. One terminating only port 443 lets the entry pin, but startup then refuses to connect and keeps retrying, with a repair naming the port — every REST call a pinned entry makes, the schema read at startup included, verifies against the pin, and a certificate the pin does not validate is refused rather than downgraded to plaintext — until the port serves the panel's own certificate. If your panel serves TLS on another port, the setup flow asks once you have moved the HTTP port off 80, and Reconfigure offers the HTTPS port to any pinned entry.
Four options in Settings → Devices & Services → Span Panel → Configure. Every one defaults to the behavior your panel already has, so upgrading changes nothing until you choose otherwise.
| Option | What it does | What it does not do |
|---|---|---|
| Who may operate the panel | Administrators only refuses circuit switches, priority selects, the GFE override, EVSE limits and adopted controls from non-admin users. Nobody stops creating those entities altogether. |
Neither affects sensors, and neither constrains anything holding the broker password. |
| Allow control without a logged-in user | Turning it off refuses commands from automations, scripts and other integrations, which arrive with no user attached. | It cannot tell a well-behaved automation from a runaway one — only that neither has a user. |
| Control lock auto-relock | Adds a switch that, while armed, refuses every control command. Anyone can arm it; only an administrator can disarm it, and never an automation. | It is not a password. It defends against misclicks and runaway automations, which is what a local control can actually do. |
| Relay debounce | Refuses a second command to the same circuit's relay within the window. | It is per circuit, so an automation cycling many circuits still gets through. |
Why Administrators only is worth setting. Home Assistant's default user policy grants every non-admin user control of every entity. Until you change this,
a dashboard-only household member can open any breaker in the house.
Administrators only does not reach an automation a non-admin triggered. It checks the user a command arrived with, and Home Assistant gives an automation
a fresh context with no user on it — the triggering context is recorded as the parent, not carried through — so an automation a non-admin set off publishes with
nothing for the admin test to hold. (A script is different: run by a person it keeps that person's user, and run from an automation it inherits the automation's
userless one.) The option that covers those callers is Allow control without a logged-in user; the two are separate settings because most households want
their automations to keep working. Set both if you want control limited to administrators acting in person.
What none of this defends against. These options constrain callers arriving through Home Assistant. They do not constrain anything that already holds the broker credential — a second Home Assistant instance, a script you wrote, or a malicious custom integration running inside this same Home Assistant process, which reads the credential straight out of memory. For that, see Recommended deployment below; network topology and a locked enclosure are the real boundary.
Nothing is deleted when you choose Nobody. The control entities stop being created and read as unavailable; their registry entries, names, areas and
customizations are kept, so turning the option back on restores exactly the entities you had. The option is also enforced where commands are published, not only
by the absence of entities, so a command reaching the panel by any other route is refused rather than sent. Dashboards and automations referencing them will
show them unavailable in the meantime — including the shipped SPAN Panel card, whose toggles call switch.turn_on and select.select_option directly and have
no card-side message for a refusal. A non-admin using that card under Administrators only gets a refusal with no explanation on the card.
Every control command fires a span_panel_control_command event and appears in the logbook, whether it succeeded, was refused, or never reached the panel. When
there is no user — an automation — the originating automation or script is named instead, so an unattended write is attributed to what rather than left blank.
Every command is also logged at INFO.
Commands report one of four outcomes, and the distinctions matter:
| Outcome | Meaning |
|---|---|
confirmed |
The panel reported the value you asked for. |
accepted |
The broker acknowledged the message and the panel did not report a change. |
unconfirmed |
Nothing came back within the deadline.Not an error — see the troubleshooting entry. |
failed |
The command was never handed to the broker and will not be delivered. |
refused:… |
This integration refused it, for the named reason. |
The span_panel.rotate_credentials action asks the panel for a new eBus MQTT broker password, stores it, and reloads the integration.
action: span_panel.rotate_credentials
data: {}Run it after a contractor visit, a suspected credential exposure, or any event that put someone else in front of the panel.
Name the panel when you have more than one. The config_entry_id field is optional with a single panel loaded and required beyond that: rotating stops the
previous broker password working for every other local client of whichever panel it ran against, so the action refuses an ambiguous call rather than picking
one.
Know the blast radius before you run it. The previous broker password stops working the moment the panel issues the new one, so every other local client using it — a second Home Assistant instance, a script, third-party tooling — must be re-provisioned from the panel before it will reconnect. This integration re-provisions itself automatically; nothing else does. The panel access token and the panel passphrase are not changed.
Only a Home Assistant administrator can run it, and it cannot be called from an automation or script: a call arriving without a logged-in user is refused outright. If the panel rejects the stored access token, reauthenticate the integration first — the existing credentials are left untouched on every failure path.
It will not run over an unverified connection. The rotation carries the access token out and the new broker password back, so an entry whose stored authority cannot be read is refused with nothing changed rather than falling back to plaintext; repair the panel's certificate authority and run it again. An entry that was never pinned has no anchor to fall back from and keeps the connection it has always used.
If the reload afterwards fails, do not rotate again. The new password is stored and the panel has already accepted it, so a second rotation would invalidate the one that works. The action reports the failure and says so; check the log and reload the integration.
The panel credential is a single all-or-nothing secret, and anyone standing at the panel can mint a fresh one with three presses of the door switch. Network topology and physical control of the panel are the real boundary; everything above is defense in depth behind it.
- Put the panel on a trusted VLAN, with default-deny between VLANs, and allow only the Home Assistant host to reach it. Open
tcp/443(REST),tcp/8883(MQTTS) andtcp/80(the plaintext certificate-authority fetch, and REST too until the entry pins), and denytcp/9001andtcp/9002unless you are actively using the SPAN Home on-premise UI. - Use the IP address or an FQDN, not the
.localname, because mDNS does not cross VLAN boundaries. The panel's IP is already in its certificate SAN, and for an FQDN the integration registers it so the panel adds it. - If Home Assistant cannot be on the panel's VLAN, put a reverse proxy there and restrict its inbound to the Home Assistant host; it holds no panel
credential and only relays. MQTTS is a TCP stream rather than HTTP, so
tcp/8883needs a stream proxy (HAProxy, nginxstream, or Caddy'slayer4plugin) while a plain HTTP proxy covers the REST port alone. - Secure the panel enclosure, because the three-press proximity bypass hands full credentials to anyone who can open the door — the equivalent of a printed root password. This outranks every software control on this page.
- Put dashboard-only household members in Home Assistant's read-only group. Home Assistant's default user policy grants every non-admin user control of every entity, which includes this integration's circuit switches and priority selects.
- Encrypt your Home Assistant backups. An unencrypted backup contains
.storage/core.config_entries, and the panel credentials in it are in plaintext. - Enable multi-factor authentication on Home Assistant and do not expose its API directly to the Internet. A compromised Home Assistant administrator account is a compromised panel.
The integration provides a span_panel/panel_topology WebSocket command that returns the full physical layout of a panel in a single call — circuits with their
breaker slot positions, entity IDs grouped by role, and sub-devices (BESS, EVSE) with their entities.
See WebSocket API Reference for the full schema, response format, and usage examples.
| Issue | Symptoms | Resolution |
|---|---|---|
| Energy Dashboard spikes after firmware updates | Huge energy-consumption spikes after panel firmware updates; charts showing untracked values that dwarf normal usage; negative energy values in statistics. Caused by the panel reporting decreased values on otherwiseTOTAL_INCREASING sensors. |
Prevention: enable Energy Dip Compensation in General Options (on by default for new installs). Fix existing spikes: in Developer Tools → Statistics, search for the affected sensor (e.g. sensor.span_panel_main_meter_consumed_energy) and use Adjust sum to correct the errant entry. The integration also notifies when a decrease in the main meter consumed sensor is detected. If you enabled compensation before 2.1.0 and saw spikes at restart: check energy_offset on your energy sensors in Developer Tools → States — an offset far larger than the sensor's own reading is compensation for a dip that never happened, and toggling Energy Dip Compensation off discards it. Offsets booked before 2.1.0 were never corroborated, so the integration cannot tell a good one from a bad one retrospectively; the spikes already in statistics still need Adjust sum either way. |
| High CPU usage | Elevated CPU on low-power hardware (e.g. Raspberry Pi). The integration rebuilds a full panel snapshot from MQTT messages at a configurable interval (default 1 s). | IncreaseSnapshot Update Interval in General Options. 10–15 s is recommended for resource-constrained systems. Setting it to 0 disables debouncing and rebuilds on every MQTT message — not recommended. |
| Replaced sub-device shows the old serial number | After replacing a SPAN sub-device (Drive / EVSE, BESS, PV inverter), the device entry in Home Assistant keeps showing the previous hardware's serial number. The integration keys entities off the panel-assigned node identity, which is intentionally stable across hardware swaps so long-term history (e.g. lifetime charging kWh for a Drive) is preserved. The device-registry serial number, however, does not auto-refresh. | InSettings → Devices & Services → Span Panel, open the affected sub-device and delete it, then reload the integration (or restart Home Assistant). The device re-registers with the new serial number. Entity IDs and their recorded history are preserved. |
| Door sensor unavailable | The SPAN API returns UNKNOWN if the cabinet door has not been operated recently. This is a defect in the SPAN API. | The integration reports the sensor as unavailable until a proper value arrives. Opening or closing the door publishes the correct state. The door is classified as a tamper sensor (Detected / Clear) to differentiate it from a normal entry door. |
| No switch on a circuit | A circuit has no switch entity exposed in Home Assistant. | The circuit is configured in the SPAN App as one of the "Always on Circuits". The API does not permit user control of those circuits, so no switch is created. |
| Reinstalling to change the entity ID style gives back the old entity IDs | The naming style is chosen at install and cannot be changed from the options, so reinstalling looks like the way to switch. It is not: every entity returns with the entity ID it had before. | Home Assistant remembers a removed entity for30 days, keyed on its unique ID, and restores that record's entity ID — along with its name, area, labels and icon — as soon as an entity with the same unique ID appears again. This integration's unique IDs do not change with the naming style, so the remembered ID wins over the one the new style asks for. Either clear the leftover registry entries between removing and reinstalling, or wait out the 30 days and let Home Assistant discard them. A tool such as ha-registry-clean can do the clearing; it is a separate project, not part of this integration. Clearing also discards the names, areas and labels you had assigned. |
| Setup fails after downgrading the integration | After installing an older release, the SPAN Panel config entry fails to set up and Home Assistant reports an unsupported configuration version. | The release that stopped storing the panel passphrase migrated the config entry to version 7. Home Assistant refuses to load a config entry whose version is newer than the installed integration understands, and there is no automatic downgrade. Reinstall the newer release, or restore a backup taken before the upgrade. Removing and re-adding the integration also works and preserves entity IDs, but needs the panel passphrase or physical access to the door again. |
| "SPAN Panel certificate authority changed" repair | The integration has stopped connecting and a repair reports two fingerprints: the one it pinned and the one the panel now advertises. Entities are unavailable. | The panel is presenting a different certificate authority than the one you accepted at setup. Two things look identical from here and only you can tell them apart: a firmware upgrade or a factory reset rotates the authority legitimately, and so does a device on your network standing in for your panel.If you know why it changed, open the repair, compare the new fingerprint, and accept it — that re-pins and reconnects. If nothing should have changed, do not accept. Check what else is on the panel's network segment first. The integration will not reconnect on its own and will not re-pin on its own, deliberately: retrying would mean waiting to succeed against whatever is answering. |
| A circuit re-commissioned in the SPAN App has the wrong controls | A circuit whose configuration changed in the SPAN App — made controllable, locked, or set to never back up — still has the controls it had before. A newly controllable circuit has no Breaker switch. A newly locked one still shows its switch, and operating it reports that the command was refused. | Reload the integration (Settings → Devices & Services → Span Panel → ⋮ → Reload). Which control entities exist is decided when the integration starts, from what the panel declares about each circuit at that moment; a change made afterwards is picked up at the next reload or restart. Reloading is safe — entity IDs, history, names and areas are preserved, because they are keyed on identifiers that do not change. Until you reload, a control the panel no longer accepts is refused rather than published: the command is not queued and the breaker does not move. |
A control reports unconfirmed |
The logbook or thespan_panel_control_command event says a command was unconfirmed. Nothing appears broken. |
This is not an error. It means the panel took the command and did not report a change within the deadline, and the most common reason by far is that there was no change to report — the relay was already open, the priority was already that value. It is also indistinguishable from a silent rejection by the panel, because SPAN's firmware sends no reason code; the integration reports what it observed rather than guessing. The two outcomes that do mean something went wrong are failed, which means the command was never sent, and refused:…, which means this integration refused it and names why. |
See Developer Documentation for setup instructions, prerequisites, and tooling.
This integration is published under the MIT license.
This repository is set up as part of an organization so a single committer is not the weak link. The repository is a fork in a long line of SPAN forks that may or may not be stable (from newer to older):
- SpanPanel/span (current GitHub organization, current repository, currently listed in HACS)
- SpanPanel/Span (was moved to SpanPanel/SpanCustom)
- cayossarian/span
- haext/span
- gdgib/span
- thetoothpick/span-hacs
- wez/span-hacs
- galak/span-hacs
Additional contributors:
- pavandave
- sargonas
- NickBorgersOnLowSecurityNode
If you have a problem with the integration, feel free to open an issue, but please know that issues regarding your network, SPAN configuration, or home electrical system are outside of our purview.
For those motivated, please consider offering suggestions for improvement in the discussions or opening a pull request. We're generally very happy to have a starting point when making a change.