Haverland Radiators Smart Box Integration

Hi everyone, I bought 4 Haverland WI9 rads, but I can’t find the smartbox in stock (I looked over the EU). Is there any other compatible smartbox or any other solution without a smartbox to control the rads remotely? Thank you (if you have an extra smartbox to sell…I’m interested :smiley: )

When the Home Assistant server restarts, the smartbox’s consumption meter continues to show exorbitant data, usually negative, which makes it impossible to know the cost of the energy consumed daily. When adding the meter consumption to the energy panel it already shows the error that the data is negative. And no matter how much you adjust the values ​​in developer tools, when you reboot again, they become corrupted again. I think it may be related to the data download mode in the integration settings, since when restarting it adjusts to the default value, which is “auto”, even if you had the “start” option selected.

I solve it with some workaround. I edit this part

STEP_USER_DATA_SCHEMA = vol.Schema( { vol.Required(CONF_API_NAME): SelectSelector( SelectSelectorConfig( options=[ SelectOptionDict(value=key, label=reseller.name) for key, reseller in AvailableResellers.resellers.items() ], mode=SelectSelectorMode.DROPDOWN, ) ), **LOGIN_DATA_SCHEMA, }, )

in /custom_components/smartbox/config_flow.py to

STEP_USER_DATA_SCHEMA = vol.Schema( { vol.Required(CONF_API_NAME): str, **LOGIN_DATA_SCHEMA, }, )

then u change the dropdown menu to textfield. 2. part is edit this python libary smartbox file : /smartbox/reseller.py
there u need to add the api for lhz :

"api-lhz": SmartboxReseller( name="Lucht LHZ", web_url="https://smartcontrol.lucht-lhz.de/", api_url="api-lhz", basic_auth="NTU2ZDc0MWI3OGUzYmU5YjU2NjA3NTQ4OnZkaXZkaQ==", serial_id=2, ),

after that u can type vendor api-lhz, username + password and it works.

simpler solution at the moment for lucht lhz is to change

/homeassistant/custom_components/smartbox/manifest.json

this line to

“requirements”: [“smartbox==2.5.0”],

the new smartbox supports LHZ out of the box, but the smartbox-hass doesn’t pull the newest version by default.

It seems that with the latest update the smartbox integration can't load anymore :frowning: Let's hope there will be a quick solution to this.

This is what is says: Unable to install package smartbox>=2.3.2,<2.4.0: × No solution found when resolving dependencies: ╰─▶ Because smartbox==2.3.2 depends on pydantic==2.12.2 and pydantic==2.13.2, we can conclude that smartbox==2.3.2 cannot be used. And because only the following versions of smartbox are available: smartbox<=2.3.2 smartbox>=2.4.0 and you require smartbox>=2.3.2,<2.4.0, we can conclude that your requirements are unsatisfiable.

@ajtudela — ping from jandado. Your hass-smartbox fork is from my repo; thank you for keeping the integration alive and usable.

I had recurring reliability problems: climate entity going stale, losing control of heaters, having to restart Home Assistant. I wanted to find the cause for a long time and never had the hours. With agentic coding I finally did — a few days of steering the work rather than writing every line by hand.

The aim was to make control stick, and to get closer to what the original smartbox web app already exposes.

What’s in the tree now:

  • Climate stays in sync after rapid off→heat→off. Manual setpoints in auto (modified_auto) stick. Celsius targets snap to the 0.5 °C grid.
  • Dedicated gateway device for the box. Away, power-limit, connectivity and a new RTC clock-drift sensor sit on it. Old per-node registry entries are re-homed; entity IDs stay the same.
  • Firmware 1.9 setup controls: radiator priority, away offset / max temperature, and the programme Frost–Eco–Comfort temperatures. Climate max temp is clamped by the device limit.
  • smartbox.set_schedule: write the heater’s own weekly programme from HA (days Mon–Sun, time slots: frost / eco / comfort). Goes with the Frost–Eco–Comfort temperature entities so the schedule and its setpoints can both be managed in HA.
  • Child lock is a real switch (switch.<node>\_lock), not a read-only binary sensor.
  • German added; Spanish and French completed.
  • Integration reload fixed

