Skip to content

Commit

Permalink
Add checking for optional support of track control commands
Browse files Browse the repository at this point in the history
The next/prev track control commands are optional in the
MediaPlayback cluster. This will check for the commands using
the AcceptedCommandList attribute in the MediaPlayback cluster and
then switch to an appropriate profile if these commands are
supported.
  • Loading branch information
ctowns committed Nov 23, 2022
1 parent 71bb158 commit 455b1c6
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 36 deletions.
2 changes: 1 addition & 1 deletion drivers/SmartThings/matter-media/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ matterGeneric:
deviceTypes:
- id: 0x0022 # Speaker
- id: 0x0028 # Video Player
deviceProfileName: media-video-speaker
deviceProfileName: media-video-player-speaker
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: media-video-speaker
name: media-video-player-speaker-track-control
components:
- id: main
capabilities:
Expand All @@ -24,4 +24,3 @@ components:
version: 1
categories:
- name: Speaker

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: media-video-player-speaker
components:
- id: main
capabilities:
- id: mediaPlayback
version: 1
- id: keypadInput
version: 1
- id: switch
version: 1
- id: refresh
version: 1
categories:
- name: Television
- id: speaker
capabilities:
- id: audioVolume
version: 1
- id: audioMute
version: 1
- id: refresh
version: 1
categories:
- name: Speaker
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: media-video-player-track-control
components:
- id: main
capabilities:
- id: mediaPlayback
version: 1
- id: mediaTrackControl
version: 1
- id: keypadInput
version: 1
- id: switch
version: 1
- id: refresh
version: 1
categories:
- name: Television
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ components:
capabilities:
- id: mediaPlayback
version: 1
- id: mediaTrackControl
version: 1
- id: keypadInput
version: 1
- id: switch
Expand Down
35 changes: 30 additions & 5 deletions drivers/SmartThings/matter-media/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ local capabilities = require "st.capabilities"
local clusters = require "st.matter.clusters"
local MatterDriver = require "st.matter.driver"
local utils = require "st.utils"
local MediaPlaybackClusterAcceptedCommandList = clusters.MediaPlayback.attributes.AcceptedCommandList

local VOLUME_STEP = 5

Expand All @@ -25,6 +26,9 @@ end

local configure_handler = function(self, device)
local variable_speed_eps = device:get_endpoints(clusters.MediaPlayback.ID, {feature_bitmap = clusters.MediaPlayback.types.MediaPlaybackFeature.VARIABLE_SPEED})
local media_playback_eps = device:get_endpoints(clusters.MediaPlayback.ID)

device:send(MediaPlaybackClusterAcceptedCommandList:read(device, media_playback_eps[1]))

if #variable_speed_eps > 0 then
device:emit_event(capabilities.mediaPlayback.supportedPlaybackCommands({
Expand All @@ -42,11 +46,7 @@ local configure_handler = function(self, device)
}))
end


device:emit_event(capabilities.mediaTrackControl.supportedTrackControlCommands({
capabilities.mediaTrackControl.commands.previousTrack.NAME,
capabilities.mediaTrackControl.commands.nextTrack.NAME,
}))
-- TODO: when to emit supported commands for MediaTrackControl?

device:emit_event(capabilities.keypadInput.supportedKeyCodes({
"UP",
Expand All @@ -70,6 +70,7 @@ local configure_handler = function(self, device)
"NUMBER8",
"NUMBER9",
}))

end

local function on_off_attr_handler(driver, device, ib, response)
Expand Down Expand Up @@ -112,6 +113,29 @@ local function media_playback_state_attr_handler(driver, device, ib, response)
end
end

local function accepted_command_list_attr_handler(driver, device, ib, response)
for _, accepted_command_id in ipairs (ib.data.elements or {}) do
local new_profile = "media-video-player"
-- TODO: do you think it is safe to just check for the "Next" command? Or both/either?
if accepted_command_id.value == clusters.MediaPlayback.commands.Next.ID then
if device:supports_capability(capabilities.audioMute, device:endpoint_to_component(ib.endpoint_id)) then
new_profile = new_profile .. "-speaker"
end
new_profile = new_profile .. "-track-control"

