FoxESS Modbus: soc_max_charge (number.max_soc) is never written to despite being configured
Description
Predbat plans charges with a specific SOC target (e.g. 64%) but never writes that value to the soc_max_charge entity (number.max_soc) on a FoxESS Modbus inverter. The inverter then charges to 100% during Force Charge mode instead of stopping at the planned target.
The reserve (number.min_soc_on_grid) and battery_min_soc (number.min_soc) entities ARE being written to successfully, so this is specific to the charge target SOC.
Expected behaviour
When predbat plans a charge to 64%, it should call number.set_value on number.max_soc to set 64 before (or when) starting the Force Charge window, and reset it to 100% after the window ends.
Actual behaviour
number.max_soc remains at 100% permanently (confirmed via HA history, no changes since at least 16th April)
- No log entries related to writing/setting
soc_max_charge
- Predbat correctly plans the charge and executes
charge_start_service (work mode switches to Force Charge), but the SOC target is never set
- The inverter charges to 100% instead of the planned target
Root cause in code
The FoxESS inverter definition in apps/predbat/config.py has has_target_soc: False:
"FoxESS": {
"name": "FoxESS",
"has_target_soc": False,
"has_reserve_soc": True,
# ... other fields
},
In apps/predbat/inverter.py, the adjust_battery_target() function checks this flag:
# When has_target_soc is True (e.g. GivEnergy):
# writes to charge_limit entity via write_and_poll_value() or rest_setChargeTarget()
# When has_target_soc is False (FoxESS):
# falls back to mimic_target_soc() which toggles charge rate on/off
# based on current SOC vs target, but NEVER writes to number.max_soc
The mimic_target_soc() fallback doesn't work correctly for FoxESS Modbus because when the inverter is in "Force Charge" mode (set via charge_start_service), it charges to whatever number.max_soc is set to (100%), regardless of predbat trying to control via rate toggling.
Compare with GivEnergy which has has_target_soc: True — predbat writes the charge target directly, and inverter_soc_reset handles resetting it afterwards.
The FoxESS community sample apps.yaml (https://foxesscommunity.com/viewtopic.php?t=2812) acknowledges this limitation:
"Theoretically Target SoC should be configured with number.max_soc but this causes issues because target_soc is not automatically reset to 100% after use by switch.predbat_inverter_soc_reset, and this blocks all charging."
Proposed fix
Change has_target_soc to True for FoxESS in config.py and ensure the SOC reset logic works for FoxESS Modbus entities. The reset just needs to call number.set_value on number.max_soc with value 100 when a charge window ends.
Alternatively, allow users to override has_target_soc in apps.yaml so that FoxESS Modbus users (who have a writable number.max_soc entity) can opt in to direct SOC target control.
Environment
- Predbat version: v8.37.5
- Inverter type: FoxESS (via FoxESS Modbus integration, local not cloud)
- inverter_type in apps.yaml: FoxESS
Relevant apps.yaml config
inverter_type: FoxESS
num_inverters: 1
soc_max_charge:
- number.max_soc
reserve:
- number.min_soc_on_grid
battery_min_soc:
- number.min_soc
set_soc_minutes: 20
charge_start_service:
service: select.select_option
entity_id: select.work_mode
option: "Force Charge"
charge_stop_service:
service: select.select_option
entity_id: select.work_mode
option: "Self Use"
Workaround
HA automation that syncs predbat.charge_limit to number.max_soc, resetting to 100% when no charge window is active:
alias: "Sync predbat charge limit to FoxESS max_soc"
trigger:
- platform: state
entity_id: predbat.charge_limit
action:
- service: number.set_value
target:
entity_id: number.max_soc
data:
value: "{% if trigger.to_state.state | int(0) > 0 %}{{ trigger.to_state.state | int }}{% else %}100{% endif %}"
mode: single
FoxESS Modbus: soc_max_charge (number.max_soc) is never written to despite being configured
Description
Predbat plans charges with a specific SOC target (e.g. 64%) but never writes that value to the
soc_max_chargeentity (number.max_soc) on a FoxESS Modbus inverter. The inverter then charges to 100% during Force Charge mode instead of stopping at the planned target.The
reserve(number.min_soc_on_grid) andbattery_min_soc(number.min_soc) entities ARE being written to successfully, so this is specific to the charge target SOC.Expected behaviour
When predbat plans a charge to 64%, it should call
number.set_valueonnumber.max_socto set 64 before (or when) starting the Force Charge window, and reset it to 100% after the window ends.Actual behaviour
number.max_socremains at 100% permanently (confirmed via HA history, no changes since at least 16th April)soc_max_chargecharge_start_service(work mode switches to Force Charge), but the SOC target is never setRoot cause in code
The FoxESS inverter definition in
apps/predbat/config.pyhashas_target_soc: False:In
apps/predbat/inverter.py, theadjust_battery_target()function checks this flag:The
mimic_target_soc()fallback doesn't work correctly for FoxESS Modbus because when the inverter is in "Force Charge" mode (set viacharge_start_service), it charges to whatevernumber.max_socis set to (100%), regardless of predbat trying to control via rate toggling.Compare with GivEnergy which has
has_target_soc: True— predbat writes the charge target directly, andinverter_soc_resethandles resetting it afterwards.The FoxESS community sample apps.yaml (https://foxesscommunity.com/viewtopic.php?t=2812) acknowledges this limitation:
Proposed fix
Change
has_target_soctoTruefor FoxESS inconfig.pyand ensure the SOC reset logic works for FoxESS Modbus entities. The reset just needs to callnumber.set_valueonnumber.max_socwith value 100 when a charge window ends.Alternatively, allow users to override
has_target_socinapps.yamlso that FoxESS Modbus users (who have a writablenumber.max_socentity) can opt in to direct SOC target control.Environment
Relevant apps.yaml config
Workaround
HA automation that syncs
predbat.charge_limittonumber.max_soc, resetting to 100% when no charge window is active: