[Risco Local] Custom component to fix utf-8 / FAILED_UNLOAD / 0x17 split bugs — and an open question on a recent regression

Hi all,

I’m running an Italian Risco LightSYS Plus on HA OS 2026.4.4 and have been chasing the well-known pyrisco failure mode where the panel keeps beeping every ~20-30 minutes of supervision and the integration ends up in FAILED_UNLOAD. I’ve put together a tiny custom_component that monkey-patches pyrisco at runtime — no fork, no vendored copy, no manual edit of /usr/src — plus a configuration recommendation that stops the panel-side TCP reset cycle.

After 67+ minutes of continuous uptime with the patch + recommended config:

  • :white_check_mark: no more UnicodeDecodeError 'utf-8' codec can't decode byte 0xa7
  • :white_check_mark: no more FAILED_UNLOAD requiring a full HA restart
  • :white_check_mark: panel-side TCP reset cycle eliminated → no more periodic supervision beeps
  • :white_check_mark: real-time push events from PIR / arm-disarm working

Repo

GitHub: GitHub - polara-data/risco-patch: Workaround custom_component for pyrisco utf-8 / FAILED_UNLOAD / 0x17 split bugs (Italian Risco panels). Stops the ~30min supervision beep. · GitHub (HACS-compatible, full README inside)

What it does (TL;DR)

3 monkey-patches applied at module-load time (so they run before risco.async_setup_entry):

  1. Patch 1. RiscoSocket.__init__ defaults encoding="latin-1" instead of utf-8. Italian / EU panels send latin-1 / cp1252 bytes; UTF-8 raises on 0x80-0xFF.
  2. Patch 2. RiscoSocket.disconnect wrapped in try/except (ConnectionResetError, OSError, BrokenPipeError). Same intent as @perceival’s PR home-assistant/core#165924 (still open after maintainer approval).
  3. Patch 3. RiscoCrypt.decode uses rfind('\x17') instead of split('\x17') so a 0x17 byte appearing in the payload doesn’t crash the listener with ValueError: too many values to unpack. Plus errors="replace" on the decode call.

+ 1 required config change — raise the integration’s scan_interval to 600 seconds (10 minutes):

In our testing, even with the three patches above the panel kept TCP-resetting every ~23 minutes (and emitting a beep at every reset) when scan_interval=30s (default). Raising it to 600 eliminated the cycle entirely (60+ min observed uptime). The cycle was polling-induced, not a firmware timer. To change it: Settings → Devices & services → Risco → Configure → Scan interval (sec) = 600. Real-time push events are unaffected.

Why this matters

The integration’s normal failure loop (without the patch) is:

panel sends byte 0xa7 → UnicodeDecodeError → listener task dies →
ConnectionResetError → reload triggered → reload fails because
unload raises → entry stuck in FAILED_UNLOAD → HA restart needed

Multiple users have reported variants of this:

Install (4 steps)

<config>/custom_components/risco_patch/
├── __init__.py
└── manifest.json

Add one line to configuration.yaml:

risco_patch:

Restart HA. Look for these WARNING lines in the log (one-shot at boot):

[risco_patch] RiscoSocket encoding default -> latin-1
[risco_patch] RiscoSocket.disconnect hardened
[risco_patch] RiscoCrypt.decode patched (rsplit + errors=replace)

Then raise scan_interval to 600 in Risco’s options.

Caveats — read before installing

  • English/US panels that already speak pure ASCII: this patch is harmless but unnecessary.
  • Panels actually needing UTF-8 (rare — emoji in zone labels, custom firmware): change _DEFAULT_ENCODING = "latin-1" at the top of __init__.py to "utf-8" to keep only Patches 2 & 3.
  • Encoding is set via setdefault, so if a future HA version starts passing encoding=... explicitly the patch becomes a no-op for Patch 1.
  • scan_interval=600 is the value that worked for our LightSYS Plus. Other panel models / firmwares may have a different optimal value — try 300 or 900 if 600 doesn’t fully eliminate resets.
  • Tested on HA OS 2026.4.4 + pyrisco 0.6.8 + Risco LightSYS Plus (IT). Reports from other panels welcome.

Happy to submit this upstream as proper PRs against pyrisco (and an extension of #165924), but in the meantime the custom_component path is the lowest-risk way to get out of the reconnect loop without waiting on a release cycle.


Tags / search keywords: risco local, pyrisco, UnicodeDecodeError 0xa7, FAILED_UNLOAD, panel beeping every 30 minutes, Italian Risco, LightSYS, Agility, ProSYS, scan_interval