Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cspell/custom-dictionary-workspace.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ dlimit
dlopen
dnoregion
docstrings
dschg
dstamp
dstart
dtype
Expand Down Expand Up @@ -275,6 +276,8 @@ gridconsumptionpower
Groq
growatt
growattsph
guoxia
guoxiatech
GWMQTT
HACS
hadashboard
Expand Down Expand Up @@ -308,6 +311,7 @@ idag
idef
idetails
idxs
iess
iflux
importlimit
inday
Expand Down Expand Up @@ -612,6 +616,7 @@ slugified
slugifies
slugify
slugifying
smartflex
snakeviz
snprintf
socb
Expand Down
38 changes: 38 additions & 0 deletions apps/predbat/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,44 @@ def load_component_class(component_info):
"phase": 1,
"can_restart": True,
},
"hanchu": {
"class": "hanchu.HanchuAPI",
"name": "Hanchu ESS Cloud API",
"inverter": True,
"event_filter": "predbat_hanchu_",
"args": {
# The e-mail address and password the user signs into the Hanchu phone app with. There
# is no developer portal and no API key: the app account IS the credential.
"account": {"required": False, "secret": True, "config": "hanchu_account"},
"password": {"required": False, "secret": True, "config": "hanchu_password"},
"inverter_sn": {"required": False, "config": "hanchu_inverter_sn"},
"automatic": {"required": False, "default": False, "config": "hanchu_automatic"},
"automatic_ignore_pv": {"required": False, "default": False, "config": "hanchu_automatic_ignore_pv"},
# On by default, matching alphaess_control_enable and sunsynk_control_enable: an
# inverter component that does not drive the inverter is not what a user configuring it
# expects. Set false for monitoring only. switch.predbat_set_read_only still gates
# every write.
"control_enable": {"required": False, "default": True, "config": "hanchu_control_enable"},
# The escape hatch for the one genuinely inferred behaviour in this component: nothing
# documents which work mode honours the timed slots, so "User-defined" is a deduction.
# Set false to write the windows and leave the mode exactly as the user set it.
"work_mode_control": {"required": False, "default": True, "config": "hanchu_work_mode_control"},
# The API reports no battery power limit directly - it is taken from the charge power
# setting's own upper bound. This is the escape hatch for a user who knows their pack's
# real limit.
"battery_rate_max": {"required": False, "config": "hanchu_battery_rate_max"},
# Same idea for the inverter's AC rating, which is taken from the discharge power
# setting's upper bound. Hanchu units come in 3.68, 5, 6 and 10 kW.
"inverter_limit": {"required": False, "config": "hanchu_inverter_limit"},
"api_delay": {"required": False, "default": 1.0, "config": "hanchu_api_delay"},
"min_write_interval": {"required": False, "default": 60, "config": "hanchu_min_write_interval"},
},
# Gate activation on having an account. Without this the component would start for every
# instance, since all individual args are optional.
"required_or": ["account"],
"phase": 1,
"can_restart": True,
},
"enphase": {
"class": "enphase.EnphaseAPI",
"name": "Enphase API",
Expand Down
56 changes: 56 additions & 0 deletions apps/predbat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2359,6 +2359,51 @@
"charge_discharge_with_rate": False,
"target_soc_used_for_discharge": True,
},
"HanchuCloud": {
"name": "HanchuCloud",
"has_rest_api": False,
"has_mqtt_api": False,
# CHG_PWR_LMT / DSCHG_PWR_LMT are real watt setpoints, not an "unrestricted" flag, and a
# rate of ZERO is how Predbat signals freeze in both directions.
"output_charge_control": "power",
# Everything goes through the device's timed slot table. The one immediate primitive this
# API has (fastChargeDischarge) is duration-based and would lapse behind Predbat's back, so
# it is deliberately unused - see hanchu_const.py.
"charge_control_immediate": False,
"has_charge_enable_time": True,
"has_discharge_enable_time": True,
# DTU_AC_CHG_SOC_LMT, the grid-charge limit. NOTE it only accepts 20-100 on this hardware, so
# a charge target below 20% cannot be expressed at all and is clamped, with a warning, by
# the component. CHG_BAT_SOC_LMT also caps solar charging, so it is never written.
"has_target_soc": True,
# DSCHG_BAT_SOC_LMT, which accepts only 5-45 - the mirror-image limitation.
"has_reserve_soc": True,
# There is no pause operation anywhere in this API, so Predbat expresses freeze through the
# rate entities instead.
"has_timed_pause": False,
# Anything other than HH:MM:SS makes inverter.py replace the published select entities with
# its own dummies and the window never reaches the component. The wire format is seconds
# since midnight; the conversion happens at the payload boundary.
"charge_time_format": "HH:MM:SS",
"charge_time_entity_is_option": True,
"soc_units": "%",
"num_load_entities": 1,
"has_ge_inverter_mode": False,
"has_ge_eco_toggle": False,
"time_button_press": True,
"clock_time_format": "%Y-%m-%d %H:%M:%S",
"write_and_poll_sleep": 2,
"has_time_window": False,
"support_charge_freeze": True,
"support_discharge_freeze": True,
"has_idle_time": False,
# Wrap-around behaviour is undocumented for the TCT_/TDT_ slot pairs - as is everything
# else about this API - so Predbat splits the window and slot 2 carries the remainder
# rather than writing a window whose meaning is unknown.
"can_span_midnight": False,
"charge_discharge_with_rate": False,
"target_soc_used_for_discharge": True,
},
"SunsynkCloud": {
"name": "SunsynkCloud",
"has_rest_api": False,
Expand Down Expand Up @@ -2824,6 +2869,17 @@
"sunsynk_automatic_ignore_pv": {"type": "boolean"},
"sunsynk_control_enable": {"type": "boolean"},
"sunsynk_battery_nominal_voltage": {"type": "float"},
"hanchu_account": {"type": "string", "empty": False},
"hanchu_password": {"type": "string", "empty": False},
"hanchu_inverter_sn": {"type": "string|string_list", "empty": False},
"hanchu_automatic": {"type": "boolean"},
"hanchu_automatic_ignore_pv": {"type": "boolean"},
"hanchu_control_enable": {"type": "boolean"},
"hanchu_work_mode_control": {"type": "boolean"},
"hanchu_battery_rate_max": {"type": "float"},
"hanchu_inverter_limit": {"type": "float"},
"hanchu_api_delay": {"type": "float"},
"hanchu_min_write_interval": {"type": "integer"},
"alphaess_app_id": {"type": "string", "empty": False},
"alphaess_app_secret": {"type": "string", "empty": False},
"alphaess_inverter_sn": {"type": "string|string_list", "empty": False},
Expand Down
Loading
Loading