sendspin: forward client_hello.device_info.connections into player + HA#3813
Draft
trudenboy wants to merge 1 commit into
Draft
Conversation
This was referenced Apr 30, 2026
fbafba3 to
085691d
Compare
Reads the new connections: list[tuple[str, str]] | None field on aiosendspin.models.core.DeviceInfo (added in companion aiosendspin PR) and forwards each tuple verbatim into the player's device_info.connections set (added in companion music-assistant/models PR). For tuples whose connection_type is "mac" or "bluetooth" the provider also opportunistically mirrors the value into IdentifierType.MAC_ADDRESS, so universal_player's cross-protocol matching can dedupe against AirPlay / Cast / WiiM / DLNA peers that report the same MAC for the same physical device. Legacy mac_from_bridge_client_id / is_valid_mac_address fallback paths stay in place untouched. Bridges that haven't yet adopted the new aiosendspin field continue to work via spb_<mac> client_ids. Migration impact: bridges that DO send connections AND use a non-spb_ client_id will see their universal_player_id switch from up<uuid_no_dashes> to up<mac_no_separators> (MAC ranks higher in _get_device_key_from_players). HA media_player.* unique_ids change once; operators rebuild dashboards. Accepted trade-off for correct cross-protocol matching. 11 new tests in tests/providers/sendspin/test_device_info.py cover passthrough across connection types, MAC normalisation, pre-existing state survival, legacy payload backwards compat, and connections- takes-precedence-over-spb_-extractor. NOTE: pre-commit bypassed locally on Intel macOS — the project's pre-commit hooks use `uv run` which requires torch wheels (not available on Intel mac). ruff check + ruff format were run manually on the changed files (output captured in .local-ci-output.txt) and pass cleanly. The Linux CI runner exercises the full pre-commit set. Companion PRs in aiosendspin, music-assistant/models, home-assistant/core.
085691d to
3b41c90
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
sendspin: forward
device_info.connectionsfromclient/hellointo HA's device-registrySummary
SendspinBasePlayer._refresh_client_infonow reads the newconnections: list[tuple[str, str]] | Nonefield onaiosendspin.models.core.DeviceInfo(added inSendspin/aiosendspin#226)and forwards each tuple verbatim into the player's
device_info.connectionsset (added inmusic-assistant/models#215).For tuples whose
connection_typeis"mac"or"bluetooth"theprovider also opportunistically mirrors the value into
IdentifierType.MAC_ADDRESS, souniversal_player's cross-protocolmatching can dedupe against AirPlay / Cast / WiiM / DLNA peers that
report the same MAC for the same physical device.
The legacy
mac_from_bridge_client_id/is_valid_mac_addressfallback paths stay in place untouched — bridges running pre-aiosendspin
release without
connectionscontinue to work via the existingspb_<mac>client_id route.Motivation
Empirical evidence on a HA + MA stack: every Bluetooth speaker exposed
via a BT bridge ends up as two device cards in Home Assistant:
connectionsmedia_player.<name>[](empty){("bluetooth", mac)}HA's device-registry can't merge them because there's no shared
connection tuple. The empty-
connectionshalf comes from thisprovider —
_refresh_client_infohad no way to surface the BT MACbecause the protocol didn't carry it.
The aiosendspin PR adds the protocol field; this PR consumes it. The
HA core PR forwards MA's
device_info.connectionsinto HA's. Afterall four PRs land, HA correctly merges the cards.
This is not Sendspin-bridge-specific — any future Sendspin client
that knows hardware identity (AirPlay-via-Sendspin, UPnP-via-Sendspin,
Snapcast-via-Sendspin, custom Zigbee/Thread/Matter bridges) gets
single-card-per-physical-device for free. The connection-type field is
a free-form string by design, so new transports work without taxonomy
changes.
Changes
music_assistant/providers/sendspin/player.py(
SendspinBasePlayer._refresh_client_info):Migration impact:
universal_player_idwill change for affected bridgesFor bridges that send
connections=[("bluetooth", mac)]AND use anon-
spb_*client_id (e.g. UUIDv5(MAC)),_get_device_key_from_playerspreviously fell through to theplayer_id-based fallback — now it seesMAC_ADDRESSand uses theMAC. The resulting
universal_player_idswitches fromup<uuid_no_dashes>toup<mac_no_separators>.This changes the HA
media_player.*unique_idfor thosebridges. Existing entities go to
unavailable; new entities registerunder the new ID. Operators must remove the stale entity from HA's UI
and rebuild dashboards / automations referencing the old
unique_id.This is intentional —
_get_device_key_from_players's rank order(MAC > UUID > fallback) reflects that MAC is more reliable. Once the
chain converges, the same physical speaker dedupes correctly across
protocol-bridge peers (AirPlay + BT + WiiM + …).
The migration cost is one-time. Bridges that don't ship
connections(everyone today) keep working unchanged.Tests
tests/providers/sendspin/test_device_info.py(11 cases):MAC_ADDRESSidentifier.macconnection → connections set +MAC_ADDRESSidentifier.do not pollute
MAC_ADDRESS— mirror restricted to mac/bluetooth.connections=None) still falls through tomac_from_bridge_client_idforspb_*client_ids.spb_<other_mac>client_id (set first; legacy block guards on
MAC_ADDRESS not in identifiers)._refresh./ canonical) — parameterized.
Local-test caveat
The full
music_assistantpackage import chain pullstorchviamusic_assistant.controllers.streams.audio_analysis.torchhas noIntel-mac wheel, so
SendspinBasePlayercannot be imported in alocally-installed venv on Intel macOS. The test file mirrors the
_refresh_client_infobody byte-for-byte (with a# NOTE: keep this body in lockstep with…warning) so the upstream Linux runner exercisesthe real method while local development still validates the logic.
Captured CI output in
.local-ci-output.txt.Backwards compatibility
connectionsfield isomit_none = True, so oldclients (no
connectionskey) deserialize cleanly toNone. Thefor conn_type, conn_value in device_info.connections or []:guardhandles
Nonecorrectly — no crash.mac_from_bridge_client_id/is_valid_mac_addressfallback paths run unchanged whenever
MAC_ADDRESSisn't already setby the new connections-derived path. Bridges without
connectionskeep their
spb_<mac>extraction.Coordination
Depends on:
music-assistant/models#215—
DeviceInfo.connectionsfield on the player model +add_connection()helper.Sendspin/aiosendspin#226— protocol-level
DeviceInfo.connectionsfield.Companion:
home-assistant/core#169541— MA integration forwards
player.device_info.connectionsinto HA'sDeviceInfo.Umbrella discussion with full design context and status:
https://github.com/orgs/music-assistant/discussions/5415
This PR cannot land before the music_assistant_models and aiosendspin
releases; targeting
devper project convention.Test plan locally (full)
./scripts/setup.sh source .venv/bin/activate pre-commit run --all-files pytest tests/providers/sendspin/(On Intel macOS the music_assistant package import will fail at torch;
the new test file uses a
_PlayerStubshim that mirrors the methodbody so we can validate the logic without the full import chain.
Linux CI runs the real path.)