Page 2 of 2

Re: Work Mode Switching Issues with foxess_modbus_EVO Integration EVO 10-8-H

Posted: Thu Jul 09, 2026 3:13 pm
by BOD
matthewsl wrote: Sun Jun 21, 2026 9:11 pm Hi,
Im also using adam newberry's integration. It really doesnt work out of the box. i can only guess that adam didnt upload all the changes he had made. its taken a lot work with claude code to fix the issues. Matty your tip regarding the registers was the final fix. The force charge and discharge parts of the integration were not mapped for the evo. claude code fixed this easily by adding the evo in for these registers. Ive asked claude code to provide a list of the other tweaks required to get this to work:
# FoxESS EVO10 / foxess_modbus_evo — Work mode writes are off-by-one: full fix including Force Charge, Force Discharge and charge period registers

---

## Background

I'm running an EVO10-H with the `foxess_modbus_evo` integration (Adam Newberry's fork). Following on from MattyS's finding about the write/read offset on register 49203 — I've confirmed this and worked out a complete set of fixes covering not just the basic work modes but also Force Charge, Force Discharge, and the registers that control grid charging. Sharing the detail so others can apply it directly or use it as a prompt for an AI assistant.

---

## Problem 1 — Work mode register 49203 write/read offset

As MattyS found: the EVO10 uses 0-based values for *writes* but 1-based values for *reads* on register 49203.

| Operation | Self Use | Feed-in First | Back-up | Peak Shaving |
|---|---|---|---|---|
| Write to inverter | 0 | 1 | 2 | 3 |
| Read back from inverter | 1 | 2 | 3 | 4 |

The integration was using the read values (1, 2, 3) for writes too, so every write landed one step off:

| HA tried to set | Inverter received | Inverter actually did | Showed in HA |
|---|---|---|---|
| Self Use | 1 | Feed-in First | Feed-in First |
| Feed-in First | 2 | Back-up | Back-up |
| Back-up | 3 | *(undefined)* | Unknown |

**Fix** — in `entities/entity_descriptions.py`, find the `ModbusWorkModeSelectDescription` for `Inv.EVO_10_H` and add a `write_options_map` with 0-based write values. Also add `0: "Self Use"` as an alias key in `options_map` to prevent a 5-second "unknown" flash caused by the controller's write-cache returning the written value (0) before the next poll confirms the read-back (1):

```python
yield ModbusWorkModeSelectDescription(
key="work_mode",
address=[ModbusAddressSpec(holding=49203, models=Inv.EVO_10_H)],
name="Work Mode",
options_map={
0: "Self Use", # write-cache alias (5s window after write)
1: "Self Use", # confirmed read-back after writing 0
2: "Feed-in First",
3: "Back-up",
4: "Peak Shaving",
},
write_options_map={
"Self Use": 0,
"Feed-in First": 1,
"Back-up": 2,
"Peak Shaving": 3,
},
)
```

Because two keys (0 and 1) now map to "Self Use", also patch `ModbusSelect.__init__` in `entities/modbus_select.py` to deduplicate the options list so the HA dropdown shows "Self Use" only once:

```python
# Replace:
self._attr_options = list(self.entity_description.options_map.values())

# With:
seen: set[str] = set()
self._attr_options = []
for v in self.entity_description.options_map.values():
if v not in seen:
seen.add(v)
self._attr_options.append(v)
```

---

## Problem 2 — Force Charge and Force Discharge use a separate remote control system

Selecting Force Charge or Force Discharge in HA does *not* just write 49203. It goes through a **remote control manager** using three separate registers:

| Register | Name | Purpose |
|---|---|---|
| 46001 | `remote_enable` | Write 1 to activate remote control, 0 to release it |
| 46002 | `timeout_set` | Watchdog in seconds — inverter reverts to fallback mode if no active-power write arrives within this time |
| 46003 / 46004 | `active_power` | Signed watts — negative = import/charge, positive = export/discharge |

When Force Charge is active, the integration writes negative active-power values every poll cycle to pull power from the grid. It also writes a **fallback work mode** to register 49203 — the mode the inverter will use if the watchdog fires. Force Charge uses **Self Use** as its fallback; Force Discharge uses **Feed-in First**.

The `work_mode_map` in `entities/remote_control_description.py` for the EVO10 block was also using wrong (1-based) write values for this fallback write. Fix it:

```python
# In the EVO_10_H RemoteControlAddressSpec:

# Wrong:
work_mode_map={
WorkMode.SELF_USE: 1,
WorkMode.FEED_IN_FIRST: 2,
WorkMode.BACK_UP: 3,
},

# Correct:
work_mode_map={
WorkMode.SELF_USE: 0,
WorkMode.FEED_IN_FIRST: 1,
WorkMode.BACK_UP: 2,
},
```

Without this fix, Force Discharge writes `2` to 49203 as its fallback, but the inverter treats `2` as Back-up. When the watchdog fires the inverter settles in Back-up rather than Feed-in First. Worse, if HA restarts while remote control is active, the in-memory `_remote_control_enabled` flag resets to False and the integration won't write 46001=0 to release remote control on the next cycle. The inverter then sits with 46001=1 permanently, ignoring all subsequent work mode writes until explicitly released.

**Recovery if already stuck** — use the `foxess_modbus_evo.write_registers` service in Developer Tools:

```yaml
# Release remote control
service: foxess_modbus_evo.write_registers
data:
inverter: FoxEvo10
start_address: 46001
values: "0"

# Set Self Use (0-based)
service: foxess_modbus_evo.write_registers
data:
inverter: FoxEvo10
start_address: 49203
values: "0"
```

