diff --git a/src/adapters/http/adminApi/transports/transportsHandlers.ts b/src/adapters/http/adminApi/transports/transportsHandlers.ts index 5c6f0a5f..eddcc2b5 100644 --- a/src/adapters/http/adminApi/transports/transportsHandlers.ts +++ b/src/adapters/http/adminApi/transports/transportsHandlers.ts @@ -201,7 +201,8 @@ async function handleSonosDiscovery( const payload = devices.map((device) => ({ id: device.udn || device.host, host: device.host, - name: device.name ?? device.roomName, + name: [device.roomName ?? device.name, device.model, device.host].filter(Boolean).join(' ยท '), + model: device.model, roomName: device.roomName, householdId: device.householdId, active: activeHost ? device.host === activeHost : undefined, diff --git a/src/adapters/outputs/sonos/sonosDiscovery.ts b/src/adapters/outputs/sonos/sonosDiscovery.ts index 19d44141..2825e9dc 100644 --- a/src/adapters/outputs/sonos/sonosDiscovery.ts +++ b/src/adapters/outputs/sonos/sonosDiscovery.ts @@ -306,13 +306,19 @@ async function resolveDeviceFromLocation( if (!host) { return null; } - const name = matchTag(xml, 'friendlyName'); + const friendlyName = matchTag(xml, 'friendlyName'); + // S2 device descriptions carry the user-configured room name directly; /status/zp + // no longer has and its carries a channel suffix like "(LF,RF)". + const descRoomName = matchTag(xml, 'roomName'); + const model = matchTag(xml, 'modelName') ?? undefined; const udn = matchTag(xml, 'UDN')?.replace(/^uuid:/i, ''); const status = await fetchStatus(host, householdId); + const roomName = descRoomName ?? status?.roomName ?? undefined; return { host, - name: name ?? status?.roomName ?? undefined, - roomName: status?.roomName, + name: roomName ?? friendlyName ?? undefined, + roomName, + model, udn, householdId: status?.householdId, }; @@ -340,7 +346,7 @@ async function fetchStatus( return null; } return { - roomName: matchTag(xml, 'RoomName') ?? matchTag(xml, 'ZoneName') ?? undefined, + roomName: matchTag(xml, 'RoomName') ?? stripChannelSuffix(matchTag(xml, 'ZoneName')) ?? undefined, householdId: household ?? undefined, }; } catch { @@ -454,6 +460,11 @@ function buildSearchRequest(mx: number, target: string): Buffer { return Buffer.from(payload); } +function stripChannelSuffix(value: string | null): string | null { + if (!value) return null; + return value.replace(/\s*\([A-Z,+]+\)\s*$/, '').trim() || null; +} + function matchTag(xml: string, tag: string): string | null { const match = xml.match(new RegExp(`<${tag}>([^<]+)`, 'i')); return match?.[1]?.trim() ?? null; diff --git a/src/ports/OutputDiscoveryPort.ts b/src/ports/OutputDiscoveryPort.ts index cfd69d0f..42d3d037 100644 --- a/src/ports/OutputDiscoveryPort.ts +++ b/src/ports/OutputDiscoveryPort.ts @@ -54,6 +54,8 @@ export interface SonosDiscoveredDevice { host: string; name?: string; roomName?: string; + /** Model from the UPnP device description, e.g. "Sonos Beam". */ + model?: string; udn?: string; householdId?: string; /**