Skip to content

Add DeviceInfo.connections for cross-integration HA card merge#215

Draft
trudenboy wants to merge 1 commit into
music-assistant:mainfrom
trudenboy:feat/device-info-connections
Draft

Add DeviceInfo.connections for cross-integration HA card merge#215
trudenboy wants to merge 1 commit into
music-assistant:mainfrom
trudenboy:feat/device-info-connections

Conversation

@trudenboy
Copy link
Copy Markdown

@trudenboy trudenboy commented Apr 30, 2026

DeviceInfo: add open-ended connections set for cross-integration HA device-card merge

Summary

Adds a new optional connections: set[tuple[str, str]] field to
music_assistant_models.player.DeviceInfo, mirroring Home Assistant's
homeassistant.helpers.device_registry.DeviceInfo.connections shape, plus
a small add_connection() helper that normalizes MAC-shaped values to
HA's canonical lowercase-with-colons form.

This is the data-model side of a coordinated change across four repos
(see Coordination below). The downstream music-assistant/server PR
populates the field from aiosendspin client_hello.device_info.connections,
and the home-assistant/core PR forwards the set verbatim into HA's
DeviceInfo.connections so HA's device-registry can dedupe device cards
across integrations that know the same hardware.

Motivation

Today every player provider that knows hardware identity (WiiM,
hass_players, AirPlay-via-Sendspin, BT-via-Sendspin, future
Zigbee/Thread/Matter bridges) sets IdentifierType.MAC_ADDRESS /
IdentifierType.IP_ADDRESS on its player's DeviceInfo.identifiers
but none of that propagates into HA's device_info.connections.
The MA HA integration constructs DeviceInfo with only identifiers,
manufacturer, model, name, configuration_url (see
homeassistant/components/music_assistant/entity.py).

Result: every MA media_player.<name> lands in HA's device-registry
with connections=[]. When another HA integration (e.g. the official
bluetooth integration, or a vendor-specific custom_component) creates
its own card for the same physical device, HA can't merge them
because there's no common connection tuple. Operators see two device
cards per speaker.

This is not Sendspin-specific — it affects every MA player provider
that has hardware identity but no path to surface it to HA. Fixing the
data model (this PR) + MA HA integration (separate PR) unblocks
identity-passthrough for the whole org.

Design

identifiers: dict[IdentifierType, str] — the existing field — keeps
serving MA-internal cross-protocol matching (universal_player ranks
MAC_ADDRESS > UUID > fallback). Closed enum is intentional there —
the matcher reasons about each type's reliability ranking.

connections: set[tuple[str, str]] — the new field — is open-ended
on purpose. HA's device_registry accepts any string as
connection_type; standard values are "mac", "bluetooth",
"zigbee", "upnp" but new transports (Matter device-id, Thread MLE,
custom protocols) should work without taxonomy changes here.

The two fields have different lifecycles and different audiences (MA
internals vs. HA forwarding) so they live side by side rather than
overloading one.

API

@dataclass
class DeviceInfo(DataClassDictMixin):
    # existing fields …
    connections: set[tuple[str, str]] = field(default_factory=set)

    def add_connection(self, connection_type: str, value: str) -> None:
        """Add a hardware connection tuple, normalizing MAC-shaped values."""
        # MAC values: any of "AA:BB:CC:DD:EE:FF" / "aa-bb-cc-dd-ee-ff" /
        # "AABBCCDDEEFF" → "aa:bb:cc:dd:ee:ff" (HA's canonical form).
        # All other connection types stored verbatim.

Tests

tests/test_player_device_info.py covers:

  • empty default,
  • MAC normalization for all 4 input forms (colons / dashes / no
    separator / already-canonical) under both mac and bluetooth types,
  • pass-through for unknown connection types (zigbee, matter, custom),
  • non-MAC value under mac type stored verbatim (no false coercion),
  • empty value silently ignored,
  • set semantics (dedupes cross-format duplicates),
  • to_dict / from_dict round-trip preserves the set,
  • independence from identifiers (no leakage between fields).

11 new tests, 100% pass. Coverage of player.py improved
(see .local-ci-output.txt).

Backwards compatibility

  • New field with default_factory=set — existing
    DeviceInfo() constructions work unchanged.
  • to_dict / from_dict round-trip preserves the set; old serialized
    payloads without connections deserialize to set() (default).
  • No change to identifiers, add_identifier, mac_address, or any
    existing API surface.
  • No new dependency on Home Assistant — the connection-type strings are
    conventionally HA-aligned but the model itself stays HA-agnostic.

Coordination

This PR is one of four in a coordinated change. Land order:

  1. THIS PR — data model (music-assistant/models).
  2. PR-C — Sendspin/aiosendspin#226
    — protocol-level DeviceInfo.connections field.
  3. PR-D — music-assistant/server#3813
    — Sendspin provider passthrough + opportunistic
    IdentifierType.MAC_ADDRESS mirror for mac/bluetooth tuples.
  4. PR-A — home-assistant/core#169541
    — MA integration forwards player.device_info.connections into HA
    DeviceInfo.connections.

Umbrella discussion with full design context, status, and cross-links:
https://github.com/orgs/music-assistant/discussions/5415

PR-D and PR-A both depend on this PR landing & a music_assistant_models
release.

Test plan locally

python -m venv .venv
source .venv/bin/activate
pip install ".[test]"
pre-commit install
pre-commit run --all-files
pytest --durations 10 tests/

Output captured in .local-ci-output.txt next to this description.

Mirrors HA's open-ended device_registry connections shape (a set of
(connection_type, value) tuples) on MA's player DeviceInfo so the HA
integration can forward the set verbatim and HA's device_registry can
dedupe cards across integrations that know the same hardware.

Existing identifiers dict[IdentifierType, str] keeps serving MA-internal
cross-protocol matching (universal_player MAC > UUID > fallback ranking).
The new field has different audience (HA forwarding) and stays
open-ended on purpose so future transports (zigbee, matter, thread,
custom) work without taxonomy changes.

Companion PRs in aiosendspin (protocol field), music-assistant/server
(Sendspin provider passthrough), and home-assistant/core (MA integration
forwarding).
@trudenboy trudenboy force-pushed the feat/device-info-connections branch from 0f3fa91 to 3708d99 Compare April 30, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant