Skip to content

Fox ESS Modbus - Setting Max SOC value #3837

@AlexEmmins

Description

@AlexEmmins

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

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions