Skip to content

About

A local Hanchu ESS integration for Home Assistant using BLE

Resources

Stars

5 stars

Watchers

3 watching

Forks

 
 

Latest commit

 

History

116 Commits

Folders and files

Repository files navigation

Hanchu ESS BLE — Home Assistant Integration

A local Bluetooth (BLE) integration for Hanchu iESS battery storage systems, providing real-time sensor data and full write control of work mode, charge/discharge limits, SOC targets, and time slots — entirely over BLE, with no dependency on the Hanchu cloud.

⚠️ This integration is not supported by Hanchu. Use at your own risk. Incorrect settings may affect your inverter's operation. Always verify critical changes via the official Hanchu app.


Background

This integration is a fork of Blustery7752's hanchu-ess-ble, which provided the original BLE read-only foundation. Write control was developed by reverse-engineering the local BLE protocol with significant reference to 1ulk's Hanchu BLE Controller — a browser-based Web Bluetooth control panel whose hanchu-params.js parameter registry and hanchu-controller.js write implementation were invaluable in confirming the protocol structure and P/L-code mappings.


Features

  • Real-time sensors: Battery SOC, Battery Power, Battery Temperature, Battery Capacity, Grid Power, AC Coupled PV Power, Load Power (derived), Grid Frequency, L1 Voltage/Current, EPS sensors, and more
  • Individual battery pack monitoring: optionally poll each battery pack's own BLE logger directly for per-pack SoC, voltage, current, and temperature — each pack shown as its own device, independent of the inverter's aggregate battery readings
  • Work Mode control: switch between Self-consumption, Backup, User-defined, and Off-grid modes
  • Charge/Discharge Power Limits: set maximum charge and discharge power in Watts
  • SOC controls: Maximum Charge SOC, Minimum Discharge SOC (on-grid), Grid to Battery Charge Maximum
  • Time slot scheduling: full control of all six charge and discharge time periods (User-defined mode)
  • Tiered polling: real-time sensors update every 30 seconds; static values (firmware versions, hardware config) rotate through on a slower schedule, reducing BLE load
  • Fully local: no cloud dependency — works even if the Hanchu cloud is unavailable
  • Staged writes with explicit confirmation: changes to numeric limits, work mode, and time slots are staged locally and only sent to the device when you press Confirm Write — multiple changes can be queued and applied together in a single BLE connection, and Discard Changes cancels any staged edits without writing them

Requirements

  • A Hanchu iESS battery storage system with a compatible inverter
  • Home Assistant with Bluetooth support, or a Bluetooth proxy
  • A Bluetooth proxy is strongly recommended for reliable connectivity — onboard Bluetooth on HA hardware has been reported as unreliable with this integration across multiple setups. Tested with the M5Stack Atom Lite running ESPHome's Bluetooth proxy firmware, placed within range of the inverter (available from The Pi Hut for around £10). If you plan to use individual battery pack monitoring, the same proxy needs to be within range of the battery packs too — other Bluetooth sources have not been verified reliable for this integration, including for the inverter itself.

Installation

Via HACS (recommended)

  1. In HACS, go to Integrations → Custom repositories
  2. Add https://github.com/upton68/hanchu-ess-ble as an Integration
  3. Search for "Hanchu ESS BLE" and install
  4. Restart Home Assistant

Manual

  1. Copy the custom_components/hanchu_ess_ble folder into your HA config/custom_components/ directory
  2. Restart Home Assistant

Configuration

  1. Go to Settings → Devices & Services → Add Integration
  2. Search for Hanchu ESS BLE
  3. Select your inverter from the discovered Bluetooth devices (device name begins with HC:)
  4. The integration will connect and populate all entities automatically

Adding individual battery packs (optional)

Battery pack monitoring is off by default — the inverter's own aggregate battery sensors (Battery SoC, Battery Power, Battery Temperature) work without any extra setup.

To monitor individual battery packs directly:

  1. Go to Settings → Devices & Services, find the Hanchu ESS BLE integration
  2. Click Configure
  3. Select any discovered battery packs from the list (device names begin HC:L101)
  4. Save — each selected battery will appear as its own device with its own sensors

Battery packs poll on a 300-second interval, independently of the inverter's faster poll cycle. To add or remove batteries later, revisit Configure at any time.


Entities

Controls

Entity Description
Work Mode Select: Self-consumption, Backup, User-defined, Off-grid
Charge Power Limit Maximum battery charge power (W)
Discharge Power Limit Maximum battery discharge power (W)
Maximum Charge SOC Upper SOC limit — battery charges to this level then stops (%)
Minimum Discharge SOC On-grid hard floor — battery will not discharge below this level (%)
Grid to Battery Charge Maximum Upper SOC limit for grid-to-battery charging (%)
Charge Slot 1–3 Start/End Charge time periods for User-defined mode
Discharge Slot 1–3 Start/End Discharge time periods for User-defined mode
Confirm Write Applies all currently staged changes to the device in a single BLE connection
Discard Changes Cancels all currently staged changes without writing them to the device
Inverter Power Full power on/off for the inverter (grid relay + solar + battery + EPS) — see warning below