Some of this needed matching work on the underlying smartbox library as well.

Integration: GitHub - jandado/hass-smartbox: Home Assistant integration for heating smartboxes · GitHub

I only have htr devices, so there may be some unhandled cases for others, but I am happy to address them as/when they are reported.

Happy to open PRs into your forks or help you merge. I do not want people to steer away from your repository and I would be happy if that remains the main source of this integration for everyone. Unless you no longer feel like maintaining it. Let me know - I am finally able to help.

Wow, there are a lot of changes there! Last month, I started implementing new fixes and features in the Smartbox library (PR here: Improvements/fix bugs by ajtudela · Pull Request #64 · ajtudela/smartbox · GitHub), and will eventually update the Hass-Smartbox version.

I’ll take a look at your changes and try to include them in my repository.

Ultimately, I would like to try to incorporate HASS-Smartbox into the core but I don’t know if it’s possible.

I was looking at the repositories before I started the work - the issues, PRs - to make sure I would either take them into account or address them. But I have completely missed your other branch!

I am in the middle of another important change right now (spent almost whole day on it) - device availability. I was trying to replicate the web-app behavior in case one of the devices is “lost” (e.g. power is cut), and the smart box detects it. I do not know the low-level chatter between the box and devices, but at some point the API pushes a lost sync status. Whereas if there is a command that is sent to the device and the device does not respond, there is only a few seconds timeout and lack of response is considered as device not available. I am basically reverse-engineering the web-app.

When I am done with it, I will push it to my repos and then I will try to look at your pending work and retro-fit into my set - so that none of the work is lost (unless superseded). Just give me a day or two.

As for the ultimate goal - it certainly is possible. I think it depends mostly on two things:

  1. integration quality,
  2. individual / team behind the integration - being capable to maintain it as core introduces changes.

I can imagine we get to the point where 1 is not an issue. I don’t know about 2 - time will tell? It sure has become much easier!

No rush, I’m very busy this week. So, I’ll look at them next week.

About reverse-engineering the app, do you think the new features must be included in the hass version or in the layer below: smartbox (python library)?

Good. I am in no hurry - I want to get it done right rather than quick.

The lost device detection (which I am working on right now) has changes in both. But most of what I did (separate box device, individual temperature settings, set_program, child lock switch) were done in the integration only.

Why?

@ajtudela — closing the loop on both threads from yesterday.

1. The device-availability work is done and released — smartbox v2.6.1 (superseded by v2.6.2, see below). The UpdateManager now derives per-node availability from the wire (sync_status: "lost" frames on /status) and confirms writes against a follow-up GET, so a heater that loses power turns Unavailable instead of showing stale state until restart. One observation: with no traffic at all, the server kept serving the last-known ok status for ~10 minutes before flipping to a bare {"sync_status": "lost"}. How quickly the box itself can detect silence is unknown to us — the box↔heater radio protocol is — so treat that number as anecdotal. The write-up is in api-notes.md in the smartbox repo.

2. I went through your PR #64 one by one — all 19 commits, intent-level, since both trees rewrote the same files. Everything is recorded per commit with evidence in ajtudela-pr-64-audit.md in my repo. The short version:

  • 10 of the 19 were already fixed by my 2.6.x round — independent convergence, and your diagnosis was right every time (frozen get_node_samples defaults, ClientSession ownership, HTTP timeouts, reconnect-log noise, token/basic-auth masking, CLI errors and typing, …).
  • 7 I adopted (each of my commits references your source hash): the set_node_setup unknown-key wipe (#1 — great catch on counter_offset; raw read + extra="allow" on the setup models), the common SmartboxError root (#5, keeping our deliberate 403 divergence), the network marker (#6), dependency pinning (#9), BadParameter for unknown -d/-n (#13), the .vscode cleanup (#17), and CODEOWNERS/README (#19).
  • 1 declined, rationale in the doc (+ the 2.5.3 version bump): the catch-all DefaultNodeStatus fallback (#7) — we keep typed-mode rejection via SmartboxValidationError with the wire payload attached; the bare {"sync_status": "lost"} frame is first-class in our design, and silently-None fields would hide the firmware drift we keep meeting in the field.

All of the above is released as smartbox v2.6.2 in my repo. Practical upshot for you: everything your PR delivers is already in my tree — the doc tells you per commit what we already had, what we took, and why.

And to reassure the “why?” behind your layering question: the split is deliberate and held to — wire-shaped stuff goes to the library, HA behavior stays in the integration. Your PR was exactly in the right place; nothing of mine drifted across that line as far as I can tell.

The only thing where my hass-smartbox diverges is pinning smartbox to my repo, not the PyPI.

Today I finally spent the time to dig where this warning is coming from.
WARNING (MainThread) [engineio.client] Server has stopped communicating, aborting

…and the bad news is - it is our integration. api.helki.com has upgraded their API over time (and we did not notice?) to what I will call ws_user. They still support socket_io, but the backend does not send ping messages (in conflict with socket_io spec), causing our end to to show this warning and disconnect / reconnect momentarily. This happens when there is no message on the socket for 85 seconds.

Now the good news - I have started the work to use ws_user - when I am done both will be supported, transparently for the user.

And another good news is the REST API is unchanged.

Good catch!

I think we didn’t notice because the integration is sitll working. At least, mine is.

@dadoj I have another branch ready to merge if you want to take a look: GitHub - ajtudela/smartbox at improvements/env-v2 · GitHub

It’s me again. I review your commits and sync them in the main branch. I made a new release of the 2.6.2 version in Pypi.

Yes, and it will most likely work for a while - the Android app still uses it (at least one of them), and not all reseller portals (I am aware of one web-app that is still using the old API) have migrated to the new API. But it’s better to know and act before they shut it down on us… I’m on it.

Superb! Thank you.

Review of improvements/env-v2 — all good, one bug found and fixed

Full review of the six commits, done before the release below: all clean and self-contained, verified on my side (fresh worktree at f6030f6, Python 3.14): full suite 313 passed, ruff/mypy clean, coverage 95.52% against your new 95% floor, and the integration’s contract tests pass 8/8. One correctness bug in the .env parser: inline # comments got swallowed into values (silently corrupting credentials written in the conventional quoted style). The fix is pushed as commit e4cbf33 on my main — feel free to cherry-pick it into env-v2.


ws_user transport — released, live-verified, ready for your read

The bigger news since: the engineio finding from earlier in this thread is fixed and released. api.helki.com now serves a per-user websocket endpoint (ws_user) alongside the legacy per-device socket_io — the old path’s 85-second silence cycles (Server has stopped communicating) come from the backend no longer sending socket_io pings. The new transport speaks plain websocket with server-driven closes at token expiry.

  • Library v2.7.0 — WsUserSocketSession: one connection serves every heater of the account; supervision loop with backoff, mandatory fresh-token reconnect at the 4-hour token expiry; check_ws_user_support() probe + WsUserUnsupportedError and the fallback doctrine (only deterministic rejections demote; transient trouble retries, never demotes); UpdateManager gains ws_user_socket=; shared retry/backoff for the REST side; README rewritten (library usage, transports, commands, .env).
  • Integration v2.6.0 — transparent wiring: ws_user preferred, socket_io fallback with an INFO log, per-entry reload limiting, diagnostics expose transport + ws_user_run_finished. No user-visible config.

Live soak, ~5 h on my production box (5 Haverland heaters): transport confirmed ws_user via diagnostics; the 4-hour token-expiry reconnect succeeded in ~3 s with a fresh lease; writes round-trip (heat/off/auto/target-temp); the 85 s engineio pattern is gone; a write to a dead heater goes Unavailable at the ~6 s confirm mark (recovery 3–5 s after power-on); and — closing the open question from my availability post — with zero traffic the server does push the bare sync_status: "lost" frame proactively on a stable ws_user socket (measured flip ~6m43s, one sample; treat it as a window, box↔heater radio cadence dependent).

Versioning. I tagged and am running the line on my forks (integration pinned to the lib’s tarball); your repo remains the publicly released version — if you merge, your release converges with my by construction (that worked perfectly with 2.6.2). If you merge env-v2 first, then this line, the interaction is clean except README/CHANGELOG; both trees pass on the merged result (352 lib / 227 integration, contract tests green both directions). Full per-commit detail is in the branches’ history and api-notes.md.