Hanchu Inverter Integrations

A quick aside (I’m feeling a little brave now…) My Solar isn’t via my Hanchu inverter but is AC coupled and is reported to the Hanchu via the second channel on the modbus meter.

So it’s not reported in the above - and I found the relevant name in the parallelPowerChart response and added this after the Solar Production sensor

  - name: Ext Solar Production
    unique_id: ext_solar_production
    value_template: "{{ value_json.data.bypMeterTotalPower | default(0)|abs }}"
    device_class: "power"
    unit_of_measurement: "W"
    state_class: "measurement"

And it works…

You’ll get hooked now.

Maybe. I think the most important problem to solve is automatically obtaining the payloads. Having to manually pull those from browser diagnostics and edit them in isn’t exactly end user friendly.

I really don’t understand* how the auth token is supposed to update monthly and then, assuming it does, how I can then use a browser session to recover other payloads - won’t I have to log in again and that will create a new token?

*Yeah, I know, I say that a lot.

There is only 1 payload that changes every month as I mentioned before. The reason for that is it is pulling that information from a monthly graph, which changes to a new month.

Ah, I hadn’t realised that. I thought you meant they all changed.

I’ve had a look at this & it appears that this call for battery temperature cannot be done through a straightforward REST call because there is an extra encryption somewhere that only makes the information available for a short period if you try to call outside Hanchu’s own web portal. To even try to duplicate it could involve cookies, additional headers, or a more complex auth flow.

1 Like

Thanks for having a look, I’ll just have to keep an eye on the portal. I managed to get that on an iframe card in HA, but it’s a bit clunky and takes over the monitor as it doesn’t seem to dynamically resize. There are some fudges out there for dynamic resizing but it’s all OTT to me lol
Thanks again :+1:

Automation to change the Payload for https://iess3.hanchuess.com/gateway/platform/pcs/historyStaticsChart on the first day of each month.

NOTE: the REST in CONFIG.YAML for historyStaticsChart remains as it is now. This process will overwrite it with 2 python (.PY) files and an AUTOMATION.

This is a 4 step process:-

STEP 1

1… Create a file in File Editor named hanchu_encrypt.py
2… Copy the contents of hanchu_encrypt.txt to this file.

#!/usr/bin/env python3
"""
Hanchu iESS Payload Encryptor
Generates AES-128-CBC encrypted payloads.

Key and IV: [YOUR SECRET KEY HERE]

Requires: pycryptodome
    pip install pycryptodome
"""

import base64
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

KEY = b"YOUR SECRET KEY HERE"
IV  = b"YOUR SECRET KEY HERE"

SN      = "YOUR INVERTER SERIAL NUMBER HERE"
DEVTYPE = "2"


def encrypt_raw(payload: dict) -> str:
    """Encrypt any dict payload as-is."""
    plaintext = json.dumps(payload, separators=(',', ':'))
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    ct = cipher.encrypt(pad(plaintext.encode('utf-8'), AES.block_size))
    return base64.b64encode(ct).decode('utf-8')


def encrypt(value: dict) -> str:
    """Encrypt an iotSet value dict wrapped in the standard structure."""
    return encrypt_raw({"devType": DEVTYPE, "value": value, "sn": SN})


def decrypt(b64: str) -> str:
    """Decrypt a base64 AES-CBC string back to plaintext."""
    ct = base64.b64decode(b64)
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return unpad(cipher.decrypt(ct), AES.block_size).decode('utf-8')


def to_seconds(t: str) -> int:
    """Convert HH:MM time string to seconds from midnight."""
    h, m = map(int, t.split(":"))
    return h * 3600 + m * 60

# ── Other endpoint payload builders ───────────────────────────────────────────

def history_statics_chart(year_month: str) -> str:
    """
    Generate payload for historyStaticsChart endpoint.
    year_month: 'YYYY-MM' e.g. '2026-03'
    """
    return encrypt_raw({
        "sn": SN,
        "devType": DEVTYPE,
        "maxCount": 1440,
        "dateStr": year_month,
        "masterSum": True
    })


# ── Main ───────────────────────────────────────────────────────────────────────
if __name__ == "__main__":


    print()
    print("=" * 70)
    print("HISTORY STATICS CHART PAYLOADS")
    print("=" * 70)
    import datetime
    now = datetime.datetime.now()
    this_month = now.strftime("%Y-%m")
    last_month = (now.replace(day=1) - datetime.timedelta(days=1)).strftime("%Y-%m")
    print(f"  This month ({this_month}): {history_statics_chart(this_month)}")
    print(f"  Last month ({last_month}): {history_statics_chart(last_month)}")

As you will see you will need to add the “Secret Key” & the serial number which is unique to your system.

The serial number is easy as you will find it on both the home pages of the app & console. It will show “SN: YOUR SERIAL NUMBER”.

The “SECRET KEY” will be found in dev tools when using the console.