New in v1.2.0: Staged Writes & Confirmation

Previously, changing a number, select, or time entity wrote to the device immediately — each change triggered its own BLE connection. As of this release, changes are staged locally instead:

  • Change any control (a limit, work mode, or a time slot) and the new value shows immediately in Home Assistant, but nothing is sent to the device yet.
  • The Confirm Write button becomes available whenever there's at least one staged change. Pressing it opens a single BLE connection and writes every staged change in sequence, then disconnects.
  • The Discard Changes button clears all staged changes and reverts the affected entities to their last known device values, without writing anything.
  • If changes are left unconfirmed for 5 minutes, they're automatically discarded and the entities revert on their own.
  • While a Confirm Write is in progress, both buttons become unavailable until it completes, to prevent overlapping write attempts.

This groups related changes (e.g. a charge start and end time) into one BLE session instead of one per field, and gives an explicit review-and-apply step before anything reaches the device. If you have automations that relied on the previous immediate-write behaviour for these entities, they will need to call the Confirm Write button (or the equivalent service) after making changes.

Inverter Power Switch (P500)

⚠️ Read this before using the Inverter Power switch. Turning this Off does not just disconnect from the grid — it stops solar production, battery charge/discharge, and EPS/backup output all together, the same as flicking the physical AC isolator switch. If there's a grid outage while the inverter is off, there is no backup power for the house during that window.

Switching either direction takes roughly 50 seconds and several audible relay clicks to fully complete, during which the inverter's own front panel goes completely blank with no partial or intermediate indication — this is expected behaviour of the hardware itself (confirmed to match both the physical isolator switch and the official Hanchu app), not a fault in this integration.

While a command is in progress, the switch shows as unavailable with a "transitioning" icon, and will not accept another press until the change is confirmed or a 90-second safety timeout elapses — this prevents repeated presses from piling up during the transition.

Asymmetric confirmation timing: real-hardware testing found that P500 reports the commanded value almost as soon as it's accepted by the device — not once the change has actually finished physically completing. Turning Off is genuinely fast (a few seconds), but turning On was observed reading back as "on" roughly 20 seconds in, well before the inverter had visibly finished its ~50-second restart sequence. To avoid the switch misleadingly showing "On" while the inverter is still restarting, it deliberately holds the transitioning state for a minimum floor per direction (10s for Off, 45s for On) before trusting a polled value match, regardless of how quickly the register itself updates. These floors were tuned against one specific inverter/firmware combination and may need adjusting for others.

Strongly recommended: this switch writes immediately, unlike the staged number/select/time entities above — there is no Confirm Write step to catch an accidental tap. Add a confirmation prompt to its dashboard card so a tap can't trigger this by mistake:

type: tile
entity: switch.inverter_power
name: Inverter Power
icon: mdi:power
tap_action:
  action: toggle
  confirmation:
    text: >-
      This will fully power cycle the inverter — solar, battery, and EPS
      backup will all stop for up to a minute. Are you sure?

Note that this confirmation prompt is a Lovelace-level safeguard only — it protects against an accidental tap on the dashboard, but does not stop an automation or script from calling the underlying service directly. Treat this switch with the same care you'd give the physical isolator.

Sensors (enabled by default)

Entity Description
Battery SoC State of charge (%)
Battery Power Charge/discharge power — positive = charging, negative = discharging (W)
Battery Temperature Battery temperature (°C)
Battery Capacity Rated battery capacity (kWh)
Grid Power Grid import/export — positive = importing, negative = exporting (W)
AC Coupled PV Power AC-coupled solar generation (W)
Load Power Derived house load — Grid + AC PV + Battery (W)
Grid Frequency AC grid frequency (Hz)
L1 Voltage Grid line voltage (V)
L1 Current Grid line current (A)
EPS Voltage/Current/Frequency/Power Emergency power supply readings

Sensors (disabled by default)

Battery Voltage, Battery Current, Active Power, Reactive Power, Power Factor, Battery Charge Today, Battery Discharge Today, PV Energy Today/Total, EPS Energy Today/Total.

Battery pack sensors (per pack, opt-in via Configure)

Each configured battery pack appears as its own device with the following sensors:

