Skip to content

Commit b4644bd

Browse files
authored
Merge pull request #2405 from SmartThingsCommunity/fix/sonos-nil-index
sonos: Fix nil index error in get_groups response
2 parents 1730cbc + dad0368 commit b4644bd

File tree

1 file changed

+61
-39
lines changed

1 file changed

+61
-39
lines changed

drivers/SmartThings/sonos/src/sonos_state.lua

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ local SonosConnection = require "api.sonos_connection"
1111
--- Information on an entire Sonos system ("household"), such as its current groups, list of players, etc.
1212
--- @field public id HouseholdId
1313
--- @field public groups table<GroupId,SonosGroupObject> All of the current groups in the system
14-
--- @field public players table<PlayerId,{player: SonosPlayerObject, device: SonosDeviceInfoObject}> All of the current players in the system
14+
--- @field public players table<PlayerId,{player: SonosPlayerObject}> All of the current players in the system
1515
--- @field public bonded_players table<PlayerId,boolean> PlayerID's in this map that map to true are non-primary bonded players, and not controllable.
1616
--- @field public player_to_group table<PlayerId,GroupId> quick lookup from Player ID -> Group ID
1717
--- @field public st_devices table<PlayerId,string> Player ID -> ST Device Record UUID information for the household
@@ -77,7 +77,7 @@ end
7777
local _STATE = {
7878
---@type Households
7979
households = make_households_table(),
80-
---@type table<string, {sonos_device: SonosDeviceInfoObject, player: SonosPlayerObject, group: SonosGroupObject, household: SonosHousehold}>
80+
---@type table<string, {sonos_device_id: PlayerId, player: SonosPlayerObject, group: SonosGroupObject, household: SonosHousehold}>
8181
device_record_map = {},
8282
}
8383

@@ -91,6 +91,7 @@ SonosState.__index = SonosState
9191
function SonosState:associate_device_record(device, info)
9292
local household_id = info.ssdp_info.household_id
9393
local group_id = info.ssdp_info.group_id
94+
-- This is the device id even if the device is a secondary in a bonded set
9495
local player_id = info.discovery_info.playerId
9596

9697
local household = _STATE.households[household_id]
@@ -120,9 +121,8 @@ function SonosState:associate_device_record(device, info)
120121
end
121122

122123
local player = (household.players[player_id] or {}).player
123-
local sonos_device = (household.players[player_id] or {}).device
124124

