Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions drivers/SmartThings/sonos/src/api/event_handlers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ local log = require "log"

local st_utils = require "st.utils"

local PlayerFields = require "fields".SonosPlayerFields

local CapEventHandlers = {}

CapEventHandlers.PlaybackStatus = {
Expand All @@ -12,41 +14,48 @@ CapEventHandlers.PlaybackStatus = {
Playing = "PLAYBACK_STATE_PLAYING",
}

local function _do_emit(device, attribute_event)
local bonded = device:get_field(PlayerFields.BONDED)
if not bonded then
device:emit_event(attribute_event)
end
end

function CapEventHandlers.handle_player_volume(device, new_volume, is_muted)
device:emit_event(capabilities.audioVolume.volume(new_volume))
_do_emit(device, capabilities.audioVolume.volume(new_volume))
if is_muted then
device:emit_event(capabilities.audioMute.mute.muted())
_do_emit(device, capabilities.audioMute.mute.muted())
else
device:emit_event(capabilities.audioMute.mute.unmuted())
_do_emit(device, capabilities.audioMute.mute.unmuted())
end
end

function CapEventHandlers.handle_group_volume(device, new_volume, is_muted)
device:emit_event(capabilities.mediaGroup.groupVolume(new_volume))
_do_emit(device, capabilities.mediaGroup.groupVolume(new_volume))
if is_muted then
device:emit_event(capabilities.mediaGroup.groupMute.muted())
_do_emit(device, capabilities.mediaGroup.groupMute.muted())
else
device:emit_event(capabilities.mediaGroup.groupMute.unmuted())
_do_emit(device, capabilities.mediaGroup.groupMute.unmuted())
end
end

function CapEventHandlers.handle_group_role_update(device, group_role)
device:emit_event(capabilities.mediaGroup.groupRole(group_role))
_do_emit(device, capabilities.mediaGroup.groupRole(group_role))
end

function CapEventHandlers.handle_group_coordinator_update(device, coordinator_id)
device:emit_event(capabilities.mediaGroup.groupPrimaryDeviceId(coordinator_id))
_do_emit(device, capabilities.mediaGroup.groupPrimaryDeviceId(coordinator_id))
end

function CapEventHandlers.handle_group_id_update(device, group_id)
device:emit_event(capabilities.mediaGroup.groupId(group_id))
_do_emit(device, capabilities.mediaGroup.groupId(group_id))
end

function CapEventHandlers.handle_group_update(device, group_info)
local groupRole, groupPrimaryDeviceId, groupId = table.unpack(group_info)
device:emit_event(capabilities.mediaGroup.groupRole(groupRole))
device:emit_event(capabilities.mediaGroup.groupPrimaryDeviceId(groupPrimaryDeviceId))
device:emit_event(capabilities.mediaGroup.groupId(groupId))
_do_emit(device, capabilities.mediaGroup.groupRole(groupRole))
_do_emit(device, capabilities.mediaGroup.groupPrimaryDeviceId(groupPrimaryDeviceId))
_do_emit(device, capabilities.mediaGroup.groupId(groupId))
end

function CapEventHandlers.handle_audio_clip_status(device, clips)
Expand All @@ -61,11 +70,11 @@ end

function CapEventHandlers.handle_playback_status(device, playback_state)
if playback_state == CapEventHandlers.PlaybackStatus.Playing then
device:emit_event(capabilities.mediaPlayback.playbackStatus.playing())
_do_emit(device, capabilities.mediaPlayback.playbackStatus.playing())
elseif playback_state == CapEventHandlers.PlaybackStatus.Idle then
device:emit_event(capabilities.mediaPlayback.playbackStatus.stopped())
_do_emit(device, capabilities.mediaPlayback.playbackStatus.stopped())
elseif playback_state == CapEventHandlers.PlaybackStatus.Paused then
device:emit_event(capabilities.mediaPlayback.playbackStatus.paused())
_do_emit(device, capabilities.mediaPlayback.playbackStatus.paused())
elseif playback_state == CapEventHandlers.PlaybackStatus.Buffering then
-- TODO the DTH doesn't currently do anything w/ buffering;
-- might be worth figuring out what to do with this in the future.
Expand All @@ -74,7 +83,7 @@ function CapEventHandlers.handle_playback_status(device, playback_state)
end

function CapEventHandlers.update_favorites(device, new_favorites)
device:emit_event(capabilities.mediaPresets.presets(new_favorites))
_do_emit(device, capabilities.mediaPresets.presets(new_favorites))
end

function CapEventHandlers.handle_playback_metadata_update(device, metadata_status_body)
Expand Down Expand Up @@ -128,7 +137,7 @@ function CapEventHandlers.handle_playback_metadata_update(device, metadata_statu
end

if type(audio_track_data.title) == "string" then
device:emit_event(capabilities.audioTrackData.audioTrackData(audio_track_data))
_do_emit(device, capabilities.audioTrackData.audioTrackData(audio_track_data))
end
end

Expand Down
2 changes: 1 addition & 1 deletion drivers/SmartThings/sonos/src/api/rest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ local SonosRestApi = {}
--- Query a Sonos Group IP address for individual player info
---@param url table a URL table created by `net_url`
---@param headers table<string,string>?
---@return SonosDiscoveryInfo|SonosErrorResponse|nil
---@return SonosDiscoveryInfoObject|SonosErrorResponse|nil
---@return string|nil error
function SonosRestApi.get_player_info(url, headers)
url.path = "/api/v1/players/local/info"
Expand Down
62 changes: 40 additions & 22 deletions drivers/SmartThings/sonos/src/api/sonos_connection.lua
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,12 @@ local function _open_coordinator_socket(sonos_conn, household_id, self_player_id
return
end

_, err =
Router.open_socket_for_player(household_id, coordinator_id, coordinator.websocketUrl, api_key)
_, err = Router.open_socket_for_player(
household_id,
coordinator_id,
coordinator.player.websocket_url,
api_key
)
if err ~= nil then
log.error(
string.format(
Expand Down Expand Up @@ -302,10 +306,13 @@ end
--- @return SonosConnection
function SonosConnection.new(driver, device)
log.debug(string.format("Creating new SonosConnection for %s", device.label))
local self = setmetatable(
{ driver = driver, device = device, _listener_uuids = {}, _initialized = false, _reconnecting = false },
SonosConnection
)
local self = setmetatable({
driver = driver,
device = device,
_listener_uuids = {},
_initialized = false,
_reconnecting = false,
}, SonosConnection)

-- capture the label here in case something goes wonky like a callback being fired after a
-- device is removed
Expand Down Expand Up @@ -339,9 +346,11 @@ function SonosConnection.new(driver, device)
device.log.warn(
string.format("WebSocket connection no longer authorized, disconnecting")
)
local _, security_err = driver:request_oauth_token()
if security_err then
log.warn(string.format("Error during request for oauth token: %s", security_err))
if not driver:is_waiting_for_oauth_token() then
local _, security_err = driver:request_oauth_token()
if security_err then
log.warn(string.format("Error during request for oauth token: %s", security_err))
end
end
-- closing the socket directly without calling `:stop()` triggers the reconnect loop,
-- which is where we wait for the token to come in.
Expand All @@ -358,19 +367,28 @@ function SonosConnection.new(driver, device)
local household_id, current_coordinator =
self.driver.sonos:get_coordinator_for_device(self.device)
local _, player_id = self.driver.sonos:get_player_for_device(self.device)
self.driver.sonos:update_household_info(header.householdId, body, self.device)
self.driver.sonos:update_household_info(header.householdId, body, self.driver)
self.driver.sonos:update_device_record_from_state(header.householdId, self.device)
local _, updated_coordinator = self.driver.sonos:get_coordinator_for_device(self.device)

local bonded = self.device:get_field(PlayerFields.BONDED)
if bonded then
self:stop()
end

Router.cleanup_unused_sockets(self.driver)

if not self:coordinator_running() then
--TODO this is not infallible
_open_coordinator_socket(self, household_id, player_id)
end
if not bonded then
if not self:coordinator_running() then
--TODO this is not infallible
_open_coordinator_socket(self, household_id, player_id)
end

if current_coordinator ~= updated_coordinator then
self:refresh_subscriptions()
if current_coordinator ~= updated_coordinator then
self:refresh_subscriptions()
end
else
self.device:offline()
end
elseif header.type == "playerVolume" then
log.trace(string.format("PlayerVolume type message for %s", device_name))
Expand Down Expand Up @@ -399,7 +417,7 @@ function SonosConnection.new(driver, device)
return
end
local group = household.groups[header.groupId] or { playerIds = {} }
for _, player_id in ipairs(group.playerIds) do
for _, player_id in ipairs(group.player_ids) do
local device_for_player = self.driver:device_for_player(header.householdId, player_id)
--- we've seen situations where these messages can be processed while a device
--- is being deleted so we check for the presence of emit event as a proxy for
Expand All @@ -423,7 +441,7 @@ function SonosConnection.new(driver, device)
return
end
local group = household.groups[header.groupId] or { playerIds = {} }
for _, player_id in ipairs(group.playerIds) do
for _, player_id in ipairs(group.player_ids) do
local device_for_player = self.driver:device_for_player(header.householdId, player_id)
--- we've seen situations where these messages can be processed while a device
--- is being deleted so we check for the presence of emit event as a proxy for
Expand All @@ -446,7 +464,7 @@ function SonosConnection.new(driver, device)
return
end
local group = household.groups[header.groupId] or { playerIds = {} }
for _, player_id in ipairs(group.playerIds) do
for _, player_id in ipairs(group.player_ids) do
local device_for_player = self.driver:device_for_player(header.householdId, player_id)
--- we've seen situations where these messages can be processed while a device
--- is being deleted so we check for the presence of emit event as a proxy for
Expand Down Expand Up @@ -477,7 +495,7 @@ function SonosConnection.new(driver, device)
return
end

local url_ip = lb_utils.force_url_table(coordinator_player.websocketUrl).host
local url_ip = lb_utils.force_url_table(coordinator_player.player.websocket_url).host
local base_url = lb_utils.force_url_table(
string.format("https://%s:%s", url_ip, SonosApi.DEFAULT_SONOS_PORT)
)
Expand All @@ -503,7 +521,7 @@ function SonosConnection.new(driver, device)
end
self.driver.sonos:update_household_favorites(header.householdId, new_favorites)

for _, player_id in ipairs(group.playerIds) do
for _, player_id in ipairs(group.player_ids) do
local device_for_player =
self.driver:device_for_player(header.householdId, player_id)
--- we've seen situations where these messages can be processed while a device
Expand Down Expand Up @@ -590,7 +608,7 @@ function SonosConnection:coordinator_running()
)
)
end
return type(unique_key) == "string" and Router.is_connected(unique_key) and self._initialized
return type(unique_key) == "string" and Router.is_connected(unique_key)
end

function SonosConnection:refresh_subscriptions(maybe_reply_tx)
Expand Down
Loading
Loading