sunenergyxt500 adapter for ioBroker
Integration and self-consumption control for SunEnergyXT 500 / 500 PRO AC-coupled hybrid battery storage systems (manufacturer: SunEnergyXT) via the device's local HTTP API — no cloud account required. One instance manages up to three heads (storage towers).
Language / Sprache
Features
- Manages one to three heads in a single instance, each under its own subtree
heads.<n>.*, plus combinedtotal.*aggregates. - Polls the local API (
GET /read) and mirrors all stable fields to states: SoC, battery/grid/load/PV power, per-MPPT current/voltage, daily energy counters, per-pack SoC, device/firmware info and meter status. - Writable control fields (
POST /write, confirmed by re-reading), matching the official integration's control surface except fields the API docs mark as reserved: grid setpointGS, inverter output limitIS, SoC limitsSI/SA/SO, self-consumption modeMM, meter configMD, timezoneTZ, restartRT, max grid outputMG, theLFB/LPS/PMswitches and local modeLM(⚠️LM=1blocks cloud/app control until reset). SoC hysteresisSI1/SA1(documented as writable by the manufacturer, default 5% each, accepted range 0…100 %) keep their establishedheads.<n>.battery.*ids so existing recordings stay intact. Fields the API docs still mark as reserved (e.g.PT) are exposed read-only. - Two switchable control modes: an adapter-side self-consumption controller (writes
GSfrom any ioBroker meter state, feed-forward + P, with watchdog/failsafe) that splits one grid setpoint across all heads, or device self-regulation (binds a supported meter into a single storage and lets the device control itself) — plus an off mode for pure monitoring. - A "Test all heads" button in the admin checks reachability of each configured head (model + SoC) before you save.
- Connection indicator (
info.connection) plusinfo.lastUpdate, and per-headonline/lastError. - The complete, unmodified
/readresponse of each head is kept inheads.<n>.info.rawResponse(JSON), so any field the adapter does not map to a dedicated state can still be read from there.
How this adapter works
This adapter controls the storage locally, without the manufacturer cloud. A single instance manages one to three heads (storage towers). Self-consumption can be handled in two mutually exclusive ways — you pick one via the Control mode setting:
Mode B — Adapter controller (default-recommended, works with any meter, 1–3 heads). ioBroker reads the current grid power from any state you point it at (gridPowerStateId) and the adapter writes the grid setpoint GS (feed-forward + P correction, with a watchdog). The meter can be anything ioBroker supports — Shelly, Tasmota, a smart-meter/Modbus adapter — including meters the storage cannot read itself. You provide one state holding the net grid power in watts (>0 = draw, <0 = feed-in; enable invert source sign if reversed; for kW / split import-export / per-phase meters compute a clean net value in a small ioBroker state first). With more than one head, the controller computes one total setpoint and splits it across the online heads — equally, capped at each head's grid-tied output limit (MG) when discharging and at its charge limit when charging, and skipping a head that is full (while charging) or empty (while discharging); its share is redistributed to the others. The adapter forces MM=0 on every head so the devices execute GS, and the meter stays fully usable in ioBroker.
Mode A — Device self-regulation (supported meters, single head only). The adapter binds a supported meter into the storage (MM=1 + MD) and lets the device regulate itself — the manufacturer's own self-consumption, which may react faster than an external loop. This mode is available only with a single head; with two or three heads configured it cannot be selected — use the adapter controller instead. Only four meter types are supported (EcoTracker, Shelly 3EM, Shelly Pro 3EM, Tasmota) and the meter must be reachable by the storage on the LAN. The adapter does not write GS in this mode. The binding is just mDNS/HTTP polling, so the meter stays usable in ioBroker — unlike the manufacturer app's meter setup, which can reconfigure the meter and remove it from ioBroker; this adapter binds directly and avoids that.
Off (default, monitoring only). The adapter never writes MM/MD/GS; it only polls. You can still command control.* states manually.
In both control modes the adapter owns MM: on every poll it checks each head's MM against the chosen mode and re-asserts it (with a warning) if something else changed it — so a stray meter binding or an external script cannot silently disable control. Note: a head only executes a written GS when MM=0; with a meter bound (MM=1) it self-regulates and ignores GS.
Multiple heads must be on different phases. This is the operator's electrical responsibility — the adapter does not (and cannot) verify it. The controller regulates the net (summed) grid power your meter reports, which is what a standard netting German bidirectional meter bills; per-phase optimisation is out of scope.
Local mode (LM=1) is required. Each device only serves its local HTTP API (/read / /write) when local mode is enabled — with it off, /read returns no data (confirmed on the tested firmware). Enabling local mode also switches off cloud/app remote control; consequently the manufacturer's phone app can no longer control the device.
Requirements
- One to three SunEnergyXT 500 (
PK=1, 800 W) or 500 PRO (PK=2, 2400 W) heads reachable in the local network (mixed models are fine). - Local mode (
LM=1) enabled on each device — required for the local HTTP API to deliver values (see How this adapter works). This also disables cloud/app remote control. - A meter, depending on the control mode: for Mode B (adapter controller) any meter whose grid power is available as an ioBroker state; for Mode A (device self-regulation, single head) one of the four supported meters (EcoTracker, Shelly 3EM, Shelly Pro 3EM, Tasmota) reachable by the storage on the LAN. Not needed in Off mode.
Installation
- In ioBroker admin open Adapters, search for sunenergyxt500 and install it.
- After installation an instance
sunenergyxt500.0is created. Open its settings and enter the Head 1 IP / hostname (add Head 2 / Head 3 if you have more heads). Leave the Control mode at Off for pure monitoring. - Save & close — the adapter starts polling and fills the object tree under
sunenergyxt500.0.heads.*(andtotal.*).
Configuration
Connection
- Head 1 IP / hostname (required) and Head 2 / Head 3 (optional) — local addresses of your storage heads, each with an optional label. Up to three heads are managed by this single instance. Put multiple heads on different phases (operator's responsibility); the adapter regulates the net summed grid power. The same address cannot be entered twice.
- Test all heads — probes each configured head and reports model + SoC (or an error), so you can verify the addresses before saving.
- Poll interval (s) — how often each head is queried via
/read(default 5 s). - Request timeout (ms) — HTTP timeout (default 8000 ms).
- Restore settings after a head restart (default on) — see Settings after a head restart below.
Control — pick a Control mode:
Off (default) — monitoring only; the adapter never writes MM/MD/GS.
Adapter controller (Mode B) — fields:
- Grid-power source state — a foreign state holding your house meter's grid power. Convention:
>0= grid draw,<0= feed-in. Enable Invert source sign if your meter uses the opposite convention. - Adaptive control (default on): regulates in three manufacturer-proven tiers — small deviations gently (every 7 s, 20 W steps), medium ones every 2.5 s (120 W), large load steps immediately (450 W), with a fixed 5 W grid dead band. Note that the per-head write dead band (10 W by default) applies on top: a correction smaller than that is not written unless the total setpoint moved by at least as much, so in practice deviations below roughly 10 W are left alone. Disable adaptive control to tune the controller manually via the gain / dead band / write interval / step-limit fields (they only appear then).
- Target grid power (W, default 0): 0 = zero feed-in; positive values keep a small deliberate grid draw (never feed in), negative values a small deliberate feed-in — same sign convention as the source state (
>0= draw). - Discharge floor of the controller (%, default 0 = off): the controller stops discharging a head once its emptiest pack reaches this value, while the head's own floor (
SI) stays lower. The head then never reaches its own discharge cut-off, where firmware 1.1.5 can lock up and refuse to charge until it is disconnected from the grid. Measured on the emptiest pack because the packs of a head drift apart; discharging resumes once the charge has risen by the head's hysteresis (SI1). Controller mode only. - Max. adjustment per correction (W, default 500, 0 = unlimited): caps how far the setpoint moves per control step, so a high gain cannot overshoot on meter spikes.
- Gain (default 0.3), Dead band (W), Min. write interval (ms), Per-head write dead band (W — minimum change of a head's setpoint before it is re-written, to avoid chatter as the split shifts). Each head's maximum power is detected automatically from the device (800 W for a 500, 2400 W for a 500 PRO), so mixed setups work without extra configuration.
- Meter settling time (ms) (default 0) — readings taken before the last setpoint write are always discarded, because they still describe the state before it. Keep 0 for meters whose value follows the physical change immediately; raise it slightly above the measured content delay for meters that publish fresh timestamps while the value still lags.
- Also steer the inverter limit (IS) (default off) — in addition to
GS, the controller sets the maximum inverter output: the discharge part ofGSplus whatever the load port draws, capped to the current PV power once a head has reached its discharge floor. Only useful when a load is wired to the load port; without one it just adds writes. On failsafe and on adapter shutdown the limit is handed back to the head's maximum on a best-effort basis (within the shutdown budget, and only to heads that answer). A hard kill of the process cannot do this — see the limitations below. - Watchdog warn / failsafe (s) — if the grid source goes stale, the controller logs a warning and finally forces
GS=0on every reachable head (safe neutral) until the source recovers. Watchdog telemetry is exposed undercontroller.*.
The controller works from the setpoint it last commanded, not from a fresh device read. The polled grid power (GP) acts as a correction: when a device visibly does not follow its setpoint for more than 10 s (internal limiting by SoC or temperature), the reported value is adopted as the new feed-forward base — anti-windup without an extra read per cycle. It also compares the GS echoed by each device against what it commanded and warns when they differ — that means a second control path (the vendor app, or another automation) is writing GS as well. Writes to different heads go out concurrently, so within a cycle one slow head does not delay the others. The next cycle still waits for the slowest head to answer, which is why regulation writes use a shorter deadline (2.5 s) than the configured request timeout.
Device self-regulation (Mode A, single head only) — fields:
- Meter type — EcoTracker / Shelly 3EM / Shelly Pro 3EM / Tasmota.
- Meter SN / IP — the serial number for Shelly/Tasmota (resolved via mDNS), or the LAN IP for EcoTracker (direct). For Tasmota use the SN prefix without the last 4 characters and set the power key matching your energy-monitor subtype.
The adapter binds the meter (MM=1 + MD) and the device regulates itself; the adapter does not write GS. The bound meter stays usable in ioBroker. This mode is hidden/blocked once a second or third head is configured.
Safety: In Off mode the adapter is read-only — it only polls
/readand never writes unless you command acontrol.*state. In a control mode the adapter enforcesMMon every head for that mode and re-asserts it if changed externally; do not run a secondGSwriter at the same time (your own script, or a device'sMMwith a different meter), otherwise they fight over the battery.
Control behaviour, accuracy and limits
What to expect: The controller keeps the grid power inside a band of typically ±10–20 W around zero and corrects load steps — depending on the settings — within ~1–3 seconds up to ~30 seconds. A permanent, exact 0.0 W is not achievable by design — with any controller on this hardware:
- Meter accuracy and noise: the external meter itself measures with a tolerance and noise of a few watts — regulating below that is meaningless. (The
GSsetpoint has 1 W resolution, so the setpoint granularity is not the limit.) - Measurement-chain latency: meter measures → ioBroker state → controller →
/write→ device ramps. Between a load step and its correction, ~1–3 seconds pass unavoidably. - Load dynamics: A compressor or kettle switches in milliseconds — every controller reacts afterwards. Short power spikes in your charts are normal and energetically meaningless (watt-seconds).
- The controller regulates towards the configured target grid power (default 0) and oscillates symmetrically around it — short, small feed-in moments are part of zero feed-in. If you must never feed in, set the target to a small deliberate draw (e.g. +10 W).
How the settings act:
| Setting | Effect | smaller value | larger value |
|---|---|---|---|
| Gain | fraction of the deviation corrected per step | sluggish, smooth (0.3 ≈ 7 steps to ~0) | fast (1.0 = one correction), but reacts harder to meter noise; >1 can overshoot |
| Target grid power (W) | the value the grid power is regulated to | <0 = deliberate feed-in | >0 = deliberate draw ("never feed in") |
| Max. adjustment per correction (W) | caps the setpoint movement per step | tames meter spikes at high gain | larger (or 0 = unlimited) reacts faster to huge load steps |
| Dead band (W) | deviations below it are ignored | more precise, more writes (0 = correct everything) | calmer, leaves a small permanent deviation |
| Min. write interval | cadence of corrections | faster settling (floor 1000 ms) | fewer device writes, slower tracking |
| Per-head write dead band | suppresses mini-redistributions between heads (multi-head) | more precise | less chatter |
Adaptive control (the default) picks its pace automatically per tier. For the manual mode, two proven profiles: Relaxed (the defaults — calm, minimal device writes, ±20–30 W band) and Precise (gain 0.8–1.0 · dead band 0 · interval 1000 ms · write dead band 0 — ±10–20 W band, settling in 1–3 s). Over a day both end up with practically the same energy balance — the difference is chart cosmetics, not money.
Sign conventions
GP(grid power):>0= feed-in,<0= draw — opposite to a Shelly meter (api.GP ≈ −shelly.gridPower).BP(battery power):>0= charging,<0= discharging.GS(grid setpoint):>0= feed-in/discharge,<0= grid charging (1 W resolution). The bound is not symmetric on a standard 500: it may feed in up to 800 W but still draw up to 2400 W, becauseMGcaps the output only.
Object tree
Each head gets its own subtree under heads.<n>.* (n = 1…3), plus combined total.* aggregates and adapter-level controller.* / info.*. Within a head, states are grouped into thematic channels; the leaf of each object id is the device's API field code (the entity id from the official field reference), and the bilingual object name describes it — so the tree maps 1:1 to the device's documented fields.
| Channel | Contents |
|---|---|
heads.<n>.battery.* | SoC (SC), battery power (BP), per-pack SoC (SC0–SC5), online packs (ON), SoC hysteresis (SI1/SA1, writable) |
heads.<n>.grid.* | grid power (GP), daily charge/feed-in energy (GD1/GD2) |
heads.<n>.load.* | load power (LP), daily off-grid load energy (LD) |
heads.<n>.pv.* | total PV (PV), daily PV generation energy (PD) and per-MPPT power/current/voltage (mppt1–mppt4) |
heads.<n>.system.* | total input/output power (IW/OP) |
heads.<n>.device.* | type/model/serial/status; network.* (IP, port, Wi-Fi); firmware.* (ES/AS/DS software, EH/AH/DH hardware, BS0–BS5 BMS) |
heads.<n>.meter.* | external meter status (MS) |
heads.<n>.ups.* | UPS mode / grid-charge / bypass (UO/UG/FP) |
heads.<n>.fault.* | fault bitmasks (TF/EF/DF1/DF2/AF1/AF2/BF) — only populated while a fault is active |
heads.<n>.control.* | all writable fields (see below) |
heads.<n>.info.* | per-head online, lastError, rawResponse (the full raw /read), desiredSettings (the settings kept across a head restart, JSON) |
total.* | combined view: capacity-weighted soc, summed batteryPower / gridPower / maxPower, onlineCount |
controller.* | self-consumption controller telemetry (status, grid-source age) |
info.* | connection (any head reachable) and lastUpdate |
Writable controls (heads.<n>.control.*)
By ioBroker convention all writable fields live under each head's control.*. Because that flattens their topic, this table shows what each one relates to:
| Object | Field | Relates to | Description |
|---|---|---|---|
control.GS | GS | grid | Grid power setpoint (>0 feed-in / <0 grid charge) |
control.IS | IS | grid | Inverter output limit (1…2400 W on both models) |
control.MG | MG | grid | Max. grid-tied output power (1…800 W on a 500, 1…2400 W on a 500 PRO) |
control.SI | SI | battery | Min. discharge SoC (grid mode) |
control.SA | SA | battery | Max. charge SoC (grid mode) |
control.SO | SO | battery | Min. discharge SoC (off-grid mode) |
control.MM | MM | mode | Local zero feed-in / self-consumption mode (coupled with MD) |
control.MD | MD | meter | Meter connection JSON (coupled with MM) |
control.LM | LM | mode | Local mode (⚠️ 1 blocks cloud/app control) |
control.LFB | LFB | mode | Load priority switch |
control.LPS | LPS | mode | Off-grid output switch |
control.PM | PM | mode | Parallel mode |
control.TZ | TZ | device | POSIX timezone |
control.RT | RT | device | Restart device (button — a soft restart, not a full power-cycle) |
Tip: in ioBroker admin you can also filter the object list by the writable flag to find all controls at once.
device.PK is derived from DevType on firmware that no longer reports PK. SI1/SA1 are writable (SoC hysteresis, default 5%); fields still marked reserved (PT) are exposed read-only. Fields the manufacturer dropped (UP) or that are doc-only artefacts (WT, BN) are not exposed; anything unmapped is still available in heads.<n>.info.rawResponse.
Settings after a head restart
Firmware 1.1.5 (reported as ES 1.1.15) does not keep values written through the local API: after a restart the head comes back with an older stored set, and it also falls back to that set on its own at night (observed at 03:00, without a restart). With local mode on, the manufacturer app cannot change them either, so a setting made through this adapter would silently fall back and stay there.
The adapter therefore remembers what you set through it — SI, SA, SO, SI1, SA1, IS, MG and the switches LFB, LPS, PM — in heads.<n>.info.desiredSettings, and writes a value again whenever the head reports a different one (logged at info level). A head that keeps refusing a value is retried more slowly and reported as a warning after three attempts. GS, MM and MD are handled by the control mode, RT is a trigger, LM is never forced, and TZ is left out because the head may echo it in a different form than it was written.
Only values set after updating to a version with this feature are remembered, so set them once through the adapter. On firmware that keeps its settings nothing ever differs and nothing is written. Turn the option off if another tool manages these settings.
Manual meter / mode fields (MM / MD)
MM/MD are a head's own meter-based self-consumption. When you select a Control mode, the adapter manages them for you (Mode A sets MM=1 + MD on the single head; Mode B forces MM=0 on every head), and its guard re-asserts the mode-appropriate MM on the next poll — so any manual change in a control mode is temporary.
The raw fields stay writable for expert/manual use (e.g. in Off mode). They follow the official coupling: turning MM off also clears MD, and writing MD enables MM (non-empty) or disables it (empty). The MD JSON formats for the four supported meters are in the device's local API reference; in Device self-regulation mode the adapter builds them for you from the meter type and SN/IP.
Limitations
- Up to three heads per instance. Single-head operation is validated on real hardware and two-head operation was confirmed by a tester; the three-head split is covered by unit tests but untested on real hardware — feedback from multi-head setups is very welcome. Device self-regulation is single-head only.
- Heads must be on different phases (operator's responsibility). The adapter regulates the net summed grid power, not per phase.
- Per-pack balancing is handled by each head's own BMS — the adapter steers the head's overall power only and reads
battery.SC(total) for control; it does not manage individual packs. - Daily energy counters (
PD/GD1/GD2/LD) are raw Wh, not kWh.PDrequires control module firmwareES 1.1.14(marketed as "1.1.4" — the public numbering differs from the internal one inES); older firmware simply omits the field and the state stays empty. - Daily counters are reset by the device on reboot, so a firmware update mid-day drops them back to 0.
MDandTZtake effect immediately but are not guaranteed to be echoed back verbatim by the device — confirm by effect, not by echo.- A hard failure of the ioBroker host leaves the last setpoint running. The heads have no setpoint timeout of their own: whatever
GSwas written last keeps being executed until a SoC limit stops it. A normal stop, restart or mode change is handled — the adapter neutralises the heads and, if that does not succeed, remembers it (info.gsOwned) and finishes the job on the next start. A power cut or a killed process cannot be covered this way. - PV inputs are untested with hardware (the reference installation runs without PV modules, so
PV1–4are always 0). The integration and controller are PV-agnostic and complete, but PV firmware edge cases (e.g. battery full + PV surplus, UPS/bypass fieldsFP/UG) are unverified — feedback welcome. Per-MPPT current and voltage are read as tenths and scaled by 0.1, matching the manufacturer's own integration; if a real PV setup shows them a factor of ten out, that is the place to look.
Troubleshooting
info.connectionstaysfalse/ no data: first make sure local mode (LM=1) is enabled on the device — without it the local API returns no values. Then verify thathttp://<device-ip>/readis reachable from the ioBroker host (test with a browser orcurl). Per head,heads.<n>.info.onlineandheads.<n>.info.lastErrorshow which one fails.- Nothing is being controlled: check the Control mode — Off never writes. In Adapter controller set a valid grid-power source state; in Device self-regulation set a supported meter type and SN/IP.
- Device ignores
GS/ battery does not react: a head only executes a writtenGSwhenMM=0. In Adapter controller mode the adapter enforces this; if you writeGSmanually, make sure no meter is bound (MM=0). With a meter bound (MM=1) the device self-regulates and ignoresGS. - The controller is too slow / never reaches exactly 0: see Control behaviour, accuracy and limits — the measurement chain adds ~1–3 s of latency and the meter itself has finite accuracy, so a band of ±10–20 W around the target is the physical optimum. For the fastest response use the Precise profile (gain 0.8–1.0, dead band 0, min. write interval 1000 ms); to never feed in, set the target grid power to a small positive draw.
- State timestamps look old / quality flag 32: the adapter only writes a state when its value changes (standard practice — it protects the states DB from millions of identical writes). A state's timestamp therefore shows the last value change, not the last poll. Check data freshness via
info.lastUpdate(updated on every successful poll) andheads.<n>.info.online. Quality 32 ("substitute initial value") only remains on states the device never delivers (e.g. the SoC of expansion packs that are not installed); after every adapter start all delivered values are written once, so their timestamps are at least as fresh as the start. - The storage does nothing at all — it neither charges nor discharges: in controller mode the adapter switches the device's own regulation (
MM) off, so if its own loop never actuates, nothing regulates. Readcontroller.gridPower: it stays empty when no value ever reached the controller (check the state id, and that the source is written withack=true— unacknowledged values are ignored on purpose;controller.statusgoes tofailsafeand the log names the state). If it shows a positive value while the house is really feeding in, the sign convention is inverted — enable grid power inverted. If it is negative andcontroller.totalTargetis negative too, the controller is asking for charge and the device is refusing: checkheads.<n>.device.ST(2 = running) andheads.<n>.battery.ON. - Two controllers fight over the battery: run only one. The adapter enforces
MMfor the selected mode — disable any externalGSscript (or a device's ownMMwith a different meter) before using a control mode. - Some states stay empty (
0/""): a device only returns the fields its firmware/topology actually provides (e.g. extra packsSC2–SC5, or fault bitmasks only during a fault). The complete raw response is always available inheads.<n>.info.rawResponse. - After updating from a single-head version the tree looks wrong: the object tree was restructured to
heads.<n>.*in 0.2.0. The adapter removes obsolete objects automatically on start; if anything lingers, delete the old objects (or re-add the instance). - Heads drop out sporadically / ping timeouts: the head's Wi-Fi module is weak, and stacking the units puts a metal case right over the antenna. Check
heads.<n>.device.network.WR(signal strength in dB) — below −75 dB the link becomes unreliable. Separate stacked units and raise the poll interval to 10–15 s (control quality barely suffers: the controller reacts to the grid-power source, not to this poll). To rule the adapter out, stop the instance and ping the head for a few minutes — if the drop-outs continue, polling is not the cause. The adapter itself sends one/readper head and interval, staggers multiple heads, closes every connection after use and backs off automatically after failed polls.
Changelog
0.3.4 (2026-09-28)
- (Creekhail) New: a discharge floor kept by the controller itself. Firmware 1.1.5 can lock up at the moment a head cuts off discharging at its own floor (
SI): the status LED turns red, the grid LED blinks, and the head refuses to charge — even from its own PV inputs — until it is disconnected from the grid; a restart over the API does not reliably clear it. The problem occurs with floors of 5, 10 and 30 % and without this adapter as well. With the new option the controller stops asking a head to discharge once its emptiest pack reaches the set value, above the head's own floor, so the head never reaches the point where it locks up. Measured on the emptiest pack because the packs of a head drift apart. Off by default; controller mode only. - (Creekhail) Shorter help texts in the instance settings. Each field now shows one short line; the full explanation appears when you hover over the field, and a link under it opens the matching section of this README, so the details are reachable on a phone or tablet as well. The introductions above the sections are shortened too.
0.3.3 (2026-09-27)
- (Creekhail) Settings survive a head restart again. Firmware 1.1.5 does not keep values written through the local API: after a restart the head comes back with an older stored set, and with local mode on nothing else can change them. Found on a 500 PRO when a discharge floor raised to 10 % was back at 5 % after a restart; a test showed the same for the off-grid floor and the self-consumption mode. The head also falls back to that older set on its own at night, without a restart: at 03:00 it switched self-consumption mode off while running, which in device self-regulation would have ended the regulation for the rest of the night. The adapter now remembers the settings you make through it (SoC limits and hysteresis, inverter and grid output limits, the load-port switches) in
heads.<n>.info.desiredSettingsand writes them again when the head reports something else. On by default, with an option to turn it off; values set before this version are not known to the adapter, so set them once again. Firmware that keeps its settings sees no extra writes.
0.3.2 (2026-09-25)
- (Creekhail) Fixed: after a short head restart the controller could stop writing altogether. A head that reboots comes back with
GS=0, and if it answers again before three polls have failed, it never leaves the control loop. The controller still believed its last setpoint was in place, and as long as its target held still — steady surplus, or a head refusing charge with the anti-windup pulling it back to the same step — nothing counted as due, so the storage sat idle in full sun until the load changed or the instance was restarted. AGSecho that differs from the commanded value now marks the head for a resend, whether a restart or a second writer caused it. The foreign value is still never adopted; the controller puts its own back. A zero echo is logged as a probable restart rather than blamed on a second controller.
0.3.1 (2026-09-21)
- (Creekhail) Fixed a blocker in 0.3.0: the instance configuration could not be saved. Heads 2 and 3 were marked invalid whatever was in them — including the empty fields a single-head installation leaves — and because those fields refuse to save while invalid, the whole settings page was stuck. The cause was a validator expression that ioBroker admin evaluated as a statement instead of a condition; it now returns its result explicitly. 0.2.10 was not affected.
0.3.0 (2026-09-21)
- (Creekhail) The heads' own SoC hysteresis is respected. A head that has reached its discharge floor (
SI) does not accept power again until its charge has risen bySI1above it, and the same applies at the top withSA/SA1. Without that, the controller kept commanding a head that was refusing — a stream of writes every few seconds, a setpoint that never took effect, and the other heads carrying a share that was silently missing. The share of a head that is held back is redistributed across the ones that can take it. The block is tracked from the reported charge alone, so it is set when a head reaches a limit and lifted when its charge leaves the band, whether or not the controller happens to be asking for power at that moment. A head whose charge is already inside a band when the adapter starts is assumed to be held there until it leaves it: from outside there is no way to tell whether the device has released it, and waiting costs a few percent of one battery while guessing wrong costs a continuous stream of writes the device ignores. - (Creekhail) Fixed: a head with a lowered feed-in limit is no longer throttled when charging or supplying its own load.
MGcaps what a head puts into the grid; it says nothing about what it may draw or what its inverter may produce for the load port — the device takes up to 2400 W in either of those regardless. UsingMGfor all three left a 500, or a PRO whose owner had capped feed-in at 800 W, charging at a third of its rate with the surplus going to the grid, and unable to cover a load larger than its export cap. - (Creekhail) A setpoint left on a head is not forgotten across a restart. A head has no setpoint timeout of its own: whatever
GSwas written last keeps being executed until something says otherwise, so a crash or a power cut could leave one charging or discharging at full power with nothing watching it. The adapter records that it has set one (info.gsOwned) before the first write goes out, neutralises the heads on the next start, and only clears the record once every head has confirmed — retrying from each head's own poll until it does. - (Creekhail) Optional: the controller can steer the inverter limit (
IS) alongside the grid setpoint — the discharge part ofGSplus whatever the load port draws, capped to the PV power once the head has reached its discharge floor. Off by default; it is only useful with a load wired to the load port. It has its own write dead band, and the limit is handed back to the device maximum on failsafe and shutdown, so a stopped controller never leaves the inverter throttled. - (Creekhail) New option "meter settling time" for meters that publish a fresh timestamp while the value still lags behind the physical change. Readings taken before the last setpoint write were already discarded — they describe the state before it — and this extends that window. A source whose clock runs behind this host no longer freezes the loop either: regulation falls back to one correction per 30 s and the log says why.
- (Creekhail) Field names and ranges checked against the manufacturer's own Home Assistant integration. Every field it exposes is covered here, with the same meaning and unit; the only deliberate differences are the daily energy counters (raw
Whas the API delivers them, where the integration converts to kWh) and the SoC limitsSI/SA/SO, which stay at 1…100 % because the API calls its narrower figures recommended values, not device limits. The SoC hysteresisSI1/SA1now accepts 0…100 % as it does there — a cap of 50 % here had been refusing settings the device accepts. - (Creekhail) Manual writes to the control states are range-checked. A typo in a script (
GS=99999,SI=150) no longer reaches the hardware. The ranges follow the device:ISis 1…2400 W on both models, while the two grid-tied output fields (GS,MG) drop to 800 W on a standard 500 as soon as a poll reports the model, just as the manufacturer's own integration does. What a head draws and what its inverter may produce stay at 2400 W on both. - (Creekhail) Two states renamed for accuracy — the object ids are unchanged, so history and existing scripts keep working.
control.ISwas called "Max. grid feed-in / inverter output limit", which conflated the two limits the device keeps apart: it is the inverter output limit, whileMGcaps the grid feed-in.total.maxPowerwas "Total available power", but it sumsMGand therefore says what the plant may put out, not what it may take in — now "Total grid-tied output limit". - (Creekhail) All 57 Tasmota / BitShake meter subtypes are selectable. Thirteen were missing — every one of them a subtype whose power key is a term (
power_in - power_out,(Pp - Pm) * 1000) rather than a plain field name, so meters like Siemens, eHZ, Metcom or AS1440 could not be configured for device self-regulation at all. The device evaluates the term itself; the adapter passes it through. Meter model names are also shown untranslated again in every language — the machine translation had turnedQ3Ainto "Pytanie 3A" andeBZinto a chemical, so the entry a user was looking for was not findable. - (Creekhail)
controller.statusreports one thing at a time:failsafewhenever anything is in failsafe,warnwhile a warning stands,okotherwise. A head that answers but delivers no usable SoC data is neutralised and reported instead of being regulated on placeholder limits, and if no head is regulatable at all the controller goes to failsafe rather than looking healthy. - (Creekhail) Fixed: selecting device self-regulation with more than one head no longer clears a meter binding. That combination is refused and the adapter falls back to monitoring — but it used to release the binding on the way, switching off the device's own regulation without anything taking over.
- (Creekhail) Heads are addressed the way you typed them, but compared normalised:
192.168.1.5,192.168.1.5/andhttp://192.168.1.5are recognised as one device. - (Creekhail) Robustness: an HTTP response that errors or closes mid-body can no longer leave a request hanging forever; a control cycle that throws is logged instead of taking the adapter down; requests to one head are serialised and stop at shutdown.
0.2.10 (2026-08-03)
- (Creekhail) Gentler on the heads' Wi-Fi: with two or three heads the adapter no longer polls them all in the same instant — each head now runs its own cycle, staggered by up to 1 s. After a failed poll that head is backed off (doubling up to 60 s) instead of being polled at full rate, which only added to the congestion that made it drop out. The other heads keep their own rhythm either way.
- (Creekhail) Every HTTP connection is closed after use (
Connection: close, a dedicated connection pool per head). Node's default keeps a socket open for 5 s, which at a 5 s poll means permanently — a slot the head's ESP32 cannot reclaim if the close is lost on a weak link. Reads and writes to the same head are now serialized as well, so a control write no longer opens a second parallel connection while a poll is running, and all sockets are closed on unload. - (Creekhail) The request timeout now covers the whole request instead of only the phase after a socket was assigned.
- (Creekhail) Admin: the poll interval carries a help text recommending 10–15 s for two or more heads or a weak Wi-Fi signal; new troubleshooting entry for sporadic head drop-outs.
0.2.9 (2026-08-02)
- (Creekhail) The controller now publishes what it actually sees:
controller.gridPoweris the house grid power the way the loop understands it (after the inverted option,>0= draw) and is updated for every accepted value — even while the dead band suppresses any write, so "no value arrives" and "values arrive but nothing happens" are finally distinguishable.controller.totalTargetshows the total setpoint before it is split across the heads; a negative value means the controller wants to charge, so a storage that stays idle anyway is a device-side problem. - (Creekhail) Fixed a silent standstill: if the configured grid-power source never delivered a single value (wrong state id, or a state written with
ack=false), the controller sat atGS=0forever without any warning — while control mode had already switched the device's own regulation (MM) off. Nothing regulated at all, and the status still readok. The watchdog now escalates tofailsafeafter the configured failsafe time and logs the state id together with what to check. - (Creekhail) A grid-power source that is written with
ack=falseis still ignored (an unacknowledged value must not drive the battery), but the adapter now warns once, names the state and points at both remedies instead of dropping every value silently. - (Creekhail) Renamed
total.gridPowerto "Storage grid-port power, total" — it sums the storages' grid ports and was too easily mistaken for the house connection. The object id is unchanged, so history and existing scripts keep working.
0.2.8 (2026-07-29)
- (Creekhail) Support for control module firmware
ES 1.1.14(marketed by the manufacturer as "1.1.4"): the reinstated fieldPDis now exposed asheads.<n>.pv.PD— today's PV generation energy in raw Wh. Verified against a 500 PRO before and after the update: no field was removed and no control value was reset, so older firmware keeps working unchanged — the state simply stays empty where the device does not deliverPD. - (Creekhail) Documentation: noted that the device's public firmware numbering differs from the internal one reported in
ES(public1.1.4= internal1.1.14) — feature availability is therefore detected by field presence, never by comparing version strings; and that the daily counters (PD/GD1/GD2/LD) are reset by the device on reboot, so a firmware update mid-day drops them to 0.
0.2.7 (2026-07-05)
- (Creekhail) New adaptive control (enabled by default): the controller regulates in three manufacturer-proven tiers — small deviations gently (every 7 s, 20 W steps), medium ones every 2.5 s (120 W), large load steps immediately (450 W), with a fixed 5 W dead band. Disable the new checkbox to keep tuning gain, dead band, write interval and step limit manually. Existing installations are switched to adaptive by this update (uncheck to return to your manual tuning).
- (Creekhail) Admin: help texts for gain, dead band and min. write interval; requires admin >= 7.8.23; CHANGELOG_OLD.md added and the Dependabot auto-merge workflow aligned with the canonical template (iobroker-bot).
0.2.6 (2026-07-04)
- (Creekhail) Controller: new target grid power (deliberate small draw or feed-in — e.g. +10 W to never feed in; the manufacturer's HA blueprint offers the same option) and a maximum adjustment per correction step (default 500 W) that allows a high gain without overshoot on meter spikes.
- (Creekhail) Documentation: corrected the setpoint-granularity statement — the device accepts
GSwith 1 W resolution (previously documented as 10 W steps); the practical accuracy limits are measurement-chain latency and meter noise.
0.2.5 (2026-07-02)
- (Creekhail) Review follow-up: added the manufacturer/product link to the README, and the controller now only accepts acknowledged grid-power values (
ack=true) from the configured meter state (a manually written test value can no longer drive the battery).
0.2.4 (2026-07-02)
- (Creekhail) State names are now provided in all ioBroker languages: English and German stay hand-written, the other languages are machine-translated (generated file, merged at object creation; existing installations are updated in place).
0.2.3 (2026-07-02)
- (Creekhail) Object-tree compliance for the repository review:
- The
headscontainer is now a folder so the hierarchy follows the required device→channel→state order. - The switch states
control.MM/LM/LFB/LPS/PMare now real booleans (previously 0/1 numbers) — adjust scripts that read or write them; the device still receives 0/1. - Corrected roles:
GS/IS/MGuselevel,ups.UOusesvalue. - Object definitions are merged onto existing objects on start, so role/type/name updates reach existing installations (user settings like history configs are preserved).
- The
0.2.2 (2026-07-02)
- (Creekhail) Repository-checker compliance: complete admin translations in all languages (including the validator messages), release notes translated into all languages, removed an unknown jsonConfig property from the test button and use the adapter-managed timer during unload.
0.2.1 (2026-07-01)
- (Creekhail) Controller robustness and hardening:
- Config values of 0 are respected (gain / dead bands) instead of silently becoming defaults.
- A failing GS write on one head no longer aborts the setpoints of the other heads.
- Anti-windup: when a device visibly limits internally (SoC/temperature), the controller adopts the reported grid power as its feed-forward base.
- GS is neutralized to 0 on all reachable heads when the controller stops (adapter stop/restart or leaving controller mode), so no head keeps executing a stale setpoint unwatched.
- Heads are polled in parallel (one unreachable head no longer stretches the poll cycle); fewer state-DB reads per poll.
- The startup object cleanup only touches adapter-managed subtrees; user-created states in the namespace survive.
- Missing MG falls back to the model's power limit (500 → 800 W) instead of assuming a PRO; device responses are size-capped.
- Manual GS writes are rejected (with a warning) while the controller is active; failsafe no longer retries offline heads every tick.
- New unit tests for the controller (regulation, dead bands, throttling, failsafe, anti-windup).
0.2.0 (2026-06-30)
- (Creekhail) Multi-head support: manage up to three SunEnergyXT heads in one instance. The adapter controller now computes one grid setpoint and splits it across all online heads (equal split, gated by each head's SoC headroom, with per-head power caps and overflow redistribution). New per-head object tree
heads.<n>.*and combinedtotal.*aggregates; a "Test all heads" connectivity button; device self-regulation is restricted to a single head. The object tree was restructured — existing single-head instances should be re-created (delete the old objects / re-add the instance).
0.1.1 (2026-06-29)
- (Creekhail) Released via npm trusted publishing (provenance) and a package metadata fix.
0.1.0 (2026-06-28)
- (Creekhail) Initial release: local-API polling to states; writable control fields; two switchable control modes — an adapter-side self-consumption controller (any ioBroker meter state, feed-forward + P, with watchdog/failsafe) and device self-regulation (binds a supported meter: EcoTracker / Shelly 3EM / Shelly Pro 3EM / Tasmota); plus a monitoring-only mode, with an MM-mode guard.
License
MIT License
Copyright (c) 2026 Marcus Bortel (Creekhail) marcus@bortel.de
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.