A Home Assistant custom integration for monitoring and controlling FoxESS inverter battery modes.
FoxESS Control polls real-time inverter data (battery SoC, charge/discharge power, solar generation, house load, temperature) and provides actions for force charge, force discharge, smart charge/discharge with SoC targets, and feed-in management. It supports two backends: the FoxESS Cloud API and local entity mode via foxess_modbus. It includes comprehensive polled sensors and can fully replace the foxess-ha integration — see Migrating from foxess-ha.
| Idle | Discharging |
|---|---|
![]() |
![]() |
| Idle | Deferred | Charging | Discharging |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- A FoxESS inverter connected to FoxESS Cloud
- A FoxESS Cloud API key (generate one at foxesscloud.com under User Profile > API Management)
- Your inverter's device serial number
Note: If you use foxess_modbus, the cloud API key is optional. See Entity mode for details.
- Open HACS in your Home Assistant instance.
- Go to Integrations.
- Click the three-dot menu in the top right and select Custom repositories.
- Add the repository URL (e.g.
https://github.com/nicois/foxess-control) with category Integration. - Click Add.
- Search for "FoxESS Control" in the HACS integrations list and click Download.
- Restart Home Assistant.
- Go to Settings > Add-ons > Add-on Store.
- Click ⋮ > Repositories and add
https://github.com/nicois/foxess-control. - Install FoxESS Control from the store and start it.
- Restart Home Assistant.
- Copy the
custom_components/foxess_controldirectory into your Home Assistantconfig/custom_components/directory. - Restart Home Assistant.
- Go to Settings > Devices & Services > Add Integration.
- Search for FoxESS Control.
- Enter your API Key and Device Serial Number.
- The integration validates your credentials by querying the FoxESS Cloud API. If successful, the integration is added.
After entering your API key and serial, an optional second step allows you to provide your FoxESS Cloud web portal username and password (the same credentials you use to log in at foxesscloud.com). These enable the real-time WebSocket data feature (see below). You can skip this step and add credentials later via Configure > Reconfigure.
The web portal API uses an obfuscated signature algorithm (shipped as a WebAssembly module) for request authentication. See docs/wasm-signature.md for a full explanation of why this is necessary and how it works.
You can enter either your raw password or its MD5 hash. If you prefer not to type your password into the HA UI, generate the hash beforehand:
echo -n 'YourPassword' | md5sum | cut -d' ' -f1Important: Use
echo -n(no trailing newline). Plainechoadds a newline which produces a different hash.
When web credentials are configured, the integration can connect to an undocumented FoxESS Cloud WebSocket that streams inverter data every ~5 seconds (vs the standard 5-minute REST API polls). This is used during smart sessions to detect and react to load spikes faster, reducing the risk of accidental grid import.
The WebSocket real-time data mode option controls when the WebSocket is used:
| Mode | Behaviour |
|---|---|
| Auto (default) | WS connects only during active forced discharge — the highest-risk window where a load spike between polls can cause grid import. |
| All smart sessions | WS also connects during smart charge and deferred discharge phases, so the overview card updates every ~5 seconds during any smart operation. |
| Always connected | WS connects at startup and stays connected regardless of session state. Useful for dashboards showing real-time power flow. Includes a watchdog that recovers the connection after transient failures. |
The WebSocket is best-effort: if the connection fails, the integration falls back to standard REST polling with no loss of functionality. Entity mode users (foxess_modbus) already have fast local data and are unaffected.
After setup, click Configure on the integration entry to adjust:
| Option | Default | Range | Description |
|---|---|---|---|
| Minimum SoC on Grid | 15% | 0-100% | The minimum battery state of charge to maintain when on grid. Applied to all schedule operations. |
| API Polling Interval | 300 s (cloud) / 30 s (entity mode) | 60-600 s | How often to poll for real-time data. In cloud mode, defaults to 5 minutes to stay within the FoxESS API quota. In entity mode, defaults to 30 seconds since reads are local. |
| Battery Capacity | 0.0 kWh | 0-100 kWh | Total usable battery capacity in kWh. Required for smart_charge power calculations. |
| Min Power Change | 500 W | 0-5000 W | Minimum watt change before updating the charge schedule during smart_charge. Lower values improve SoC tracking, higher values reduce API calls. |
| Minimum API fdSoc | 11% | 0-11% | The minimum fdSoc value sent to the FoxESS API. The API normally rejects values below 11 (errno 40257). Only lower this if you know your firmware supports it. |
| Smart Headroom | 10% | 0-25% | Spare capacity reserved during smart_charge and smart_discharge for transient load variation. Applied as both a time buffer (plan to finish in 90% of the window) and a power multiplier (request 110% of the calculated rate). For smart charge, lower values charge more slowly and defer longer; higher values start earlier and charge faster. For smart discharge, the headroom determines how long the deferred self-use phase lasts — lower values defer longer (start forced discharge later), higher values start earlier. When a feed-in energy limit is set, the headroom is doubled (up to 40%) to account for household consumption reducing the effective export rate — except on sites where the configured Grid Export Limit (below) is meaningfully smaller than inverter max power and projected peak load is within the clamp's slack, in which case the hardware clamp already shields export rate from load volatility and the single headroom is used. Set to 0 for no headroom (not recommended — transient loads may prevent reaching the target). |
| Grid Export Limit | 5000 W | 0-20000 W | Net export cap set on the inverter (DNO / firmware limit). When non-zero, smart discharge deferral treats this as the effective maximum export rate and the active-discharge phase requests inverter max power, letting the hardware actuator do the export capping. Set to 0 for legacy power-pacing behaviour. |
| WebSocket real-time data mode | Auto | Auto / All smart sessions / Always connected | When web credentials are configured, controls when the real-time WebSocket is used. See Real-time WebSocket data. |
Warning: The inverter's behaviour when it reaches this SoC level during force discharge or feed-in is unintuitive. Consider using an automation to cancel the override before the battery reaches this level. See Known limitations.
If you use foxess_modbus for local Modbus control, foxess_control can optionally read inverter state from and write mode changes to foxess_modbus's HA entities instead of the FoxESS Cloud API. This gives you fast local control combined with foxess_control's smart charge/discharge algorithms — no cloud API connection required.
Setup: When foxess_modbus is installed, the options flow automatically shows a second step with entity mappings. Entities are auto-detected from the foxess_modbus entity registry and pre-populated — in most cases no manual configuration is needed. You can override any mapping if the auto-detection picks the wrong entity.
If foxess_modbus is not installed, the entity mapping step is hidden entirely.
| Option | Domain | Required | Description |
|---|---|---|---|
| Work Mode Entity | select |
Yes | foxess_modbus work mode select entity. Setting this enables entity mode. |
| Charge Power Entity | number |
For smart charge | foxess_modbus charge power number entity. |
| Discharge Power Entity | number |
For smart discharge | foxess_modbus discharge power number entity. |
| Min SoC Entity | number |
No | foxess_modbus min SoC number entity. |
| SoC Entity | sensor |
No | Battery SoC sensor (overrides cloud polling). |
| Loads Power Entity | sensor |
No | House load sensor (improves consumption-aware charging). |
| PV Power Entity | sensor |
No | Solar generation sensor (improves charge deferral). |
| Feed-in Energy Entity | sensor |
No | Cumulative feed-in energy sensor (for discharge energy limits). |
| Battery Charge Power Entity | sensor |
No | Battery charge power sensor (populates overview card charge rate). |
| Battery Discharge Power Entity | sensor |
No | Battery discharge power sensor (populates overview card discharge rate). |
| Grid Consumption Power Entity | sensor |
No | Grid import power sensor (populates overview card grid section). |
| Grid Feed-in Power Entity | sensor |
No | Grid export power sensor (populates overview card grid section). |
| Max Grid Export Limit Entity | number |
No | foxess_modbus "Max Grid Export Limit" number entity. When mapped, smart discharge controls feed-in by writing this entity each tick (clamped to [peak × 1.5, Grid Export Limit]) instead of modulating the cloud schedule's fdPwr. The cloud schedule is pinned at inverter max. Every session exit path reverts the entity to the configured Grid Export Limit above. Optional — when unmapped, discharge pacing stays on the cloud-schedule path. |
| Inverter Rated Power | — | No | Inverter's maximum power in watts (default 12000). Used as the default power limit when no explicit power is specified in actions. In cloud mode this is queried from the API automatically. |
When entity mode is active:
- No cloud connection required. The FoxESS Cloud API key is not needed. If provided, it is unused while entity mode is active.
- Reads come from HA entity states (polled every 30 seconds by default) instead of the FoxESS Cloud API.
- Automatic unit conversion: source entity units are read from
unit_of_measurementand converted to match the cloud API format (e.g. W→kW, Wh→kWh) using HA's built-in unit converters. No manual unit configuration needed. - Writes use
select.select_optionandnumber.set_valueservice calls to foxess_modbus entities instead of the cloud API scheduler. - All actions (
force_charge,smart_charge,smart_discharge,feedin,clear_overrides) work identically — only the underlying transport changes. - Schedule merging and multi-window management are not used; foxess_control sets the mode directly.
- Smart session recovery after HA restart works without checking cloud schedule state.
The integration registers six actions (services) under the foxess_control domain. These are intended to be called from automations.
Most actions accept a replace_conflicts parameter. In cloud mode, the inverter's schedule can hold multiple time-windowed override groups (e.g. a ForceCharge window and a ForceDischarge window). If a new override's time window overlaps with an existing override of a different mode, the action aborts by default to prevent conflicts. Setting replace_conflicts: true silently removes the overlapping overrides instead. In entity mode (no multi-window schedule), this parameter has no effect.
Clears overrides and returns the inverter to self-use mode. If mode is specified, only overrides of that mode are removed; other overrides are retained.
If a smart_charge or smart_discharge session is running, clear_overrides also cancels its background listeners — the session stops cleanly without fighting the cleared schedule. Clearing ForceCharge cancels smart charge; clearing ForceDischarge cancels smart discharge; clearing all overrides (no mode) cancels both.
| Parameter | Required | Default | Description |
|---|---|---|---|
mode |
No | All | Only clear overrides of this mode (ForceCharge, ForceDischarge, etc.). |
# Clear all overrides (also stops any active smart charge/discharge)
action: foxess_control.clear_overrides# Clear only force-charge overrides (also stops smart charge), keeping others
action: foxess_control.clear_overrides
data:
mode: ForceChargePrioritises feeding excess solar to the grid for a specified duration, similar to self-use but with grid export priority. The inverter automatically reverts to self-use when the window ends. Does not cancel running smart charge/discharge sessions.
| Parameter | Required | Default | Description |
|---|---|---|---|
duration |
Yes | How long to feed in. Maximum 4 hours. Must not extend past midnight. | |
power |
No | Inverter max | Feed-in power limit in watts (min 100). |
start_time |
No | Now | Time of day to start the override (e.g. "14:00:00"). |
replace_conflicts |
No | false | Remove conflicting overrides instead of aborting. |
action: foxess_control.feedin
data:
duration: "02:00:00"
power: 5000Charges the battery at maximum inverter power for a specified duration. Internally creates a smart session with full_power mode, providing circuit breaker protection, restart recovery, and UI state. The inverter automatically reverts to self-use when the window ends or the battery reaches 100% SoC.
| Parameter | Required | Default | Description |
|---|---|---|---|
duration |
Yes | How long to force charge. Maximum 4 hours. Must not extend past midnight. | |
start_time |
No | Now | Time of day to start the override (e.g. "14:30:00"). |
replace_conflicts |
No | false | Remove conflicting overrides instead of aborting. |
action: foxess_control.force_charge
data:
duration: "01:30:00"Discharges the battery at maximum inverter power for a specified duration. Internally creates a smart session with full_power mode, providing circuit breaker protection, restart recovery, and UI state. The inverter automatically reverts to self-use when the window ends or the battery reaches min SoC.
| Parameter | Required | Default | Description |
|---|---|---|---|
duration |
Yes | How long to force discharge. Maximum 4 hours. Must not extend past midnight. | |
start_time |
No | Now | Time of day to start the override (e.g. "17:00:00"). |
replace_conflicts |
No | false | Remove conflicting overrides instead of aborting. |
action: foxess_control.force_discharge
data:
duration: "02:00:00"Charges the battery within a time window, deferring grid charging as long as possible to maximise the opportunity for solar to contribute. Only starts grid charging when necessary to reach the target SoC by the end of the window.
How it works:
- Calculates the latest possible start time by estimating the effective charge rate — inverter max power minus current household consumption (read from the polled
loadsPowerandpvPowerdata), with a minimum headroom (configurable via Smart Headroom, default 10%) reserved for transient loads. This consumption-aware calculation means the deferral adapts to real-time site conditions rather than using a fixed buffer. - Deferred phase: Until the calculated start time, no
ForceChargeschedule is set. The inverter stays in its current mode (typically self-use), allowing solar generation to charge the battery naturally. - Charging phase: When the deferred start time arrives, sets a
ForceChargeschedule withfdSocset high (100%) so the inverter never stops charging on its own — HA is the sole authority for stopping. Charge power targets finishing within the configured headroom buffer and accounts for current household consumption, so the inverter typically runs below full capacity. - Every 5 minutes, re-reads the current SoC, household consumption, and solar generation, then recalculates. When available, the inverter's
ResidualEnergysensor (direct kWh measurement) is used for higher precision than integer SoC% × capacity. During the deferred phase, if solar has raised the SoC, the start time is pushed later. During the charging phase, power is adjusted up or down based on both the remaining energy deficit and current net consumption. If the power change is below the configured Min Power Change threshold, the update is skipped to avoid unnecessary API calls. If the actual energy stored is significantly behind the ideal headroom-adjusted trajectory (a linear ramp from the starting energy to the target, completing within the headroom-shortened window), the charge rate temporarily jumps to full power until the trajectory is regained. The deficit must exceed a tolerance derived from the Min Power Change setting to avoid premature bursting from minor measurement fluctuations. If the active-charge phase finds that solar has pushed the battery ahead of the trajectory, the session re-enters the deferred phase (stopping forced charging) and re-evaluates on each subsequent tick; it only resumes forced charging when the deadline requires it. This avoids reaching the target early and wasting cheap-rate self-use time. If the remaining window cannot reach the target even at full power (after headroom, taper, and consumption), thecharge_target_reachableattribute becomesfalseand an HA Repair issue is raised. It is dismissed automatically when the target becomes reachable again (e.g. consumption drops) or when the session ends. - When the SoC reaches the target (whether from solar during the deferred phase or grid charging), the
ForceChargegroup is removed from the schedule immediately to stop unnecessary charging. The session continues monitoring for one more reading: if the SoC is confirmed at or above target, the session ends; if it drops back below, the charge override is re-applied. This prevents a single SoC spike from prematurely ending the session while avoiding overcharging during the confirmation period. Other modes' schedule groups (e.g. a standingForceDischargewindow) are preserved. - When the time window ends, the
ForceChargegroup is removed from the schedule and listeners are cancelled. This prevents the schedule from replaying the next day.
If the battery capacity is too large or the SoC too low to reach the target within the window (accounting for current consumption), charging starts immediately (no deferral).
When the BMS battery temperature is below 16°C, the maximum charge power is automatically capped at 80A × live battery voltage (~4 kW at 50 V) to match the BMS's physical current limit. This prevents over-requesting, which causes the inverter to oscillate. The effective limit is exposed via the charge_effective_max_power_w sensor attribute.
Only one smart charge session can be active at a time. Starting a new smart_charge cancels any previous session, and also cancels any active smart_discharge session to prevent schedule conflicts. A force_charge action also cancels any running smart charge, replacing it with a full-power smart session.
Stopping a running smart charge: Call foxess_control.clear_overrides (with no mode, or with mode: ForceCharge). This removes the schedule and cancels the background listeners.
HA restart: Smart charge sessions are persisted to .storage. If HA restarts mid-session, the session is automatically resumed if still within the time window, or cleaned up if expired. You do not need to re-trigger the automation.
Requires Battery Capacity to be configured in the integration options.
| Parameter | Required | Default | Description |
|---|---|---|---|
start_time |
Yes | Time of day to start charging (e.g. "02:00:00"). |
|
end_time |
Yes | Time of day to stop charging (e.g. "06:00:00"). Must be after start time, within 4 hours. |
|
target_soc |
Yes | Charge the battery to this SoC level (5-100%). Charging stops and reverts to self-use when reached. | |
power |
No | Inverter max | Maximum charge power in watts (min 100). The actual power may be lower to pace charging to the end of the window. |
replace_conflicts |
No | false | Remove conflicting overrides instead of aborting. |
action: foxess_control.smart_charge
data:
start_time: "02:00:00"
end_time: "06:00:00"
target_soc: 80
power: 6000Discharges the battery within a time window, deferring forced discharge as long as possible to keep the battery serving household load naturally. Only switches to forced discharge when necessary to meet the SoC or feed-in target by the end of the window.
How it works:
- Deferred phase (requires
battery_capacity_kwhin options): Calculates the latest possible start time for forced discharge, considering two independent deadlines — (a) the time needed at full power to drain from the current SoC tomin_soc, and (b) iffeedin_energy_limit_kwhis set, the time needed to export the required energy. The inverter stays in self-use during this phase, so the battery naturally serves household load without grid export. This prevents accidental grid import that can occur when paced discharge power is set below the house load. The feed-in deadline applies doubled headroom (up to 40%) because household consumption normally reduces net export — except when a Grid Export Limit is configured meaningfully below inverter max power and projected peak load stays within the clamp's slack, because then the hardware clamp already shields export rate from load volatility; in that case the single headroom is used. Withoutbattery_capacity_kwhconfigured, forced discharge starts immediately. - Discharging phase: When the deferred deadline arrives, sets a
ForceDischargeschedule withfdSocset low (11%) so the inverter never stops discharging on its own — HA is the sole authority for stopping. The initial discharge power is calculated based on the remaining energy to discharge and time remaining, then re-evaluated every 5 minutes. Power is adjusted if the change exceeds the Minimum Power Change threshold. Whenfeedin_energy_limit_kwhis set, pacing factors in the remaining export budget so the feed-in limit is not exhausted early. When apowervalue is provided, it acts as a ceiling — pacing still operates but never exceeds the specified limit. When available, the inverter'sResidualEnergysensor (direct kWh measurement) is used for higher precision than integer SoC% × capacity. When a Max Grid Export Limit Entity is mapped (entity mode), the discharge phase switches to the hardware-actuator path: the cloudForceDischargeschedule is pinned at inverter max, and the paced feed-in target is written to the export-limit entity each tick (clamped to[peak_consumption × 1.5, Grid Export Limit]). Sub-threshold deltas are suppressed to avoid chatty modbus writes. Every session exit path (timer expire, circuit-breaker abort, SoC threshold, feed-in limit, manual cancel) reverts the entity to the configured maximum. - Monitors the battery SoC periodically. When the SoC drops to the
min_socthreshold for two consecutive readings, theForceDischargegroup is removed from the schedule, all listeners are cancelled, and the session ends. Requiring two readings prevents a single SoC dip from prematurely ending the session. Other modes' schedule groups (e.g. a standingForceChargewindow) are preserved. - If
feedin_energy_limit_kwhis set, the cumulative grid feed-in counter is snapshot at the start and compared each interval. When the exported energy reaches the limit, the session ends. This uses the API's lifetimefeedincounter rather than integrating instantaneous power, so it is accurate across HA restarts. - When the time window ends, the
ForceDischargegroup is removed from the schedule and listeners are cancelled. This prevents the schedule from replaying the next day.
The session stops at whichever condition is reached first: time window end, SoC threshold, or feed-in energy limit.
Only one smart discharge session can be active at a time. Starting a new smart_discharge cancels any previous session, and also cancels any active smart_charge session to prevent schedule conflicts. A force_discharge action also cancels any running smart discharge, replacing it with a full-power smart session.
Stopping a running smart discharge: Call foxess_control.clear_overrides (with no mode, or with mode: ForceDischarge). This removes the schedule and cancels the background listeners.
HA restart: Smart discharge sessions are persisted to .storage. If HA restarts mid-session, the session is automatically resumed if still within the time window, or cleaned up if expired.
Battery SoC is read from the integration's polled data.
| Parameter | Required | Default | Description |
|---|---|---|---|
start_time |
Yes | Time of day to start discharging (e.g. "17:00:00"). |
|
end_time |
Yes | Time of day to stop discharging (e.g. "20:00:00"). Must be after start time, within 4 hours. |
|
power |
No | Inverter max | Discharge power limit in watts (min 100). |
min_soc |
Yes | Stop discharging and revert to self-use when the battery reaches this SoC level (0-100%). | |
feedin_energy_limit_kwh |
No | Stop discharging after this much energy (kWh) has been fed into the grid. This is the excess energy exported beyond household self-consumption. Uses the cumulative feedin counter from the API for accuracy across restarts. |
|
replace_conflicts |
No | false | Remove conflicting overrides instead of aborting. |
action: foxess_control.smart_discharge
data:
start_time: "17:00:00"
end_time: "20:00:00"
min_soc: 30
power: 5000# Discharge up to 5 kWh of grid feed-in, then stop
action: foxess_control.smart_discharge
data:
start_time: "17:00:00"
end_time: "20:00:00"
min_soc: 10
feedin_energy_limit_kwh: 5.0The integration polls inverter data at a configurable interval and creates the following sensor entities. In cloud mode, data comes from the FoxESS Cloud API (default: 5 minutes). In entity mode, data comes from foxess_modbus HA entities (default: 30 seconds) — only sensors with a mapped entity are available.
| Entity | Description | Unit |
|---|---|---|
sensor.foxess_battery_soc |
Battery state of charge | % |
sensor.foxess_charge_rate |
Battery charge power | kW |
sensor.foxess_discharge_rate |
Battery discharge power | kW |
sensor.foxess_house_load |
Current house load | kW |
sensor.foxess_solar_power |
Current solar generation | kW |
sensor.foxess_residual_energy |
Residual energy in battery | kWh |
sensor.foxess_battery_temperature |
Battery temperature (inverter sensor) | °C |
sensor.foxess_bms_battery_temperature |
BMS cell temperature (web portal) | °C |
sensor.foxess_grid_consumption |
Power drawn from grid | kW |
sensor.foxess_grid_feed_in |
Power fed to grid | kW |
sensor.foxess_generation |
Total generation power | kW |
sensor.foxess_battery_voltage |
Battery voltage | V |
sensor.foxess_battery_current |
Battery current | A |
sensor.foxess_pv1_power |
PV string 1 power | kW |
sensor.foxess_pv2_power |
PV string 2 power | kW |
sensor.foxess_ambient_temperature |
Ambient temperature | °C |
sensor.foxess_inverter_temperature |
Inverter temperature | °C |
sensor.foxess_grid_feed_in_energy |
Cumulative grid feed-in energy (lifetime) | kWh |
sensor.foxess_grid_consumption_energy |
Cumulative grid consumption energy (lifetime) | kWh |
sensor.foxess_solar_generation_energy |
Cumulative solar generation energy (lifetime) | kWh |
sensor.foxess_battery_charge_energy |
Cumulative battery charge energy (lifetime) | kWh |
sensor.foxess_battery_discharge_energy |
Cumulative battery discharge energy (lifetime) | kWh |
sensor.foxess_house_load_energy |
Cumulative house load energy (lifetime) | kWh |
sensor.foxess_battery_throughput |
Cumulative battery throughput (lifetime) | kWh |
sensor.foxess_grid_meter_power |
Grid meter power (signed: negative = exporting) | kW |
sensor.foxess_grid_voltage |
Grid voltage | V |
sensor.foxess_grid_current |
Grid current | A |
sensor.foxess_grid_frequency |
Grid frequency | Hz |
sensor.foxess_eps_power |
EPS / backup output power | kW |
sensor.foxess_work_mode |
Current inverter work mode (SelfUse, ForceCharge, etc.) | — |
sensor.foxess_data_freshness |
Current data source (ws, api, or modbus) with last_update and age_seconds attributes |
— |
The cumulative energy sensors use SensorStateClass.TOTAL_INCREASING and are compatible with Home Assistant's Energy Dashboard.
These sensors update automatically and are always available (not dependent on an active smart operation). They are backed by Home Assistant's DataUpdateCoordinator, so all entities update atomically from a single poll. In cloud mode, the work mode sensor makes an additional API call per poll cycle to read the active schedule. In entity mode, work mode is read from the mapped select entity.
Note: Diagnostic-only sensors (temperatures, voltages, currents, grid frequency, EPS, throughput) are categorised as
DIAGNOSTICso they don't clutter default dashboards. Rarely-used sensors (PV1/PV2, battery voltage/current, ambient/inverter temp, grid current/frequency, EPS, throughput) are disabled by default — enable them in Settings > Devices > Entities as needed.
| Entity | Description |
|---|---|
sensor.foxess_debug_log |
Rolling buffer of the last 75 debug log messages. Enabled via input_boolean.foxess_debug_log. |
sensor.foxess_init_debug_log |
Non-wrapping buffer preserving the first 75 log messages after startup. |
sensor.foxess_info_log |
Rolling buffer of the last 75 INFO+ messages, retaining operational context longer than the debug log. |
The following sensors track active smart charge/discharge sessions. They are unavailable when no smart operation is active.
| Entity | Description | Example value |
|---|---|---|
sensor.foxess_status |
Compact status for Android Auto. Dynamic icon reflects current state. | Chg 6kW→80%, Wait→80%, Dchg@18:00, Dchg 5kW→20:00, Dchg 5kW 5.0kWh, Idle |
sensor.foxess_smart_operations |
Dashboard overview with rich attributes for templating (see below). | Charging to 80%, Deferred charge to 80%, Discharge scheduled at 18:00, Discharging until 20:00, Discharging 5.0 kWh feed-in, Idle |
sensor.foxess_smart_operations attributes:
Always present:
| Attribute | Type | Description |
|---|---|---|
charge_active |
bool | Whether a smart charge session is running. |
discharge_active |
bool | Whether a smart discharge session is running. |
When charge_active is true:
| Attribute | Type | Description |
|---|---|---|
charge_phase |
string | "scheduled" (before window), "deferred" (within window, waiting), or "charging". |
charge_power_w |
int | Current charge power in watts. |
charge_max_power_w |
int | Configured maximum charge power. |
charge_effective_max_power_w |
int | Effective max power after cold-temperature curtailment. |
charge_target_soc |
int | Target SoC percentage. |
charge_current_soc |
float | Current battery SoC. |
charge_window |
string | Time window (e.g. "02:00 – 06:00"). |
charge_remaining |
string | Time remaining or deferred status (e.g. "1h 30m", "starts in 2h 15m", "defers 18m"). |
charge_end_time |
string | End time in ISO format. |
charge_target_reachable |
bool | false when even max power can't reach the target SoC in remaining time. An HA Repair issue is raised when this becomes false mid-session. |
charge_time_slack_s |
int | null | During the deferred phase, seconds remaining until forced charging must start. Recomputed every tick — solar surplus grows the slack and load spikes shrink it. null outside the deferred phase. |
When discharge_active is true:
| Attribute | Type | Description |
|---|---|---|
discharge_phase |
string | "scheduled" (before window), "deferred" (within window, waiting), "discharging", or "suspended". |
discharge_power_w |
int | Current discharge power in watts. |
discharge_target_power_w |
int | Target discharge power before feed-in pacing adjustments. |
discharge_min_soc |
int | Minimum SoC threshold. |
discharge_current_soc |
float | Current battery SoC. |
discharge_window |
string | Time window (e.g. "17:00 – 20:00"). |
discharge_remaining |
string | Time remaining or status (e.g. "45m", "1.0 kWh left", "defers 12m"). |
discharge_end_time |
string | End time in ISO format. |
discharge_time_slack_s |
int | null | During the deferred phase, seconds remaining until forced discharge must start (deferred_start − now). Recomputed every tick, so solar surplus grows the slack and load spikes shrink it. null outside the deferred phase. |
discharge_export_limit_w |
int | null | When an export-limit actuator is mapped (entity mode), the current paced export-limit value written to the hardware. null when the actuator is not configured. |
When any session is active, error state attributes are also available:
| Attribute | Type | Description |
|---|---|---|
has_error |
bool | true when the session has encountered errors. |
last_error |
string | Most recent error message. |
last_error_at |
string | Timestamp of the most recent error. |
error_count |
int | Number of consecutive errors (circuit breaker opens at 3). |
| Entity | Description | Example value |
|---|---|---|
sensor.foxess_charge_power |
Current charge power in watts. | 6000 |
sensor.foxess_charge_window |
Charge time window. | 02:00 – 06:00 |
sensor.foxess_charge_remaining |
Time remaining in the charge window, or time until deferred charging begins. | 1h 30m, starts in 2h 15m, starting |
| Entity | Description | Example value |
|---|---|---|
sensor.foxess_discharge_power |
Current discharge power in watts. | 5000 |
sensor.foxess_discharge_window |
Discharge time window. | 17:00 – 20:00 |
sensor.foxess_discharge_remaining |
Time remaining in the discharge window, energy remaining if energy limit is closer, or time until discharge begins. | 45m, 1h 20m, 1.0 kWh left, starts in 3h 45m, defers 12m |
sensor.foxess_discharge_export_limit |
Current export-limit value (W) written to the hardware actuator. Present only when a Max Grid Export Limit Entity is mapped (entity mode). Attributes: configured_max (the Grid Export Limit option value, or entity hardware max), modulated (current paced value), entity (source entity id). |
5000 |
| Entity | Description |
|---|---|
sensor.foxess_battery_forecast |
Projected battery SoC (%) over time. The forecast attribute contains a list of {"time": <epoch_ms>, "soc": <float>} data points (5-minute intervals) for charting. |
The forecast projects SoC based on the active smart operation:
- Charging: SoC rises from current level toward target_soc at the current charge power
- Deferred charge: SoC stays flat until the estimated start time, then rises
- Discharging: SoC drops from current level toward min_soc at the current discharge power
Requires Battery Capacity to be configured in the integration options.
Use the apexcharts-card custom card to display the forecast on a dashboard:
type: custom:apexcharts-card
header:
title: Battery Forecast
show: true
graph_span: 6h
yaxis:
- min: 0
max: 100
decimals: 0
apex_config:
title:
text: "SoC %"
series:
- entity: sensor.foxess_battery_forecast
data_generator: |
return entity.attributes.forecast.map(p => [p.time, p.soc]);
name: Forecast
type: area
color: "#4CAF50"
opacity: 0.3
stroke_width: 2To overlay the forecast on top of actual SoC history:
type: custom:apexcharts-card
header:
title: Battery SoC
show: true
graph_span: 12h
span:
start: day
yaxis:
- min: 0
max: 100
decimals: 0
series:
- entity: sensor.foxess_battery_soc
name: Actual
type: area
color: "#2196F3"
opacity: 0.2
stroke_width: 2
- entity: sensor.foxess_battery_forecast
data_generator: |
return entity.attributes.forecast.map(p => [p.time, p.soc]);
name: Forecast
type: line
color: "#FF9800"
stroke_width: 2
stroke_dash: 4The integration includes a custom Lovelace card that displays the current smart operation status with a battery gauge, progress indicators, and action buttons for starting/cancelling sessions. Both a visual editor and YAML configuration are supported.
The card is auto-registered as a Lovelace resource when the integration loads (storage mode dashboards). No manual resource setup is needed.
type: custom:foxess-control-cardThat's it — no configuration required. The card auto-discovers the sensor.foxess_smart_operations and sensor.foxess_battery_soc entities.
What the card shows:
- Header: Battery SoC gauge with colour-coded fill (green/orange/red by level), data source badge with staleness indicator
- Action buttons: Charge and Discharge buttons open inline parameter forms (start time, end time, SoC target). Cancel button (with double-tap confirmation) appears during active sessions.
- Smart Charge (green section): Time window, power, target SoC with progress bar, remaining time badge. Shows "Charge Scheduled" with a dim indicator when deferred, "Smart Charge" with a pulsing dot when actively charging.
- Smart Discharge (orange section): Time window, power, min SoC, feed-in energy limit. Shows "Discharge Scheduled" before the window opens, "Discharge Deferred" during the deferred self-use phase, and "Smart Discharge" with a pulsing dot when actively discharging. Power is hidden during the deferred phase since no forced discharge is active.
- Idle: Clean message when no smart operation is active.
To hide the card when no smart operation is active, wrap it in a conditional card:
type: conditional
conditions:
- condition: state
entity: sensor.foxess_smart_operations
state_not: Idle
card:
type: custom:foxess-control-cardConfiguration options:
| Option | Default | Description |
|---|---|---|
operations_entity |
Auto-discovered | Smart operations sensor entity ID. |
soc_entity |
Auto-discovered | Battery SoC sensor entity ID. |
freshness_entity |
Auto-discovered | Data freshness sensor for staleness badge. |
show_cancel |
true |
Show the cancel button during active sessions. Set to false to hide it. |
type: custom:foxess-control-card
show_cancel: falseA second built-in card shows live energy flows between solar, battery, grid and house in a responsive grid layout. Both a visual editor and YAML configuration are supported.
type: custom:foxess-overview-cardNo configuration required — all entities are auto-discovered. The card shows:
- Solar: Total solar power with PV1/PV2 breakdown (clickable for history)
- House: Household consumption (clickable for history)
- Grid: Import/export power with direction indicator, voltage and frequency (clickable for history)
- Battery: SoC gauge, charge/discharge rate with direction indicator, BMS cell temperature, inverter temperature, residual energy (clickable for history)
- Work mode: Current inverter work mode badge in the header
- Data source badge: Shows current data source (WS/API/Modbus) with staleness indicator
Clicking any energy flow node opens the HA entity history dialog. Sub-details (cell temperature, PV strings, grid voltage/frequency, residual energy) are individually clickable.
Show, hide, reorder, relabel, and re-icon the four energy flow boxes via the visual editor or YAML boxes config. The layout adapts responsively for 1, 3, or 4 visible boxes.
type: custom:foxess-overview-card
boxes:
- battery
- type: solar
label: PV
icon: "☀️"Each entry in boxes can be a string shorthand ("solar") or an object with type, label, and icon properties. Boxes not listed are hidden. Omit boxes entirely to show all four with defaults.
All entities are auto-discovered. To override:
type: custom:foxess-overview-card
solar_entity: sensor.foxess_solar_power
house_entity: sensor.foxess_house_load
grid_import_entity: sensor.foxess_grid_consumption
grid_export_entity: sensor.foxess_grid_feed_in
battery_charge_entity: sensor.foxess_charge_rate
battery_discharge_entity: sensor.foxess_discharge_rate
soc_entity: sensor.foxess_battery_soc
work_mode_entity: sensor.foxess_work_mode
pv1_entity: sensor.foxess_pv1_power
pv2_entity: sensor.foxess_pv2_power
grid_voltage_entity: sensor.foxess_grid_voltage
grid_frequency_entity: sensor.foxess_grid_frequency
bat_temp_entity: sensor.foxess_battery_temperature
bms_temp_entity: sensor.foxess_bms_battery_temperature
residual_entity: sensor.foxess_residual_energy
data_freshness_entity: sensor.foxess_data_freshnessAn SVG-based card showing the projected SoC trajectory with target/min SoC markers.
type: custom:foxess-forecast-cardNo configuration required — entities are auto-discovered. To override:
type: custom:foxess-forecast-card
forecast_entity: sensor.foxess_battery_forecast
operations_entity: sensor.foxess_smart_operationsA 24-hour horizontal timeline with coloured session bars and SoC trace overlay.
type: custom:foxess-history-card| Option | Default | Description |
|---|---|---|
hours |
24 |
Timeline span: 12, 24, or 48. |
operations_entity |
Auto-discovered | Smart operations sensor entity ID. |
soc_entity |
Auto-discovered | Battery SoC sensor entity ID. |
type: custom:foxess-history-card
hours: 48YAML mode dashboards: If you use YAML-mode Lovelace (not the default storage mode), add the resources manually to your
configuration.yaml:lovelace: resources: - url: /foxess_control/foxess-control-card.js type: module - url: /foxess_control/foxess-overview-card.js type: module - url: /foxess_control/foxess-forecast-card.js type: module - url: /foxess_control/foxess-history-card.js type: module
The integration UI — entity names, service descriptions, config options, and both Lovelace cards — is fully translated into the following languages:
| Language | Code |
|---|---|
| English | en |
| German | de |
| French | fr |
| Dutch | nl |
| Spanish | es |
| Italian | it |
| Polish | pl |
| Portuguese | pt |
| Simplified Chinese | zh-Hans |
| Japanese | ja |
Home Assistant automatically selects the language based on the user's profile language setting. Lovelace cards use hass.language for card-level UI elements (labels, durations, status text).
The integration creates two binary sensors that track whether a smart charge or smart discharge session is currently active:
| Entity | State | Attributes when on |
|---|---|---|
binary_sensor.foxess_smart_charge_active |
on while a smart charge session is running |
target_soc, current_power_w, max_power_w, end_time |
binary_sensor.foxess_smart_discharge_active |
on while a smart discharge session is running |
min_soc, last_power_w, end_time |
These sensors are useful for:
- Dashboard indicators showing active sessions
- Automation conditions (e.g. suppress other actions while a smart charge is in progress)
- Template sensors that expose session attributes like remaining time or current power
New to FoxESS Control? See EXAMPLES.md for a quick-start guide with copy-pasteable automations and dashboard setup.
Smart charge during off-peak hours to 80%, then smart discharge during the evening peak down to 30%:
automation:
- alias: "Off-peak smart charge"
trigger:
- platform: time
at: "02:00:00"
action:
- action: foxess_control.smart_charge
data:
start_time: "02:00:00"
end_time: "06:00:00"
target_soc: 80
- alias: "Evening peak smart discharge"
trigger:
- platform: time
at: "17:00:00"
action:
- action: foxess_control.smart_discharge
data:
start_time: "17:00:00"
end_time: "20:00:00"
min_soc: 30- Force charge/discharge actions write a time-windowed override to the inverter's scheduler via the FoxESS Cloud API. Outside scheduled windows, the inverter defaults to self-use mode.
- Each force action only replaces existing overrides of the same mode (e.g. force charge replaces previous force charge windows, but leaves force discharge windows intact). Overrides of a different mode are always preserved, even if their time window has already passed today — this allows standing daily schedules (e.g. a free-electricity charge window) to coexist with evening discharge overrides.
- If the new window would overlap with an existing override of a different mode, the action aborts with an error to prevent conflicts.
- When a smart action ends (SoC target reached, time window expired), it removes only its own mode's groups from the schedule. Other modes' groups are preserved. For example, a smart discharge ending does not remove a standing
ForceChargewindow. If no groups remain after removal, the schedule reverts to self-use. - Smart sessions are persisted to Home Assistant's
.storagedirectory. If HA restarts during an active session, it is automatically resumed if still within its time window, or cleaned up (schedule groups removed) if expired. Sessions from a previous day are discarded. - The API client throttles requests (minimum 5 seconds between calls) and retries with exponential backoff on rate limits.
- Force charge/discharge actions set the inverter's work mode directly via foxess_modbus entity service calls. foxess_control manages time windows using Home Assistant timers — no cloud schedule involved.
- There is no multi-window schedule management. Each action sets a single mode;
clear_overridesreturns to Self Use. - Smart sessions use the same algorithms (consumption-aware deferral, SoC monitoring, power adjustment) as cloud mode — only the read/write transport differs.
- Session recovery after HA restart resumes based on persisted state without needing to verify cloud schedule groups.
Smart sessions are designed to survive transient failures:
- Transient API errors: A single cloud API failure is retried on the next timer tick. Only 3 consecutive failures open the circuit breaker.
- Circuit breaker: After 3 consecutive adapter errors, the session holds position (keeps the current schedule). After 5 more ticks without recovery, the session aborts to self-use to protect the battery.
- Automatic replay after outage: When the circuit breaker aborts a session and the time window is still open, the integration probes the API every 5 minutes and restarts the session on recovery (up to 6 attempts).
- HA restart recovery: Smart sessions are persisted to
.storage. If HA restarts mid-session, the session is automatically resumed if still within its time window, or cleaned up if expired. - Schedule safety horizon: The discharge schedule end time is set to a dynamically computed safe horizon based on current SoC, discharge rate, and safety factor. If HA loses connectivity, the inverter's schedule expires and reverts to self-use — the battery is protected without HA intervention.
-
Minimum SoC behaviour is unintuitive: When the battery reaches the minimum SoC during force discharge or feed-in, the inverter's behaviour may not match expectations. Smart actions work around this by setting
fdSocto an extreme value (100% for charge, 11% for discharge) so the inverter never triggers its own threshold — HA monitors SoC and stops the action at the user's configured target. For plainforce_charge/force_discharge, consider using an automation to cancel the override before the battery reaches the minimum SoC level. -
Schedule race condition (cloud mode only): Force charge/discharge actions read the current schedule, modify it, then write it back. If the schedule is changed between the read and write (e.g. via the FoxESS app), those changes will be overwritten. Enable debug logging for
foxess_controlto see before/after state if schedules change unexpectedly. Entity mode does not have this issue. -
FoxESS Cloud API latency (cloud mode only): All commands go through the FoxESS Cloud API, which throttles requests to one every 5 seconds. Actions are not instantaneous. For faster local control, enable entity mode with foxess_modbus.
-
FoxESS mode scheduler bugs (cloud mode only): The FoxESS Cloud API has known issues with schedule validation (e.g. rejecting its own saved schedules due to overlap detection on disabled groups). This integration works around known issues, but the API may introduce new ones.
FoxESS Control includes all the polled sensors that foxess-ha provides (SoC, charge/discharge power, solar generation, house load, grid power, temperatures, cumulative energy counters, and more), plus smart battery management. The legacy foxess-ha integration is read-only, so it is unlikely to be deeply wired into automations or dashboards.
The cleanest migration path is to remove foxess-ha first, then install FoxESS Control:
- Remove the foxess-ha integration from Settings > Devices & Services. This avoids entity name collisions and gives FoxESS Control the cleanest possible entity IDs.
- Clean up orphaned entities (if any remain after removal): go to Settings > Devices & Services > Entities, filter by "unavailable" or search for
foxess, and delete any leftover entities from the old integration. - Install FoxESS Control via HACS and configure it. Sensor entity IDs will follow the
sensor.foxess_*pattern — see the Sensors section above for the full list. - Update any automations or dashboards that referenced foxess-ha entities to use the new entity IDs. Since foxess-ha was read-only, these are typically just sensor references on dashboard cards.
Note: If you prefer to run both integrations side-by-side temporarily, keep the polling interval at 300 seconds (the default) on both to avoid exceeding the FoxESS API quota (~1440 requests/day).
See FAQ.md for answers to common questions: back-to-back sessions, working without web credentials, HA restart behaviour, API quota, entity mode vs cloud mode, battery capacity, and more.
See TROUBLESHOOTING.md for decision-tree guides to common issues: sessions not starting, grid import during discharge, WebSocket problems, early session aborts, and stale data.
Diagnostics: Click "Download Diagnostics" on the integration page (Settings > Devices & Services > FoxESS Control) to export coordinator data, session state, WebSocket status, taper profile, and config — with API keys and credentials redacted.
Repair issues: Actionable errors (unmanaged work mode, session aborts, sensor state-write failures) surface in HA's Repairs panel instead of just logs. Issues auto-clear when the problem is resolved or a new session starts. If a sensor fails to write its state, the Repair names the specific sensor and the error — other sensors continue updating rather than silently freezing.
Reauthentication: When the FoxESS API key expires or becomes invalid, HA shows a "Reconfigure" prompt instead of silently failing. Enter a new key without removing and re-adding the integration.
If you find this integration useful, consider buying me a coffee:
MIT