Entity Description
Battery SoC State of charge, as reported directly by that pack (%)
Pack Voltage Total voltage of that pack (V)
Battery Temperature Cell temperature (°C)
Environmental Temperature Ambient temperature at the pack (°C)
PCBA Temperature Control board temperature (°C)
Battery Current Charge/discharge current for that pack (A)
Serial Number, Hardware Version, Model, Firmware Version Diagnostic identification fields

These are independent per-pack readings, distinct from the inverter's aggregate Battery SoC/Power/Temperature sensors — useful for spotting imbalance between packs on multi-battery systems.

New in v1.0.10: Firmware & RTC Diagnostics

Firmware version sensors (Main, Safety, ARM) are now categorised under Diagnostics. Three new diagnostic sensors have also been added to help verify the inverter's onboard clock:

  • Timezone (L020) — IANA timezone string reported by the DTU (e.g. Europe/London)
  • RTC Register L094 — the DTU's current Unix epoch time, exposed as a proper Home Assistant timestamp
  • RTC Daylight Saving Offset (L096) — DST offset in minutes (e.g. 60 during BST, 0 during GMT)

These are most useful for troubleshooting: if RTC Register L094 reads noticeably different from actual current time, it indicates the inverter's onboard clock has drifted, which could affect any scheduling that relies on the inverter's own internal clock rather than commands sent live from Home Assistant.


Services

In addition to the automatable entities above, this integration exposes a service for advanced use.

hanchu_ess_ble.confirm_write

Flush all currently staged changes to the device in a single BLE connection, returning proper success/failure feedback. This is the automation-facing equivalent of pressing the Confirm Write button — useful when an automation (e.g. a Predbat bridge script) needs to know definitively whether a batch of staged changes was actually written, rather than relying on a button press with no response.

This service takes no fields — it flushes whatever is currently staged for the configured device. If multiple Hanchu BLE devices are configured, it acts on whichever is found first.

Returns:

Field Description
success true if the confirm succeeded, or if there was nothing staged to confirm; false if the write failed
message Human-readable detail — e.g. "Confirmed", "Nothing staged to confirm", or the error if it failed

Example automation action:

- action: time.set_value
  target:
    entity_id: time.hc_yourdevice_charge_slot_1_start
  data:
    time: "06:00:00"
- action: hanchu_ess_ble.confirm_write
  response_variable: result
- if:
    - condition: template
      value_template: "{{ not result.success }}"
  then:
    - action: notify.mobile_app_yourphone
      data:
        message: "Hanchu BLE write failed: {{ result.message }}"

Predbat Integration

The Hanchu iESS also works with Predbat using this local BLE integration instead of the cloud one — useful if you'd rather not depend on cloud connectivity for live battery control, or if you're already using this integration for local monitoring and want the same connection driving Predbat too.

Predbat integrates with the BLE version the same way it does the cloud version — via Predbat's generic Service API — but the write side works differently, since BLE has no single-call equivalent to the cloud's iotSet/write_settings. Instead, a bridge script stages the affected charge/discharge slot 1 time entities (via time.set_value, the same entities documented above) and then calls hanchu_ess_ble.confirm_write to flush them together in one BLE connection.

At a high level, the pattern is:

  1. Predbat's charge_start_service/discharge_start_service/_stop_service hooks (in apps.yaml) point at a small bridge script.
  2. The script sets the relevant slot 1 time entities directly from Predbat's own time sensors, always zeroing the unused pair (charge or discharge) so slot 1 only ever reflects the one action Predbat currently intends.
  3. The script calls hanchu_ess_ble.confirm_write, retrying once on failure and notifying if both attempts fail.
  4. A separate automation watches Predbat's charge/discharge end-time sensors and re-stages+confirms the affected slot if the window changes mid-session (e.g. Predbat recalculating).

This mirrors the cloud integration's write_settings-based bridge pattern, adapted for BLE's per-register write model rather than a single batched API call. Full setup instructions — the helpers, the bridge script, the mid-window end-time automation, the daily-energy helpers and an apps.yaml template — are in docs/predbat-bridge.md. This is a community setup and is not part of the official Predbat documentation.

Note: unlike the cloud integration, the BLE integration writes directly to raw Hanchu register codes (L005/L006/L011/L012 for charge/discharge slot 1) rather than the cloud API's TCT_START_1/TDT_START_1-style field names — anyone adapting their own cloud-based Predbat automations should map by register/entity, not by cloud field name.


Polling

Registers are split into two tiers to reduce BLE load on the inverter:

Fast poll (every 30 seconds) — real-time values: power flows, battery SOC, voltage, current, temperature, grid readings, work mode, charge/discharge slots and limits.

Slow-Poll Rotation

