Skip to content

Commit af2d26e

Browse files
authored
docs: add Hanchu iESS inverter setup sectio
Added Hanchu iESS integration details and setup instructions.
1 parent c7e66f4 commit af2d26e

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

docs/inverter-setup.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Once you get everything working please share the configuration as a GitHub issue
4646
| [Fox Cloud](#fox-cloud) | Predbat | [fox_cloud.yaml](https://raw.githubusercontent.com/springfall2008/batpred/refs/heads/main/templates/fox_cloud.yaml) |
4747
| [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) |
4848
| [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) |
49+
| [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) |
4950
| [Huawei](#huawei) | [Huawei Solar](https://github.com/wlcrs/huawei_solar) | [huawei.yaml](https://raw.githubusercontent.com/springfall2008/batpred/main/templates/huawei.yaml) |
5051
| [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) |
5152
| [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 <https://solar-assistant.io>
559560

560561
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.
561562

563+
## Hanchu iESS
564+
565+
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`.
566+
567+
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.
568+
569+
### Prerequisites
570+
571+
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.
572+
573+
### Step 1 — Create helpers
574+
575+
Create the following helpers in Home Assistant (Settings → Devices & Services → Helpers):
576+
577+
**Toggle helpers** (toggle type):
578+
579+
| Entity ID | Name |
580+
| --------- | ---- |
581+
| `input_boolean.predbat_charge_start` | Predbat Charge Start |
582+
| `input_boolean.predbat_discharge_start` | Predbat Discharge Start |
583+
584+
**Text helper** (text type):
585+
586+
| Entity ID | Name |
587+
| --------- | ---- |
588+
| `input_text.hanchu_last_mode_action` | Hanchu Last Mode Action |
589+
590+
`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.
591+
592+
### Step 2 — Create the bridge script
593+
594+
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.
595+
596+
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:
597+
598+
```yaml
599+
alias: Hanchu Set State Queued
600+
mode: queued
601+
fields:
602+
mode_action:
603+
required: true
604+
selector:
605+
select:
606+
options:
607+
- charge_start
608+
- charge_stop
609+
- discharge_start
610+
- discharge_stop
611+
sequence:
612+
- variables:
613+
# mode_action is sometimes only populated under `data` rather than as a
614+
# bare template variable, depending on whether the script is invoked from
615+
# the HA UI or by a real service call from Predbat's AppDaemon dispatch.
616+
# Check both so it works reliably either way.
617+
act: >-
618+
{% if mode_action is defined %}{{ mode_action }}
619+
{% elif data is defined and data.mode_action is defined %}{{ data.mode_action }}
620+
{% else %}unknown{% endif %}
621+
- if:
622+
- condition: template
623+
value_template: "{{ act == states('input_text.hanchu_last_mode_action') }}"
624+
then:
625+
- stop: "No change — same action already applied, skipping API call"
626+
- variables:
627+
start_seconds: "{{ (now() - now().replace(hour=0, minute=0, second=0, microsecond=0)).seconds }}"
628+
tct_start: "{{ start_seconds if act == 'charge_start' else 0 }}"
629+
tct_end: "{{ 39600 if act == 'charge_start' else 0 }}" # 11:00:00
630+
tdt_start: "{{ start_seconds if act == 'discharge_start' else 0 }}"
631+
tdt_end: "{{ 86340 if act == 'discharge_start' else 0 }}" # 23:59:00
632+
- action: hanchuess.device_control
633+
data:
634+
sn: YOURSERIAL
635+
dev_type: "2"
636+
value:
637+
TCT_START_1: "{{ tct_start }}"
638+
TCT_END_1: "{{ tct_end }}"
639+
TDT_START_1: "{{ tdt_start }}"
640+
TDT_END_1: "{{ tdt_end }}"
641+
response_variable: result
642+
- if:
643+
- condition: template
644+
value_template: "{{ not result.success }}"
645+
then:
646+
- delay:
647+
seconds: 5
648+
- action: hanchuess.device_control
649+
data:
650+
sn: YOURSERIAL
651+
dev_type: "2"
652+
value:
653+
TCT_START_1: "{{ tct_start }}"
654+
TCT_END_1: "{{ tct_end }}"
655+
TDT_START_1: "{{ tdt_start }}"
656+
TDT_END_1: "{{ tdt_end }}"
657+
response_variable: result2
658+
- if:
659+
- condition: template
660+
value_template: "{{ not result2.success }}"
661+
then:
662+
- action: notify.notify # Replace with your own notification service
663+
data:
664+
title: "⚠️ Hanchu {{ act }} FAILED"
665+
message: >-
666+
{{ act }} write failed after retry ({{ result2.message }})
667+
— check manually.
668+
- stop: "Both attempts failed — leaving last_mode_action unchanged for retry"
669+
- action: input_text.set_value
670+
target:
671+
entity_id: input_text.hanchu_last_mode_action
672+
data:
673+
value: "{{ act }}"
674+
- choose:
675+
- conditions: "{{ act == 'charge_start' }}"
676+
sequence:
677+
- action: input_boolean.turn_on
678+
entity_id: input_boolean.predbat_charge_start
679+
- conditions: "{{ act == 'charge_stop' }}"
680+
sequence:
681+
- action: input_boolean.turn_off
682+
entity_id: input_boolean.predbat_charge_start
683+
- conditions: "{{ act == 'discharge_start' }}"
684+
sequence:
685+
- action: input_boolean.turn_on
686+
entity_id: input_boolean.predbat_discharge_start
687+
- conditions: "{{ act == 'discharge_stop' }}"
688+
sequence:
689+
- action: input_boolean.turn_off
690+
entity_id: input_boolean.predbat_discharge_start
691+
```
692+
693+
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.
694+
695+
### Step 3 — Add the soc_kw template sensor
696+
697+
Predbat requires a `soc_kw` sensor reporting battery state of charge in kWh. Add the following to your `configuration.yaml`:
698+
699+
```yaml
700+
template:
701+
- sensor:
702+
- name: "Home Battery State of Charge kWh"
703+
unique_id: home_battery_soc_kwh
704+
unit_of_measurement: "kWh"
705+
state_class: measurement
706+
device_class: energy
707+
state: >
708+
{{ ((states('sensor.hanchuess_YOURSERIAL_battery_soc') | float(0)) / 100 * NN.NN) | round(2) }}
709+
```
710+
711+
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.
712+
713+
### Step 4 — Configure apps.yaml
714+
715+
- Replace `YOURSERIAL` throughout the template with your device serial number as it appears in your HA entity IDs
716+
- 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
717+
- Delete the `template: True` line to allow Predbat to start
718+
- Configure your energy rates — see [Energy Rates](https://springfall2008.github.io/batpred/energy-rates/)
719+
720+
> **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.
721+
722+
### Hanchu Notes
723+
724+
- **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.
725+
- **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.
726+
- **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.
727+
- **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.
728+
- **Min SOC:** Managed via `battery_min_soc` pointing directly to the Hanchu entity — no separate Predbat reserve setting needed.
729+
562730
## Huawei
563731

564732
Copy the Huawei template over your existing `apps.yaml` and modify all entity IDs, battery capacity and power limits for your own system:

0 commit comments

Comments
 (0)