device.log.info(string.format("Updating device profile to %s.", new_profile))
device:try_update_metadata({profile = new_profile})

-- TODO: Should this be moved here or no? Should this event be emitted somewhere else now?
-- device:emit_event(capabilities.mediaTrackControl.supportedTrackControlCommands({
-- capabilities.mediaTrackControl.commands.previousTrack.NAME,
-- capabilities.mediaTrackControl.commands.nextTrack.NAME,
-- }))
return
end
end
end

local function handle_mute(driver, device, cmd)
local endpoint_id = device:component_to_endpoint(cmd.component)
local req = clusters.OnOff.server.commands.Off(device, endpoint_id)
Expand Down Expand Up @@ -208,6 +232,7 @@ local matter_driver_template = {
},
[clusters.MediaPlayback.ID] = {
[clusters.MediaPlayback.attributes.CurrentState.ID] = media_playback_state_attr_handler,
[MediaPlaybackClusterAcceptedCommandList.ID] = accepted_command_list_attr_handler
}
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
local test = require "integration_test"
local capabilities = require "st.capabilities"
local t_utils = require "integration_test.utils"
local Uint32 = require "st.matter.data_types".Uint32

local clusters = require "st.matter.clusters"

Expand All @@ -33,10 +34,6 @@ local mock_device = test.mock_device.build_test_matter_device({
cluster_type = "SERVER",
cluster_revision = 1,
feature_map = 0, --u32 bitmap
attributes = nil, -- attribute id list
server_commands = nil, --server cmd id list
client_commands = nil, --client cmd id list
events = nil, --event id list
},
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"},
{cluster_id = clusters.MediaPlayback.ID, cluster_type = "SERVER", feature_map = 0x0},
Expand All @@ -61,10 +58,6 @@ local mock_device_variable_speed = test.mock_device.build_test_matter_device({
cluster_type = "SERVER",
cluster_revision = 1,
feature_map = 0, --u32 bitmap
attributes = nil, -- attribute id list
server_commands = nil, --server cmd id list
client_commands = nil, --client cmd id list
events = nil, --event id list
},
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"},
{cluster_id = clusters.MediaPlayback.ID, cluster_type = "SERVER", feature_map = 0x2},
Expand All @@ -74,6 +67,29 @@ local mock_device_variable_speed = test.mock_device.build_test_matter_device({
}
})

local mock_device_track_control = test.mock_device.build_test_matter_device({
profile = t_utils.get_profile_definition("media-video-player-track-control.yml"),
manufacturer_info = {
vendor_id = 0x0000,
product_id = 0x0000,
},
endpoints = {
{
endpoint_id = 1,
clusters = {
{
cluster_id = clusters.OnOff.ID,
cluster_type = "SERVER",
cluster_revision = 1,
feature_map = 0, --u32 bitmap
},
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"},
{cluster_id = clusters.MediaPlayback.ID, cluster_type = "SERVER", feature_map = 0x2},
{cluster_id = clusters.KeypadInput.ID, cluster_type = "SERVER"}
}
}
}
})

local function test_init()
local cluster_subscribe_list = {
Expand All @@ -83,25 +99,30 @@ local function test_init()
test.socket.matter:__set_channel_ordering("relaxed")
local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device)
for i, cluster in ipairs(cluster_subscribe_list) do
print(i)
if i > 1 then
subscribe_request:merge(cluster:subscribe(mock_device))
end
print(subscribe_request)
end
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
test.mock_device.add_test_device(mock_device)

local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_variable_speed)
for i, cluster in ipairs(cluster_subscribe_list) do
print(i)
if i > 1 then
subscribe_request:merge(cluster:subscribe(mock_device_variable_speed))
end
print(subscribe_request)
end
test.socket.matter:__expect_send({mock_device_variable_speed.id, subscribe_request})
test.mock_device.add_test_device(mock_device_variable_speed)

local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_track_control)
for i, cluster in ipairs(cluster_subscribe_list) do
if i > 1 then
subscribe_request:merge(cluster:subscribe(mock_device_track_control))
end
end
test.socket.matter:__expect_send({mock_device_track_control.id, subscribe_request})
test.mock_device.add_test_device(mock_device_track_control)
end