Static or rarely-changing values (firmware versions, hardware configuration, RTC/timezone diagnostics) are polled one key per cycle on a rotating basis, rather than every cycle. With the default 30-second scan interval and 10 slow-poll keys, each individual slow-tier sensor refreshes approximately every 5 minutes. Slow poll values persist their last known reading between updates — sensors will not show as unavailable simply because a slow register was not included in the current cycle.

Startup behaviour — after a restart, slow-tier sensors will show as unavailable for the first few minutes while the rotation works through all seven slow keys for the first time (~3.5 minutes at the default interval). This is expected and not a sign of a problem.

Cycle duration variation — BLE read times can vary between cycles (typically 6–17 seconds has been observed). This is believed to be partly caused by the inverter handling cloud polling requests simultaneously, which occasionally delays its BLE response. Value persistence means this variation does not cause sensors to go unavailable.

Battery Pack Polling

Individual battery packs (if configured) poll on a separate, much slower 300-second interval, since SoC/voltage/temperature change gradually and don't need the inverter's faster cadence. Each battery reads all of its sensor values in a single BLE connection per cycle, rather than the tiered fast/slow approach used for the inverter. Battery polling failures follow the same consecutive-failure tolerance as the inverter, and are tracked independently per pack — one battery's BLE issues cannot affect another battery or the inverter.


Known Limitations

  • Daily grid import/export energy — not available over the local BLE protocol on tested hardware/firmware. The official app shows these figures, but the corresponding BLE keys were not found to return data despite exhaustive testing.
  • AC Coupled PV Power — the sign convention of P237 varies between hardware and firmware versions. Some systems return a positive value when generating, others return negative. The raw sensor reflects whatever the inverter reports. The derived Load Power sensor uses the absolute value of P237 so it calculates correctly regardless of which convention your system uses.
  • Fast charge/discharge — not available locally. This feature appears to be cloud-only on tested hardware.
  • DC-coupled PV sensors (PV1/PV2 Voltage/Current, PV Total Power) — will read zero on AC-coupled systems where solar is connected via a separate inverter rather than directly into the Hanchu's DC inputs.
  • This integration is not supported by Hanchu. It was developed independently by reverse-engineering the local BLE protocol.
  • Battery pack visibility — battery packs must be within range of, and previously discovered by, your Bluetooth proxy before they'll appear as selectable options in Configure. If a battery isn't listed, check it's showing as a discovered device under Settings → Devices & Services → Bluetooth before trying again.
  • Staged writes are not device-level atomic — when Confirm Write applies multiple staged changes, each is still sent as its own write within the single BLE connection, one after another. There's a brief window where an earlier change has been applied and a later one hasn't yet. If a write in the middle of a batch fails, the changes already applied are not rolled back, and the remaining staged changes stay pending for retry.
  • Inverter Power switch is not staged — unlike the number/select/time entities, it writes immediately and is not part of the Confirm Write/Discard Changes flow. A dashboard confirmation prompt is strongly recommended (see above) since there is no other safeguard against an accidental tap.
  • Confirm Write is global per device, not per-edit — if an automation triggers Confirm Write (or calls the underlying service) while you have an unrelated change staged on the dashboard but haven't pressed Confirm yourself, your staged change will be applied too, and the buttons will show as unavailable immediately afterward. This is expected given the buffer is shared per device rather than scoped per user or session. In practice this requires a manual edit and an automation-triggered confirm to overlap within the same short window, which is unlikely in typical single-user home use, but worth being aware of if you have automations that call Confirm Write directly.

Protocol Notes

The Hanchu iESS communicates over BLE using AES-128-CFB8 encryption with a per-session key derived from a handshake token. Reads use act: "1" and writes use act: "3" in a JSON payload sent to the write characteristic (0000ff02-...), with responses arriving via notifications on the read characteristic (0000ff01-...).

For a comprehensive mapping of all known P/L-codes, see the protocol mapping reference included in this repository.


Credits

  • Blustery7752 — original hanchu-ess-ble Home Assistant integration, which provided the BLE connection, encryption, and read foundation this fork builds on
  • 1ulk — browser-based Hanchu BLE Controller, whose hanchu-params.js parameter registry and hanchu-controller.js write implementation were essential references for the write protocol and P/L-code mappings
  • PaulDGAL — real-world testing and community feedback that identified BLE load as the root cause of sensor unavailability issues, and proposed the tiered polling approach and the sensor value persistence improvement that became v1.0.2. Also carried out the initial investigation and hands-on hardware testing of the inverter power switch (P500) — including confirming its behaviour and full transition timing on real hardware — which the Inverter Power Switch feature is built on.

Disclaimer

This is an independent community project with no affiliation with or support from Hanchu. Use at your own risk. The authors accept no liability for any damage to equipment or loss of functionality resulting from use of this integration.

About

A local Hanchu ESS integration for Home Assistant using BLE

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages