From c7e66f4a16d51fd205befac126edd2eebac340ca Mon Sep 17 00:00:00 2001 From: upton68 Date: Thu, 20 Aug 2026 18:11:17 +0100 Subject: [PATCH 1/4] docs: add Hanchu iESS apps.yaml template This file contains the configuration for the Hanchu iESS, detailing setup instructions, sensor definitions, and control parameters for integration with Home Assistant. --- templates/hanchu_cloud.yaml | 270 ++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 templates/hanchu_cloud.yaml diff --git a/templates/hanchu_cloud.yaml b/templates/hanchu_cloud.yaml new file mode 100644 index 000000000..176d162a0 --- /dev/null +++ b/templates/hanchu_cloud.yaml @@ -0,0 +1,270 @@ +# Predbat config for the Hanchu iESS, via the hanchu-ess-ha HACS +# integration (https://github.com/upton68/hanchu-ess-ha) +# +# Copy this over your apps.yaml and: +# 1. Install the "Hanchu iESS" integration via HACS and configure it +# with your Hanchu cloud account credentials. +# 2. Create three helpers in Home Assistant (see inverter-setup docs). +# 3. Create the bridge script (see inverter-setup docs). +# 4. Add the soc_kw template sensor to configuration.yaml (see inverter-setup docs). +# 5. Replace YOURSERIAL throughout with your device serial number as it +# appears in your HA entity IDs (set automatically by the integration). +# 6. Adjust inverter_limit, inverter_limit_charge, inverter_limit_discharge, +# inverter_limit_export and battery_rate_max to match your hardware. +# 7. Delete the template: True line below once you have configured this file. +# +# Note: The 5000W limits are set to the author's hardware — adjust these +# to match your own inverter and battery rated capacity. +--- +pred_bat: + module: predbat + class: PredBat + prefix: predbat + timezone: Europe/London + currency_symbols: + - '£' + - 'p' + threads: auto + # XXX: This is a configuration template, delete this line once you edit your configuration + template: True + load_filter_threshold: 30 + # ─── Hanchu iESS daily energy sensors ───────────────────────────────────── + load_today: + - sensor.hanchuess_YOURSERIAL_daily_load_energy + import_today: + - sensor.hanchuess_YOURSERIAL_daily_grid_import + export_today: + - sensor.hanchuess_YOURSERIAL_daily_grid_export + pv_today: + - sensor.hanchuess_YOURSERIAL_daily_pv_energy + # ─── Hanchu iESS inverter definition ────────────────────────────────────── + num_inverters: 1 + inverter_type: "HC" + inverter: + name: "Hanchu iESS" + has_rest_api: false + has_mqtt_api: false + has_service_api: true + output_charge_control: "power" + charge_control_immediate: true + has_charge_enable_time: false + has_discharge_enable_time: false + has_target_soc: false + has_reserve_soc: false + has_timed_pause: false + charge_time_format: "S" + charge_time_entity_is_option: false + soc_units: "%" + num_load_entities: 1 + has_ge_inverter_mode: false + has_fox_inverter_mode: false + time_button_press: false + clock_time_format: "%d-%m-%y %H:%M:%S" + write_and_poll_sleep: 2 + has_time_window: false + support_charge_freeze: false + support_discharge_freeze: false + balance_inverters_seconds: 0 + # ─── Hanchu iESS control surface (write) ────────────────────────────────── + # All four hooks call the same queued script, passing mode_action so it + # knows which state to apply. See inverter-setup docs for the full script. + charge_start_service: + - service: script.hanchu_set_state_queued + data: + mode_action: charge_start + charge_stop_service: + - service: script.hanchu_set_state_queued + data: + mode_action: charge_stop + discharge_start_service: + - service: script.hanchu_set_state_queued + data: + mode_action: discharge_start + discharge_stop_service: + - service: script.hanchu_set_state_queued + data: + mode_action: discharge_stop + # ─── Hanchu iESS sensor entities (read) ─────────────────────────────────── + charge_rate: + - number.hanchuess_YOURSERIAL_charge_power_limit + discharge_rate: + - number.hanchuess_YOURSERIAL_discharge_power_limit + battery_power: + - sensor.hanchuess_YOURSERIAL_battery_power + battery_power_invert: + - true # Hanchu sign convention is opposite to Predbat's + pv_power: + - sensor.hanchuess_YOURSERIAL_power + load_power: + - sensor.hanchuess_YOURSERIAL_load_power + load_power_invert: + - false + grid_power: + - sensor.hanchuess_YOURSERIAL_grid_power + grid_power_invert: + - true # Hanchu sign convention is opposite to Predbat's + soc_percent: + - sensor.hanchuess_YOURSERIAL_battery_soc + soc_max: + - sensor.hanchuess_YOURSERIAL_battery_capacity_kwh # Returns capacity in kWh, used to derive soc_kw + soc_kw: + - sensor.home_battery_state_of_charge_kwh # Template sensor — see configuration.yaml step in inverter-setup docs + battery_min_soc: + - number.hanchuess_YOURSERIAL_minimum_discharge_soc + # ─── Hanchu iESS battery physical specs ─────────────────────────────────── + # Adjust all limits to match your inverter and battery rated capacity in watts + inverter_battery_rate_min: + - 0 # Older firmware may require 500 + inverter_limit: + - 5000 + inverter_limit_charge: + - 5000 + inverter_limit_discharge: + - 5000 + inverter_limit_export: + - 5000 + battery_rate_max: + - 5000 + # Charge/discharge power curves based on observed Hanchu iESS behaviour + battery_charge_power_curve: + 97: 0.5 + 98: 0.2 + 99: 0.1 + battery_discharge_power_curve: + 4: 1.0 + inverter_clock_skew_start: 0 + inverter_clock_skew_end: 0 + inverter_clock_skew_discharge_start: 0 + inverter_clock_skew_discharge_end: 0 + inverter_can_charge_during_export: false # Single inverter setup + clock_skew: 0 + # ─── Solcast solar forecast ─────────────────────────────────────────────── + # Solcast cloud interface, set this or the local interface below + #solcast_host: 'https://api.solcast.com.au/' + #solcast_api_key: 'xxxx' + #solcast_poll_hours: 8 + pv_forecast_today: re:(sensor.(solcast_|)(pv_forecast_|)forecast_today) + pv_forecast_tomorrow: re:(sensor.(solcast_|)(pv_forecast_|)forecast_tomorrow) + pv_forecast_d3: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_3|d3)) + pv_forecast_d4: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_4|d4)) + # ─── Car charging ───────────────────────────────────────────────────────── + car_charging_energy: 're:(sensor.myenergi_zappi_[0-9a-z]+_charge_added_session|sensor.wallbox_portal_added_energy)' + num_cars: 1 + car_charging_planned: + - 're:(sensor.wallbox_portal_status_description|sensor.myenergi_zappi_[0-9a-z]+_plug_status)' + car_charging_planned_response: + - 'yes' + - 'on' + - 'true' + - 'connected' + - 'ev connected' + - 'charging' + - 'paused' + - 'waiting for car demand' + - 'waiting for ev' + - 'scheduled' + - 'enabled' + - 'latched' + - 'locked' + - 'plugged in' + - 'waiting' + #car_charging_now: + # - off + car_charging_now_response: + - 'yes' + - 'on' + - 'true' + #car_charging_battery_size: + # - 75 + #car_charging_limit: + # - 're:number.tsunami_charge_limit' + #car_charging_soc: + # - 're:sensor.tsunami_battery' + #car_charging_exclusive: + # - True + # ─── Octopus Energy ─────────────────────────────────────────────────────── + octopus_intelligent_slot: 're:(binary_sensor.octopus_energy([0-9a-z_]+|)_intelligent_dispatching)' + octopus_ready_time: 're:((select|time).octopus_energy_([0-9a-z_]+|)_intelligent_target_time)' + octopus_charge_limit: 're:(number.octopus_energy([0-9a-z_]+|)_intelligent_charge_target)' + #octopus_intelligent_slot: 'binary_sensor.ohme_slot_active' + #octopus_ready_time: 'time.ohme_target_time' + #octopus_charge_limit: 'number.ohme_target_percent' + #octopus_slot_low_rate: False + octopus_saving_session: 're:(event.octopus_energy([0-9a-z_]+|)_saving_session_event(s|))' + octopus_saving_session_octopoints_per_penny: 8 + octopus_free_session: 're:(event.octopus_energy_([0-9a-z_]+|)_octoplus_free_electricity_session_events)' + # octopus_free_url: 'http://octopus.energy/free-electricity' + # axle_api_key: "xxxxxxx" + # ─── Energy rates ───────────────────────────────────────────────────────── + metric_octopus_import: 're:(event.octopus_energy_electricity_[0-9a-z_]+(? Date: Thu, 20 Aug 2026 18:15:29 +0100 Subject: [PATCH 2/4] docs: add Hanchu iESS inverter setup sectio Added Hanchu iESS integration details and setup instructions. --- docs/inverter-setup.md | 168 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/docs/inverter-setup.md b/docs/inverter-setup.md index e9ba1b1e3..9a743f2c6 100644 --- a/docs/inverter-setup.md +++ b/docs/inverter-setup.md @@ -46,6 +46,7 @@ Once you get everything working please share the configuration as a GitHub issue | [Fox Cloud](#fox-cloud) | Predbat | [fox_cloud.yaml](https://raw.githubusercontent.com/springfall2008/batpred/refs/heads/main/templates/fox_cloud.yaml) | | [Fronius GEN24](#fronius-gen24) | [Fronius](https://www.home-assistant.io/integrations/fronius/) + [fronius-modbus-control](https://github.com/knackerbrot/fronius-modbus-control) | [fronius.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/fronius.yaml) | | [Growatt with Solar Assistant](#growatt-with-solar-assistant) | [Solar Assistant](https://solar-assistant.io/help/home-assistant/setup) | [spa.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/solar_assistant_growatt_spa.yaml) or [sph.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/solar_assistant_growatt_sph.yaml) | + | [Hanchu iESS](#hanchu-iess) | [hanchu-ess-ha](https://github.com/upton68/hanchu-ess-ha) | [hanchu_cloud.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/hanchu_cloud.yaml) | | [Huawei](#huawei) | [Huawei Solar](https://github.com/wlcrs/huawei_solar) | [huawei.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/huawei.yaml) | | [Kostal Plenticore](#kostal-plenticore) | [Kostal Plenticore](https://www.home-assistant.io/integrations/kostal_plenticore) | [kostal.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/kostal.yaml) | | [LuxPower](#luxpower) | [LuxPython](https://github.com/guybw/LuxPython_DEV) | [luxpower.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/luxpower.yaml) | @@ -559,6 +560,173 @@ You need to have a Solar Assistant installation Growatt has two popular series of inverters, SPA and SPH. Copy the template that matches your model from templates over the top of your `apps.yaml`, and edit inverter and battery settings as required. Yours may have different entity IDs on Home Assistant. +## Hanchu iESS + +The Hanchu iESS has no native Predbat integration. Control is implemented via Predbat's generic Service API: Predbat calls four service hooks (`charge_start_service`, `charge_stop_service`, `discharge_start_service`, `discharge_stop_service`), all of which point at a single Home Assistant script that writes the corresponding time slots directly to the device via `hanchuess.device_control`. + +Copy the template [hanchu_cloud.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/hanchu_cloud.yaml) over your `apps.yaml` and follow the steps below. + +### Prerequisites + +Install the [hanchu-ess-ha](https://github.com/upton68/hanchu-ess-ha) integration via HACS and configure it with your Hanchu cloud account credentials. Confirm that inverter and battery sensors are appearing in Home Assistant before proceeding. + +### Step 1 — Create helpers + +Create the following helpers in Home Assistant (Settings → Devices & Services → Helpers): + +**Toggle helpers** (toggle type): + +| Entity ID | Name | +| --------- | ---- | +| `input_boolean.predbat_charge_start` | Predbat Charge Start | +| `input_boolean.predbat_discharge_start` | Predbat Discharge Start | + +**Text helper** (text type): + +| Entity ID | Name | +| --------- | ---- | +| `input_text.hanchu_last_mode_action` | Hanchu Last Mode Action | + +`input_text.hanchu_last_mode_action` tracks the last mode successfully applied so the bridge script can skip a redundant API call when Predbat reasserts a state that is already active. + +### Step 2 — Create the bridge script + +All four of Predbat's service hooks call the same script, `script.hanchu_set_state_queued`, passing a `mode_action` field to indicate which state to apply. The script runs with `mode: queued` so if Predbat fires two calls close together — for example stopping a discharge and starting a charge in the same plan-evaluation cycle — Home Assistant queues the second call behind the first rather than letting both `device_control` calls race each other. + +Create a new script (Settings → Automations & Scenes → Scripts → Add Script → Edit in YAML) and paste the following, replacing `YOURSERIAL` with your device serial number as it appears in your HA entity IDs, and replacing `notify.notify` with your own mobile notification service: + +```yaml +alias: Hanchu Set State Queued +mode: queued +fields: + mode_action: + required: true + selector: + select: + options: + - charge_start + - charge_stop + - discharge_start + - discharge_stop +sequence: + - variables: + # mode_action is sometimes only populated under `data` rather than as a + # bare template variable, depending on whether the script is invoked from + # the HA UI or by a real service call from Predbat's AppDaemon dispatch. + # Check both so it works reliably either way. + act: >- + {% if mode_action is defined %}{{ mode_action }} + {% elif data is defined and data.mode_action is defined %}{{ data.mode_action }} + {% else %}unknown{% endif %} + - if: + - condition: template + value_template: "{{ act == states('input_text.hanchu_last_mode_action') }}" + then: + - stop: "No change — same action already applied, skipping API call" + - variables: + start_seconds: "{{ (now() - now().replace(hour=0, minute=0, second=0, microsecond=0)).seconds }}" + tct_start: "{{ start_seconds if act == 'charge_start' else 0 }}" + tct_end: "{{ 39600 if act == 'charge_start' else 0 }}" # 11:00:00 + tdt_start: "{{ start_seconds if act == 'discharge_start' else 0 }}" + tdt_end: "{{ 86340 if act == 'discharge_start' else 0 }}" # 23:59:00 + - action: hanchuess.device_control + data: + sn: YOURSERIAL + dev_type: "2" + value: + TCT_START_1: "{{ tct_start }}" + TCT_END_1: "{{ tct_end }}" + TDT_START_1: "{{ tdt_start }}" + TDT_END_1: "{{ tdt_end }}" + response_variable: result + - if: + - condition: template + value_template: "{{ not result.success }}" + then: + - delay: + seconds: 5 + - action: hanchuess.device_control + data: + sn: YOURSERIAL + dev_type: "2" + value: + TCT_START_1: "{{ tct_start }}" + TCT_END_1: "{{ tct_end }}" + TDT_START_1: "{{ tdt_start }}" + TDT_END_1: "{{ tdt_end }}" + response_variable: result2 + - if: + - condition: template + value_template: "{{ not result2.success }}" + then: + - action: notify.notify # Replace with your own notification service + data: + title: "⚠️ Hanchu {{ act }} FAILED" + message: >- + {{ act }} write failed after retry ({{ result2.message }}) + — check manually. + - stop: "Both attempts failed — leaving last_mode_action unchanged for retry" + - action: input_text.set_value + target: + entity_id: input_text.hanchu_last_mode_action + data: + value: "{{ act }}" + - choose: + - conditions: "{{ act == 'charge_start' }}" + sequence: + - action: input_boolean.turn_on + entity_id: input_boolean.predbat_charge_start + - conditions: "{{ act == 'charge_stop' }}" + sequence: + - action: input_boolean.turn_off + entity_id: input_boolean.predbat_charge_start + - conditions: "{{ act == 'discharge_start' }}" + sequence: + - action: input_boolean.turn_on + entity_id: input_boolean.predbat_discharge_start + - conditions: "{{ act == 'discharge_stop' }}" + sequence: + - action: input_boolean.turn_off + entity_id: input_boolean.predbat_discharge_start +``` + +The script always writes all four time slot fields (`TCT_START_1`, `TCT_END_1`, `TDT_START_1`, `TDT_END_1`) on every call, zeroing whichever pair is not the active mode. This keeps charge and discharge mutually exclusive on the device without relying on separate stop/start calls landing in the right order. + +### Step 3 — Add the soc_kw template sensor + +Predbat requires a `soc_kw` sensor reporting battery state of charge in kWh. Add the following to your `configuration.yaml`: + +```yaml +template: + - sensor: + - name: "Home Battery State of Charge kWh" + unique_id: home_battery_soc_kwh + unit_of_measurement: "kWh" + state_class: measurement + device_class: energy + state: > + {{ ((states('sensor.hanchuess_YOURSERIAL_battery_soc') | float(0)) / 100 * NN.NN) | round(2) }} +``` + +Replace `YOURSERIAL` with your device serial number and `NN.NN` with your total battery capacity in kWh (for example `18.80` for a dual 9.4 kWh system). Restart Home Assistant after adding this. + +### Step 4 — Configure apps.yaml + +- Replace `YOURSERIAL` throughout the template with your device serial number as it appears in your HA entity IDs +- Adjust `inverter_limit`, `inverter_limit_charge`, `inverter_limit_discharge`, `inverter_limit_export` and `battery_rate_max` to match your inverter and battery rated capacity in watts +- Delete the `template: True` line to allow Predbat to start +- Configure your energy rates — see [Energy Rates](https://springfall2008.github.io/batpred/energy-rates/) + +> **Note:** Double-check that `inverter_limit` is spelled exactly as shown — an accented character (for example `é` instead of `e` from autocorrect) will cause Predbat to silently ignore the setting and fall back to its own default. + +### Hanchu Notes + +- **Skipping redundant calls:** Predbat re-evaluates its plan on its normal cycle and can re-issue the same service call mid-window, simply reasserting the plan rather than changing anything. The `input_text.hanchu_last_mode_action` check skips the API call entirely when the requested mode is already the last one successfully applied. The tracker only updates after a confirmed successful write, so a failed attempt still retries correctly on the next cycle. +- **Behaviour on Predbat restart:** Whenever Predbat restarts it issues both `charge_stop_service` and `discharge_stop_service` in quick succession to put the inverter into a known neutral state. This is expected behaviour. The queued script handles this cleanly — if one of the calls matches the already-active state it is skipped as redundant; the other runs if it represents a real change. You may see one or both fire immediately after any restart. +- **Automation latency:** Start/stop commands are occasionally delayed by up to ~2 minutes due to HA scheduling. This has not caused any practical issues in production use. +- **No charge/discharge enable toggle:** Hanchu has no explicit enable/disable for charge or discharge. The slot zeroing mechanism (setting both start and end to `00:00:00`) is the disable method. +- **Min SOC:** Managed via `battery_min_soc` pointing directly to the Hanchu entity — no separate Predbat reserve setting needed. + ## Huawei Copy the Huawei template over your existing `apps.yaml` and modify all entity IDs, battery capacity and power limits for your own system: From 0093a1afbc968ad9bb5833836b41a0a9ee278cf8 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:13:41 +0100 Subject: [PATCH 3/4] Update prerequisites section for Hanchu iESS --- docs/inverter-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/inverter-setup.md b/docs/inverter-setup.md index 9a743f2c6..c3e73b7ad 100644 --- a/docs/inverter-setup.md +++ b/docs/inverter-setup.md @@ -566,7 +566,7 @@ The Hanchu iESS has no native Predbat integration. Control is implemented via Pr Copy the template [hanchu_cloud.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/hanchu_cloud.yaml) over your `apps.yaml` and follow the steps below. -### Prerequisites +### Hanchu iESS Prerequisites Install the [hanchu-ess-ha](https://github.com/upton68/hanchu-ess-ha) integration via HACS and configure it with your Hanchu cloud account credentials. Confirm that inverter and battery sensors are appearing in Home Assistant before proceeding. From b7399ab8ea8efc389595c8d7e5aacd2eec26bd87 Mon Sep 17 00:00:00 2001 From: upton68 Date: Sat, 22 Aug 2026 18:10:14 +0100 Subject: [PATCH 4/4] Add custom words to cspell dictionary --- .cspell/custom-dictionary-workspace.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index a3dba1958..bf2fb5f43 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -208,6 +208,9 @@ hadashboard hahistory hainterface halfhourly +Hanchu +hanchu +hanchuess hanres HAOS hasattr @@ -584,6 +587,7 @@ yaxis yaxistooltip yday ylabel +YOURSERIAL yuanzhi zappi zigbee