Skip to content

UniFi Play v1.4.1

Choose a tag to compare

@github-actions github-actions released this 21 Sep 19:17
53ab847

What's new in 1.4.1

A bug-fix release. The last stable tag was v1.4.0, and the only behaviour
that changes is zone playback. No new entities, no new services, no change to
the supported Home Assistant floor.

Restart Home Assistant after updating. Reloading a config entry does not
reload Python; only a restart picks up the new code.

Zones created in Home Assistant now play on their members

In 1.4.0 a zone created through the integration registered correctly on every
speaker, reported the right membership and dev_count, showed sensible
entities, and then played on the host alone. The members stayed silent. The
same speakers grouped in the Play app worked.

Three fields were missing from the written set_groups payload, each one
captured off a speaker's own MQTT broker and diffed against what the app
sends:

Field Play app 1.4.0 1.4.1
body timestamp epoch seconds absent sent, 0 when the last zone is cleared
group_index 1 on create 0 1 on create, existing value on edit
wb_enable / wb_device / wb_input only while broadcasting always only while broadcasting

Without the body stamp the speakers store the zone as timestamp: -1. Note
this is the body envelope, not the per-group timestamp that #22 and #23
retired: that one is never echoed back and really is write-only noise.

The wideband question #22 left open is now settled. An absent wb_enable
reads as off, not "leave unchanged". Switching a broadcasting zone back to
Streaming with all three keys omitted clears wb_enable on every speaker.

Measured by @rwarner on five UPL-PORT speakers, one zone of three including
one on Wi-Fi, with every zone deleted and the speakers confirmed empty between
runs. That precaution matters and is now written into docs/verification.md:
speakers hold recent zone state, so a back-to-back retrial passes regardless
of what was written, which is how each of these fields was wrongly ruled out
the first time. Run on firmware 1.1.10 and repeated on 1.1.13 after the
speakers auto-updated, with identical results.

scripts/check_set_groups_payload.py grows from two checks to five and fails
against 1.4.0 as shipped, naming all three regressions. It runs in CI, so the
payload cannot quietly drift back.

A rename could be lost just after a broadcast was turned off

Found while reviewing the change above, and fixed in the same release.

Because the write now omits the wideband keys rather than blanking them, the
firmware keeps wb_device and wb_input at their old values and echoes them
back with wb_enable false. The zone signature still compared both, so Home
Assistant's own readback matched neither the pending document nor the write
behind it, and reconciliation read it as a Play-app edit.

In practice: turn a broadcast off, rename the zone before the readback lands,
and the rename was silently discarded. A second effect came from the same
cause, since the leftovers are per speaker. Two speakers holding different
values disagreed permanently, so the "speakers disagree about zone" warning
never cleared and diagnostics reported two copies of a zone every speaker
agreed on.

Both fields are inert while wb_enable is false, and every consumer already
gated on it. The signature now does the same.

Certificate generations on the Audio Port, measured

The firmware table's UPL-PORT row was an inference. Five Ports auto-updated
to 1.1.13 during this work and turned it into a measurement: the legacy
certificate still completes the TLS handshake and is rejected afterwards,
at the MQTT layer, while the 2026 pair connects normally.

So a reachability check on port 8883 proves nothing about which generation a
speaker wants. Only a full MQTT session tells them apart. README.md and
docs/api.md are corrected accordingly, including a note that a speaker
rotating its certificate while already set up may need the integration
reloaded once before it comes back.

Verification

Both lanes, the per-module coverage gate, strict typing, formatting, hassfest,
HACS and all six protocol guards pass. 587 tests.

The payload change is backed by hardware, on the five Audio Ports described
above. The reconciliation fix on top of it is covered by test only:
test_a_readback_of_retained_wideband_fields_is_not_an_app_edit and
test_leftover_wideband_fields_are_not_a_zone_conflict, both of which fail
without it. Nothing in this release was verified on a UPL-AMP.

As in 1.4.0, hardware smoke on both models is the release checklist, not
CI
, and CI is explicitly not evidence that a published value routes audio.

Install

HACS installs from the repository as usual and will offer 1.4.1 as the latest
release. For a manual install, download unifi_play.zip and unpack it into
config/custom_components/, then restart Home Assistant.

The archive is built from the tagged commit with fixed timestamps and file
modes, so rebuilding it from this tag reproduces the published checksum:

python scripts/build_release_archive.py
sha256sum -c dist/unifi_play.zip.sha256