125-
if not (player and sonos_device) then
125+
if not player then
126126
log.error(
127127
string.format(
128128
"No record of Sonos player for device %s",
@@ -132,12 +132,12 @@ function SonosState:associate_device_record(device, info)
132132
return
133133
end
134134

135-
household.st_devices[sonos_device.id] = device.id
135+
household.st_devices[player_id] = device.id
136136

137137
_STATE.device_record_map[device.id] =
138-
{ sonos_device = sonos_device, group = group, player = player, household = household }
138+
{ sonos_device_id = player_id, group = group, player = player, household = household }
139139

140-
local bonded = household.bonded_players[sonos_device.id or {}] and true or false
140+
local bonded = household.bonded_players[player_id] and true or false
141141

142142
local sw_gen_changed = utils.update_field_if_changed(
143143
device,
@@ -179,7 +179,7 @@ function SonosState:associate_device_record(device, info)
179179
local player_id_changed = utils.update_field_if_changed(
180180
device,
181181
PlayerFields.PLAYER_ID,
182-
sonos_device.id,
182+
player_id,
183183
{ persist = true }
184184
)
185185

@@ -306,6 +306,40 @@ function SonosState:update_device_record_from_state(household_id, device)
306306
self:update_device_record_group_info(household, current_mapping.group, device)
307307
end
308308

309+
-- Helper function for when updating household info
310+
local function update_device_info(driver, player, household, known_bonded_players, sonos_device_id)
311+
household.players[sonos_device_id] = { player = player }
312+
local previously_bonded = known_bonded_players[sonos_device_id] and true or false
313+
local currently_bonded
314+
local group_id
315+
316+
-- The primary bonded player will have the same id as the top level player id
317+
if sonos_device_id == player.id then
318+
currently_bonded = false
319+
else
320+
currently_bonded = true
321+
end
322+
group_id = household.player_to_group[player.id]
323+
household.player_to_group[sonos_device_id] = group_id
324+
household.bonded_players[sonos_device_id] = currently_bonded
325+
326+
local maybe_device_id = household.st_devices[sonos_device_id]
327+
if maybe_device_id then
328+
_STATE.device_record_map[maybe_device_id] = _STATE.device_record_map[maybe_device_id] or {}
329+
_STATE.device_record_map[maybe_device_id].household = household
330+
_STATE.device_record_map[maybe_device_id].group = household.groups[group_id]
331+
_STATE.device_record_map[maybe_device_id].player = player
332+
_STATE.device_record_map[maybe_device_id].sonos_device_id = sonos_device_id
333+
if previously_bonded ~= currently_bonded then
334+
local target_device = driver:get_device_info(maybe_device_id)
335+
if target_device then
336+
target_device:set_field(PlayerFields.BONDED, currently_bonded, { persist = false })
337+
driver:update_bonded_device_tracking(target_device)
338+
end
339+
end
340+
end
341+
end
342+
309343
--- @param id HouseholdId
310344
--- @param groups_event SonosGroupsResponseBody
311345
--- @param driver SonosDriver
@@ -323,41 +357,29 @@ function SonosState:update_household_info(id, groups_event, driver)
323357
end
324358
end
325359

360+
-- Iterate through the players and track all the devices associated with them
361+
-- for bonded set tracking.
362+
local log_devices_error = false
326363
for _, player in ipairs(players) do
327-
for _, device in ipairs(player.devices) do
328-
household.players[device.id] = { player = player, device = device }
329-
local previously_bonded = known_bonded_players[device.id] and true or false
330-
local currently_bonded
331-
local group_id
332-
-- non-primary bonded players are excluded from a group's list of PlayerID's so we use the group membership
333-
-- of the primary device
334-
if type(device.primaryDeviceId) == "string" and device.primaryDeviceId ~= "" then
335-
currently_bonded = true
336-
group_id = household.player_to_group[device.primaryDeviceId]
337-
else
338-
currently_bonded = false
339-
group_id = household.player_to_group[device.id]
364+
-- Prefer devices because deviceIds is deprecated but all we care about is
365+
-- the ID so either way is fine.
366+
if player.devices then
367+
for _, device in ipairs(player.devices) do
368+
update_device_info(driver, player, household, known_bonded_players, device.id)
340369
end
341-
household.player_to_group[device.id] = group_id
342-
household.bonded_players[device.id] = currently_bonded
343-
344-
local maybe_device_id = household.st_devices[device.id]
345-
if maybe_device_id then
346-
_STATE.device_record_map[maybe_device_id] = _STATE.device_record_map[maybe_device_id] or {}
347-
_STATE.device_record_map[maybe_device_id].household = household
348-
_STATE.device_record_map[maybe_device_id].group = household.groups[group_id]
349-
_STATE.device_record_map[maybe_device_id].player = player
350-
_STATE.device_record_map[maybe_device_id].sonos_device = device
351-
if previously_bonded ~= currently_bonded then
352-
local target_device = driver:get_device_info(maybe_device_id)
353-
if target_device then
354-
target_device:set_field(PlayerFields.BONDED, currently_bonded, { persist = false })
355-
driver:update_bonded_device_tracking(target_device)
356-
end
357-
end
370+
elseif player.deviceIds then
371+
for _, device_id in ipairs(player.deviceIds) do
372+
update_device_info(driver, player, household, known_bonded_players, device_id)
358373
end
374+
else
375+
log_devices_error = true
376+
-- We can still track the primary player in this case
377+
update_device_info(driver, player, household, known_bonded_players, player.id)
359378
end
360379
end
380+
if log_devices_error then
381+
log.warn_with( { hub_logs = true}, "Group event contained neither devices nor deviceIds in player")
382+
end
361383

362384
household.id = id
363385
_STATE.households[id] = household
@@ -489,7 +511,7 @@ function SonosState:get_sonos_ids_for_device(device)
489511

490512
-- player id *should* be stable
491513
if not player_id then
492-
player_id = sonos_objects.sonos_device.id
514+
player_id = sonos_objects.sonos_device_id
493515
device:set_field(PlayerFields.PLAYER_ID, player_id, { persist = true })
494516
end
495517

0 commit comments

Comments
 (0)