test.set_test_init_function(test_init)
Expand Down Expand Up @@ -315,32 +336,32 @@ test.register_message_test(
channel = "capability",
direction = "receive",
message = {
mock_device.id,
mock_device_track_control.id,
{ capability = "mediaTrackControl", component = "main", command = "previousTrack", args = { } }
}
},
{
channel = "matter",
direction = "send",
message = {
mock_device.id,
clusters.MediaPlayback.server.commands.Previous(mock_device, 1)
mock_device_track_control.id,
clusters.MediaPlayback.server.commands.Previous(mock_device_track_control, 1)
}
},
{
channel = "capability",
direction = "receive",
message = {
mock_device.id,
mock_device_track_control.id,
{ capability = "mediaTrackControl", component = "main", command = "nextTrack", args = { } }
}
},
{
channel = "matter",
direction = "send",
message = {
mock_device.id,
clusters.MediaPlayback.server.commands.Next(mock_device, 1)
mock_device_track_control.id,
clusters.MediaPlayback.server.commands.Next(mock_device_track_control, 1)
}
},
}
Expand Down Expand Up @@ -517,17 +538,15 @@ test.register_coroutine_test(
function()
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure"})

test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
capabilities.mediaPlayback.supportedPlaybackCommands({ "play", "pause", "stop" })
)
)
test.socket.matter:__expect_send({
mock_device.id,
clusters.MediaPlayback.attributes.AcceptedCommandList:read(mock_device, 1)
})

test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
capabilities.mediaTrackControl.supportedTrackControlCommands({ "previousTrack", "nextTrack" })
capabilities.mediaPlayback.supportedPlaybackCommands({ "play", "pause", "stop" })
)
)

Expand Down Expand Up @@ -568,6 +587,11 @@ test.register_coroutine_test(
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_variable_speed.id, "doConfigure"})

test.socket.matter:__expect_send({
mock_device_variable_speed.id,
clusters.MediaPlayback.attributes.AcceptedCommandList:read(mock_device_variable_speed, 1)
})

test.socket.capability:__expect_send(
mock_device_variable_speed:generate_test_message(
"main",
Expand All @@ -578,7 +602,49 @@ test.register_coroutine_test(
test.socket.capability:__expect_send(
mock_device_variable_speed:generate_test_message(
"main",
capabilities.mediaTrackControl.supportedTrackControlCommands({ "previousTrack", "nextTrack" })
capabilities.keypadInput.supportedKeyCodes({
"UP",
"DOWN",
"LEFT",
"RIGHT",
"SELECT",
"BACK",
"EXIT",
"MENU",
"SETTINGS",
"HOME",
"NUMBER0",
"NUMBER1",
"NUMBER2",
"NUMBER3",
"NUMBER4",
"NUMBER5",
"NUMBER6",
"NUMBER7",
"NUMBER8",
"NUMBER9",
})
)
)

mock_device_variable_speed:expect_metadata_update({ provisioning_state = "PROVISIONED" })
end
)

test.register_coroutine_test(
"Profile change due to doConfigure checking for MediaTrackControl commands in AcceptedCommandList cluster attribute",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_variable_speed.id, "doConfigure"})

test.socket.matter:__expect_send({
mock_device_variable_speed.id,
clusters.MediaPlayback.attributes.AcceptedCommandList:read(mock_device_variable_speed, 1)
})

test.socket.capability:__expect_send(
mock_device_variable_speed:generate_test_message(
"main",
capabilities.mediaPlayback.supportedPlaybackCommands({ "play", "pause", "stop", "rewind", "fastForward" })
)
)

Expand Down Expand Up @@ -611,6 +677,13 @@ test.register_coroutine_test(
)

mock_device_variable_speed:expect_metadata_update({ provisioning_state = "PROVISIONED" })
mock_device_variable_speed:expect_metadata_update({ profile = "media-video-player-track-control" })

test.wait_for_events()
test.socket.matter:__queue_receive({
mock_device_variable_speed.id,
clusters.MediaPlayback.attributes.AcceptedCommandList:build_test_report_data(mock_device_variable_speed, 1, {Uint32(0x05)}),
})
end
)

Expand Down

0 comments on commit 455b1c6

Please sign in to comment.