---

## Problem 3 — Force Charge from grid needs charge period registers, not just work mode

This caught us out. Setting work mode to Force Charge is *necessary* but not *sufficient* for the battery to charge from the grid. The inverter also requires a **charge period** to be configured — the time window during which grid charging is permitted.

The EVO10 uses the same charge period register layout as the H1 G2:

| Register | Purpose |
|---|---|
| 41001 | Period 1 — enable charge from grid (0=off, 1=on) |
| 41002 | Period 1 — start time |
| 41003 | Period 1 — end time |
| 41004 | Period 2 — enable charge from grid |
| 41005 | Period 2 — start time |
| 41006 | Period 2 — end time |

The `foxess_modbus_evo` fork correctly maps the EVO10 to these registers (same as `Inv.H1_G2_SET`) in `entities/charge_period_descriptions.py`. If you're using Predbat, note that **Predbat only writes to the work mode selector — it does not write charge period registers**. You must configure at least one period with `enable_charge_from_grid=on` and a valid time window yourself. A permanently-wide window (e.g. 00:00–23:59) with `enable_charge_from_grid=on` is the simplest approach if you want Predbat to handle all scheduling.

---

## Min/Max SoC registers

The EVO10 uses the same SoC limit registers as the H3 Pro:

| Register | Entity | Purpose |
|---|---|---|
| 46609 | `number.foxevo10_min_soc` | Minimum SoC — battery won't discharge below this |
| 46610 | `number.foxevo10_max_soc` | Maximum SoC for remote control Force Charge |
| 46611 | `number.foxevo10_min_soc_on_grid` | Minimum SoC when on grid |

Note: if you have the FoxESS app's **Scheduler** feature enabled, it blocks external Modbus writes to register 46609 with exception 0x03 "Illegal Data Value". Disable the scheduler in the FoxESS app to restore write access.

---

## Prompt for AI assistants

> I have the `foxess_modbus_evo` Home Assistant custom integration (Adam Newberry's fork of `foxess_modbus` with EVO10 support). Three fixes are needed:
>
> **Fix 1** — `entities/entity_descriptions.py`: Find the `ModbusWorkModeSelectDescription` for `Inv.EVO_10_H` at register 49203. Add `write_options_map={"Self Use": 0, "Feed-in First": 1, "Back-up": 2, "Peak Shaving": 3}` because the EVO10 uses 0-based write values. Also update `options_map` to include both `0: "Self Use"` (write-cache alias) and `1: "Self Use"` (confirmed read-back) alongside the existing entries for 2, 3, 4.
>
> **Fix 2** — `entities/modbus_select.py`: In `ModbusSelect.__init__`, replace `self._attr_options = list(self.entity_description.options_map.values())` with a version that deduplicates the list using a `seen` set, so "Self Use" only appears once in the HA dropdown despite having two keys in `options_map`.
>
> **Fix 3** — `entities/remote_control_description.py`: Find the `RemoteControlAddressSpec` for `Inv.EVO_10_H`. Change its `work_mode_map` from `{SELF_USE: 1, FEED_IN_FIRST: 2, BACK_UP: 3}` to `{SELF_USE: 0, FEED_IN_FIRST: 1, BACK_UP: 2}`. This controls the fallback work mode written to register 49203 during Force Charge and Force Discharge remote control sessions — the wrong values cause the inverter to revert to Back-up instead of Self Use/Feed-in First when the watchdog fires, and can leave the inverter stuck ignoring all work mode writes after an HA restart.
>
> **Context on charge period registers**: The EVO10 charge period registers (41001–41006) are mapped using the H1 G2 layout in `charge_period_descriptions.py` (`models=Inv.H1_G2_SET | Inv.EVO_10_H`). Force Charge from grid requires both the work mode set to Force Charge AND at least one charge period with `enable_charge_from_grid=on` and a valid time window. The min/max SoC registers (46609, 46610, 46611) are mapped using the H3 Pro layout. If the FoxESS app Scheduler is enabled, it blocks writes to 46609 — disable it in the app first.
>
> After making all three code changes, do a full HA restart (required for custom component code changes).
Laurence,

I’ve tried applying the changes you described, but I’m hitting an error when Home Assistant restarts. The problem is with write_options_map — it isn’t defined anywhere in the ModbusSelectDescription class in my installation, so HA rejects it as an unknown argument.

Did you have to modify that class yourself, or make any other changes to get write_options_map working? I’d really appreciate knowing what you did so I can line my version up with yours.

Brian

Re: Work Mode Switching Issues with foxess_modbus_EVO Integration EVO 10-8-H

Posted: Fri Jul 10, 2026 9:02 am
by BOD
I managed to track down the cause of the issue. The fork of Adam Newberry’s integration includes changes in the **mronionsonions/foxess_modbus_EVO_Extended** version, where `modbus_select.py` has been updated to support `write_options_map`. Replacing my local copy of that file in Home Assistant removed the startup error.

However, when I tested mode switching, it didn’t work. The fix was to comment out the `write_options_map` entries in `entity_descriptions.py`. After doing that, the mode changes worked normally. It appears my EVO 10‑8‑H doesn’t use the write/read offset that the extended integration expects.

I still need to do more testing to ensure it all works correctly.

For reference, my inverter firmware versions are:

- INV_Master: 1.21
- INV_Slave: 1.01
- INV_Manager: 1.18
- AFCI: 0.37
- Software: 2.09
- BAT_BCU: 1.004

Brian