Go to SOURCES
Tap/open “file-cdn.hanchuess.com
Tap/open “_/iess3/assets”
Tap/open “index-05281ed2.js”
In this file, search for “AES”. There should be 6 instances of this. It is when you come to the 6th instance you should see something like this:

 function fge(e, t) {
       typeof  e != “string”  && (e = JSON.stringify(e));
       const n = po.AES.encrypt(e, po.enc.Utf8.parse(t), {
               iv: po.enc.Utf8.parse (“YOUR SECRET KEY”),
               mode: po.mode.CBC
      });

Once both these are added save the file.

You can test output works by testing in terminal.

To test first load using:

pip install pycryptodome

Then test in terminal by typing:

python3 /config/hanchu_encrypt.py

If it produces the required output (it will print the current months & last months payload). Check the current month’s Payload with the Payload you have currently in the REST. They should match.

STEP 2

1… Create a file in File Editor named update_history_payload.py
2… Copy the contents of update_history_payload.txt to this file.

#!/usr/bin/env python3
"""
Run on the 1st of each month to update the historyStaticsChart payload
in configuration.yaml with the current month's encrypted payload.
"""
import sys
import datetime
import re

sys.path.insert(0, '/config')
from hanchu_encrypt import history_statics_chart

# Generate new payload for current month
new_payload = history_statics_chart(datetime.datetime.now().strftime('%Y-%m'))

# Read config
with open('/config/configuration.yaml', 'r') as f:
    lines = f.readlines()

# Find the historyStaticsChart resource line, then find the payload line after it
updated = False
in_history_block = False
for i, line in enumerate(lines):
    if 'historyStaticsChart' in line:
        in_history_block = True
    if in_history_block and line.strip().startswith('payload:'):
        lines[i] = line[:line.index('payload:')] + 'payload: "' + new_payload + '"\n'
        updated = True
        break

if not updated:
    print("ERROR: Pattern not found - payload not updated")
    sys.exit(1)

new_config = ''.join(lines)

# Write back
with open('/config/configuration.yaml', 'w') as f:
    f.write(new_config)

print(f"SUCCESS: Updated payload for {datetime.datetime.now().strftime('%Y-%m')}")
print(f"New payload: {new_payload}")

Nothing needs changing in this file.

STEP 3

You need to add to your CONFIG.YAML the following:

shell_command:
update_hanchu_history_payload: "python3 /config/update_history_payload.py”

DON’T FORGET THE INDENTATION.

STEP 4

AUTOMATION
I have named “Update Hanchu History Payload Monthly”

This will run at 5 minutes pass midnight on the 1st of each month and will update the historyStaticsChart Payload & restart CONFIG.YAML.

alias: Update Hanchu History Payload Monthly
description: >
  On the 1st of each month, regenerates the encrypted historyStaticsChart
  payload for the new month and reloads REST sensors.
triggers:
  - at: "00:05:00"
    trigger: time
conditions:
  - condition: template
    value_template: "{{ now().day == 1 }}"
actions:
  - action: shell_command.update_hanchu_history_payload
  - delay: "00:00:10"
  - action: rest.reload
mode: single

There you go. Good luck.

I’m interested in this but not sure where I get the JWT and AES keys from. Can you provide instructions on this please?

You will need to log in to the Hanchu Console via your browser (not the app), have access to your browser’s Development Tools function to be able to go “under the hood”. Can you access this?

1 Like

If you have a look at @BitcoinSmartthings post of January 29th he has a REST process to retrieve the Auth Token daily.

1 Like

The integration repo posted previously seems to have gone…

I had already forked a copy, so I’ve made it available here: https://github.com/Blustery7752/hanchu-ess

1 Like

I’ve made some changes and released v0.2.0, so it’s now much easier to configure - you just need to add your Username and Password when prompted - please let me know if this works well.
https://github.com/Blustery7752/hanchu-ess

Apolgies for delay, been fiddling around with it and got it working from the post mentioned.

Works brilliantly. I could probably do with some hand holding to work out how to pass a duration to the fast charge function, but I’m off to YouTube in the hope of finding an example!

Is what you’ve implemented the extent of the API or it is possible to set operation modes?

‘Actions’ (such as the fast charge) is generally called from Automations. If you add the Hanchu ESS: Fast charge/discharge to an automation then it should prompt you to select mode and optionally provide a duration.

There’s plenty of API still left to do, I’ve only done what I use so far. I’ll have a look at the operation modes next!

As mentioned in the issues on your repository, the reason the sensors don’t work is the hyphen in the Domain name and the component name.

Change those to an underscore and it’s fine.

Where have you gotten the api definition from? I’m sure others would like to help

Thanks - the PR to fix that has been merged, so v0.3.0/v0.3.1 resolved this issue.
The change of directory may confuse things in Home Assistant - I’d recommend uninstalling the old one and installing v0.3.1

I’m figuring out the API myself, I don’t have a full definition for it. Some of the endpoints are not striaghtforward, e.g. the Work Mode values are submitted as numbers, but different inverters re-use the numbers for different modes.

This all looks really cool. Im just waiting for my kit to be installed and will have a play too.

I read that you can connected locally to the unit via its access point. Has anyone played with that? With what happening to GivEnergy it be good to have none-cloud solution too!

Thank you @brownjl for the inspiration - I’d forgotten that the app had “local control”
Turns out this uses Bluetooth Low Energy, and I’ve managed to get basic data scraping working. I’ll be able to add more data and control, it’ll just take a while to work through all of the available values.

The integration is here: GitHub - Blustery7752/hanchu-ess-ble: A local Hanchu ESS integration for Home Assistant using BLE · GitHub
You’ll need a Bluetooth Proxy for Home Assistant, which I’ve done using an ESP32 and ESP home Bluetooth Proxy - ESPHome - Smart Home Made Simple