Configuration¶
All settings are managed through the Settings tab in the miniEMS dashboard.
Values are stored in /data/config.json and survive restarts, updates, and Supervisor reloads.
No HA add-on config UI
As of v1.4.0, the HA add-on "Configuration" tab is intentionally empty. All configuration is done through the miniEMS Settings page.
How values are loaded
On every startup miniEMS reads three sources, in this priority order (highest first):
/data/options.json— Supervisor-managed values that differ from the default (you changed them via the HA UI)/data/config.json— the last persisted values (survives anoptions.jsonreset caused by Supervisor reloads / addon updates)- Built-in defaults
The merged result is written back to /data/config.json on every start, so your settings are never lost. Old field names (e.g. from versions before v2.0) are migrated automatically to their current names.
Values that do not appear on the Settings form can be edited directly as JSON via the config.json / options.json tabs in the dashboard (see the "Raw File Editors" section at the bottom of this page).
Inverter Entities¶
Find these in HA → Developer Tools → States — filter by deye to locate your exact entity IDs.
| Setting | Default | Description |
|---|---|---|
pv_power_entity |
sensor.deye_pv_total_power |
Total PV output (W) |
battery_soc_entity |
sensor.deye_battery_soc |
Battery state of charge (%) |
battery_power_entity |
sensor.deye_battery_power |
Battery power (W) — negative = charging, positive = discharging on the Deye 8K (verified live; an earlier version of this line had the sign backwards) |
grid_power_entity |
sensor.deye_grid_power |
Grid power (W) — positive = import, negative = export |
load_power_entity |
sensor.deye_load_power |
House load (W) |
grid_import_energy_entity |
sensor.deye8k_today_energy_import |
HA entity providing the inverter's daily grid import total (kWh, resets at midnight). When set, replaces the calculated import kWh; grid cost is still accumulated per tick. Leave empty to fall back to calculation from grid_power_entity. |
feed_in_energy_entity |
sensor.deye8k_today_energy_export |
HA entity providing the inverter's daily feed-in total (kWh, resets at midnight). When set, replaces the calculated feed-in value. Leave empty to fall back to calculation from grid_power_entity. |
load_consumption_entity |
sensor.deye8k_today_load_consumption |
HA entity providing the inverter's daily household consumption total (kWh, resets at midnight). When set, replaces the value extrapolated from load_power_entity — making it immune to add-on restarts. Load cost (€) is still accumulated per tick, since that needs the price at each point in time. Leave empty to fall back to calculation. Since v2.0.3. |
grid_import_total_entity |
sensor.deye8k_total_energy_import |
Lifetime grid-import counter (kWh, never resets). Preferred over the daily entity above since v2.0.4: the inverter resets its daily counters on its own clock — measured 4min54s after local midnight — and still reports yesterday's closing total during that window. miniEMS derives the daily delta itself instead, cutting the day where it cuts cost. The daily entity stays in use as the anchor bootstrap after a mid-day restart. Leave empty to fall back to the daily entity. |
feed_in_total_entity |
sensor.deye8k_total_energy_export |
Lifetime feed-in counter. Behaves like grid_import_total_entity. Since v2.0.4. |
load_consumption_total_entity |
sensor.deye8k_total_load_consumption |
Lifetime household-consumption counter. Behaves like grid_import_total_entity. Since v2.0.4. |
solcast_last_fetch_entity |
sensor.solcast_pv_forecast_zeitpunkt_letzter_api_abruf |
Timestamp of the last successful Solcast fetch. What is evaluated is this entity's value, not its timestamp — the value is exactly the age of the forecast data. Needed because Solcast keeps serving its disk cache when the API is unreachable: the sensors stay available, their timestamps keep advancing, and nothing reveals that the numbers are days old. Beyond 30 h of data age the forecast is discarded for decisions and reported in the banner. Leave empty to disable the check. Since v2.0.4. |
grid_charge_min_margin_eur_kwh |
0.02 |
Minimum economic margin for grid charging (€/kWh). Charging happens only when efficiency × discharge_tariff − purchase price exceeds this. The discharge tariff is derived from history (discharge-weighted price over the last 7 days); avg_discharge_tariff_eur_kwh overrides it when set. While efficiency or discharge tariff are unknown the check does not apply. Since v2.0.4. |
All five required entities above are checked for staleness (sensor_max_age_sec, default 300 s): if an entity stops updating for that long, miniEMS treats it as "unavailable" and falls back to the safe state (see the "Grid-Friendly PV Strategy" section further down).
Battery Settings¶
| Setting | Default | Description |
|---|---|---|
battery_capacity_kwh |
10.0 |
Usable battery capacity in kWh. Overridden by battery_capacity_entity if set. |
battery_min_soc |
15 |
Minimum SoC (%). Below this, miniEMS switches to Battery Protection and blocks discharging. |
battery_max_soc |
95 |
Maximum SoC (%). Charging stops (Idle mode) once reached. |
Automatic safeguard
If battery_min_soc ≥ battery_max_soc, miniEMS automatically disables battery_control_enabled on startup and logs a warning — an inconsistent configuration can't reach the inverter unfiltered.
Authentication¶
| Setting | Default | Description |
|---|---|---|
long_lived_token |
(empty) | HA long-lived token — used as fallback if the Supervisor token is rejected with a 401. Usually not needed. |
To create one: HA → Profile → Long-Lived Access Tokens → Create Token.
Octopus Energy / Electricity Price¶
| Setting | Default | Description |
|---|---|---|
electricity_price_entity |
sensor.octopus_energy_electricity_current_rate |
Current spot price sensor (€/kWh) |
cheap_rate_threshold_eur |
0.10 |
Grid charging triggers when price is below this value — also the low tier ceiling |
medium_rate_threshold_eur |
0.20 |
Price tier boundary: low below cheap_rate_threshold_eur, medium between the two, high at or above this value |
feed_in_tariff_eur_kwh |
0.08 |
Revenue per kWh exported to the grid |
fix_price |
0.30 |
Fixed-rate tariff for the "Cost at Fix Price" comparison sensor |
Price tier logic
Three tiers classify every tick's consumption for the Price Tier Usage dashboard section and the six sensor.miniems_*kwh*rate HA sensors:
| Tier | Condition |
|---|---|
| Low | price < cheap_rate_threshold_eur |
| Medium | cheap_rate_threshold_eur ≤ price < medium_rate_threshold_eur |
| High | price ≥ medium_rate_threshold_eur |
The current tier is also shown next to the price on the dashboard (green / amber / red). If the price hasn't updated for longer than price_max_age_sec (default 21,600 s = 6 h), it's treated as stale and cannot trigger grid charging.
EMS Parameters¶
| Setting | Default | Description |
|---|---|---|
pv_surplus_threshold_w |
200 |
Minimum PV surplus (W, pv_power - load_power) to trigger PV Charging mode |
update_interval_sec |
30 |
How often the EMS loop runs (seconds, 10–300) |
event_log_retention_days |
30 |
How many days of event log entries to keep in the database |
Battery Control¶
Enable Simulation Mode first
Before enabling live control, run with Simulation Mode on. Verify the log shows the correct commands for your inverter.
Amps, not watts
The Deye charge/discharge limits are entities in amps, not watts — unlike in miniEMS versions before v2.0. The old *_power_w field names are migrated automatically to the new *_current_a fields on startup; any previously stored watt values are not converted, since there is no valid 1:1 conversion. Double-check the migrated values in Settings once after updating from an older version.
| Setting | Default | Description |
|---|---|---|
battery_control_enabled |
false |
Master switch for inverter control |
battery_control_simulation |
true |
Log commands but do not send them |
inverter_charge_current_entity |
number.deye8k_battery_max_charging_current |
Entity for setting the charge current limit (A) |
grid_charge_switch_entity |
switch.deye8k_battery_grid_charging |
Switch entity that enables/disables grid charging |
battery_discharging_current_entity |
number.deye8k_battery_max_discharging_current |
Entity for setting the discharge current limit (A) |
battery_max_charge_current_a |
185 |
Maximum charge current (A). Automatically clamped to 0–350 A. |
battery_max_discharge_current_a |
185 |
Maximum discharge current (A). Automatically clamped to 0–350 A. |
How inverter control works¶
Every EMS mode sets all three inverter values explicitly, so the resulting state never depends on the mode that preceded it:
| EMS Mode | Grid Charge Switch | Charge Current | Discharge Current |
|---|---|---|---|
| Grid Charging | switch.turn_on |
battery_max_charge_current_a |
0 A (prevents immediately discharging what was just bought) |
| PV Charging | switch.turn_off |
battery_max_charge_current_a |
battery_max_discharge_current_a |
| Export Surplus (grid-friendly hold) | switch.turn_off |
export_hold_charge_current_a |
battery_max_discharge_current_a |
| Battery Protection | switch.turn_off |
battery_max_charge_current_a |
0 A |
| Idle | switch.turn_off |
battery_max_charge_current_a |
battery_max_discharge_current_a |
Commands are idempotent — miniEMS only sends a service call when the target value changes. Export Surplus only appears when pv_export_priority_enabled is on (see below).
Write confirmation (since v2.0.1)
A service call Home Assistant accepts (HTTP 200) is not proof the inverter applied it — some Deye/Solarman bridges only confirm a written value on their next poll, which can lag several minutes. miniEMS therefore compares the real HA state against the target on every tick and automatically resends the command as long as they don't match — independently for charge current, discharge current, and the grid-charge switch. If a value stays unconfirmed, a warning appears in the dashboard banner ("Inverter control: N unconfirmed write(s)").
Grid-Friendly PV Strategy (Phase 7)¶
Optional strategy that exports PV surplus to the grid instead of storing it, for as long as the Solcast remaining-today forecast stays above roughly what the battery still needs. Goal: fill the battery late in the day when there's still enough sun coming — sparing the grid an unnecessary midday export spike while still leaving enough reserve for the evening.
Disabled by default
With pv_export_priority_enabled = false, miniEMS behaves exactly as before: any PV surplus charges the battery immediately. The strategy also has no effect while battery_control_enabled = false — miniEMS logs a warning in that case.
| Setting | Default | Description |
|---|---|---|
pv_export_priority_enabled |
false |
Master switch for the strategy |
pv_charge_margin_factor |
1.2 |
Safety factor applied to the battery's need when comparing it to the remaining forecast. >1 charges earlier (buffer against an over-optimistic Solcast forecast). Automatically clamped to 0.5–3.0. |
pv_charge_hysteresis_frac |
0.10 |
Deadband around the trigger point (0.10 = ±10 %) so the mode cannot flap. Automatically clamped to 0.0–0.5. |
pv_export_min_soc_pct |
30 |
Below this SoC the export hold is never applied — the battery always charges. Must exceed battery_min_soc; otherwise it's automatically raised to battery_min_soc + 10. |
pv_charge_backstop_hour |
14 |
Local hour (0–23) from which the battery always charges regardless of the forecast — prevents an overly optimistic afternoon from leaving the battery empty. |
export_hold_charge_current_a |
0 |
Charge current (A) while holding the export. 0 blocks charging entirely. Automatically clamped to 0–350 A. |
mode_dwell_sec |
300 |
A new mode must be requested continuously for this long before it actually applies (anti-flapping). Urgent transitions (SoC protection, sensor failure) bypass this delay. Automatically clamped to 0–3600 s. |
battery_soc_hysteresis_pct |
2 |
Battery Protection is only left once the SoC has risen above battery_min_soc + battery_soc_hysteresis_pct — prevents flapping right at the boundary. |
grid_charge_min_free_kwh |
1.0 |
Minimum free battery capacity (kWh) below which grid charging is no longer considered worthwhile. |
grid_charge_dark_start_hour / grid_charge_dark_end_hour |
21 / 6 |
Time window (local hours) in which grid charging is allowed when neither today's nor tomorrow's Solcast forecast can decide it ("no more sun today, and nothing reliable known about tomorrow either"). Both automatically clamped to 0–23. |
sensor_max_age_sec |
300 |
Power/SoC sensors are considered stale after this long without an update — 5 minutes frozen on a live value means a broken connection. |
forecast_max_age_sec |
28800 |
Staleness limit for the Solcast forecast (typically updates every ~30 min in daylight, but can legitimately go quiet for 6+ hours overnight depending on your Solcast plan — 8 h of margin avoids false alarms). |
price_max_age_sec |
21600 |
Staleness limit for the dynamic tariff price (some providers hold the same value for several hours). |
How the decision works
- The hold (
Export Surplus) only starts when all conditions are met: strategy enabled, before the backstop hour, SoC abovepv_export_min_soc_pct, free capacity available, and the Solcast remaining-today forecast (solcast_remaining_today_entity) above the threshold computed frompv_charge_margin_factor/pv_charge_hysteresis_frac. - Any missing or stale input (SoC, forecast, time window) immediately releases the hold and lets the battery charge normally — the logic always fails toward "fill the battery," never toward "leave it empty."
- When today's forecast is missing, stale, or simply spent (e.g. in the evening, once no more sun is coming), grid charging now checks tomorrow's Solcast forecast (
solcast_tomorrow_entity) first, added in v2.0.1: if it's enough to refill the battery on its own, miniEMS will not buy grid energy. Only when tomorrow's forecast is also missing/insufficient/stale does it fall back to the plain dark window (grid_charge_dark_start_hour–grid_charge_dark_end_hour), same as before.
Forecast & Prediction¶
| Setting | Default | Description |
|---|---|---|
weather_entity |
weather.openweathermap |
HA weather entity for temperature-based load prediction |
The prediction model uses historical consumption data from similar-temperature days. If no history exists yet, temperature-based fallback rules apply:
| Condition | Predicted Load |
|---|---|
| Night temp < 0 °C and day temp < 0 °C | 30 kWh |
| Night temp < 0 °C and day temp < 10 °C | 20 kWh |
| Night temp > 0 °C and day temp < 15 °C | 10 kWh |
Solcast PV Forecast¶
Solcast provides highly accurate rooftop PV forecasts. Install the Solcast HA integration and configure the entities here.
| Setting | Default | Description |
|---|---|---|
solcast_remaining_today_entity |
sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute |
Expected PV remaining for today (kWh) — used in both the grid-charge and export-hold decisions |
solcast_today_entity |
sensor.solcast_pv_forecast_prognose_heute |
Total expected PV for today (kWh) — dashboard display |
solcast_tomorrow_entity |
sensor.solcast_pv_forecast_prognose_morgen |
Expected PV for tomorrow (kWh) — dashboard display and, since v2.0.1, a fallback for the grid-charge decision (see below) |
Why Solcast remaining matters
Both the grid-charge decision and the grid-friendly PV strategy compare the battery's free capacity against solcast_remaining_today_kwh.
If Solcast is not configured, stale for longer than forecast_max_age_sec, or today's sun is simply spent (legitimately ~0, e.g. in the evening), the day's forecast is treated as "unavailable". For the grid-charge decision, miniEMS then also checks solcast_tomorrow_kwh before falling back to the more conservative dark-window rules above — the grid-friendly PV strategy (export hold) still relies on the today forecast only.
Advanced Sensors for Balance-Based Cost Calculation¶
These fields are optional and not yet available as form fields on the Settings page. Set them via the config.json tab (see the "Raw File Editors" section at the bottom of this page). When set, miniEMS uses the inverter's actual daily totals instead of the values extrapolated from instantaneous power — see Sensors for calculation details.
| Setting | Default | Description |
|---|---|---|
battery_charge_entity |
sensor.deye8k_today_battery_charge |
Daily battery charge total (kWh) from the inverter |
battery_discharge_entity |
sensor.deye8k_today_battery_discharge |
Daily battery discharge total (kWh) from the inverter |
battery_capacity_entity |
sensor.deye8k_battery_capacity |
Battery capacity read directly from the inverter — overrides battery_capacity_kwh when set |
battery_state_entity |
sensor.deye8k_battery_state |
Battery state enum (charging / discharging / idle) |
today_production_entity |
sensor.deye8k_today_production |
Daily gross PV production (kWh) — used for the inverter efficiency calculation |
today_losses_entity |
sensor.deye8k_today_losses |
Daily inverter losses (kWh) |
power_losses_entity |
sensor.deye8k_power_losses |
Real-time power losses (W) — for the live efficiency display |
Advanced Cost Parameters¶
Also editable only via config.json:
| Setting | Default | Description |
|---|---|---|
daily_base_price_eur |
0.0 |
Fixed daily base/standing charge (€/day) added on top of energy costs — e.g. your contract's monthly base fee, spread over the day |
avg_discharge_tariff_eur_kwh |
0.0 |
Average discharge tariff used for the battery discharge ROI calculation (€/kWh). 0 = auto-derive from the three price tiers |
Raw File Editors (config.json / options.json)¶
The config.json and options.json tabs in the dashboard let you edit and save the entire configuration directly as JSON — useful for fields that don't (yet) have their own form field on the Settings page (see the two sections above).
| Tab | File | Purpose |
|---|---|---|
| config.json | /data/config.json |
Persistent values written by miniEMS itself. Takes precedence over options.json whenever a value differs from the built-in default. Recommended for durable changes. |
| options.json | /data/options.json |
Managed by the HA Supervisor. Can be overwritten by an addon reconfiguration or schema update — changes made here are not guaranteed to persist. |
Both editors validate the JSON before saving (use the "Reformat JSON" button to check/pretty-print) and restart the addon automatically after saving so the new configuration takes effect.