From 455b1c6831a4e135d64d2ee18c1769b0070cd110 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Wed, 23 Nov 2022 11:30:27 -0500 Subject: [PATCH] Add checking for optional support of track control commands 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. --- .../SmartThings/matter-media/fingerprints.yml | 2 +- ...ia-video-player-speaker-track-control.yml} | 3 +- .../profiles/media-video-player-speaker.yml | 24 ++++ .../media-video-player-track-control.yml | 16 +++ .../profiles/media-video-player.yml | 2 - drivers/SmartThings/matter-media/src/init.lua | 35 ++++- .../test/test_matter_media_video_player.lua | 125 ++++++++++++++---- 7 files changed, 171 insertions(+), 36 deletions(-) rename drivers/SmartThings/matter-media/profiles/{media-video-speaker.yml => media-video-player-speaker-track-control.yml} (89%) create mode 100644 drivers/SmartThings/matter-media/profiles/media-video-player-speaker.yml create mode 100644 drivers/SmartThings/matter-media/profiles/media-video-player-track-control.yml diff --git a/drivers/SmartThings/matter-media/fingerprints.yml b/drivers/SmartThings/matter-media/fingerprints.yml index 20e7a381e1..f7259cf973 100644 --- a/drivers/SmartThings/matter-media/fingerprints.yml +++ b/drivers/SmartThings/matter-media/fingerprints.yml @@ -14,4 +14,4 @@ matterGeneric: deviceTypes: - id: 0x0022 # Speaker - id: 0x0028 # Video Player - deviceProfileName: media-video-speaker + deviceProfileName: media-video-player-speaker diff --git a/drivers/SmartThings/matter-media/profiles/media-video-speaker.yml b/drivers/SmartThings/matter-media/profiles/media-video-player-speaker-track-control.yml similarity index 89% rename from drivers/SmartThings/matter-media/profiles/media-video-speaker.yml rename to drivers/SmartThings/matter-media/profiles/media-video-player-speaker-track-control.yml index 1a0c5b0d70..43ac667b05 100644 --- a/drivers/SmartThings/matter-media/profiles/media-video-speaker.yml +++ b/drivers/SmartThings/matter-media/profiles/media-video-player-speaker-track-control.yml @@ -1,4 +1,4 @@ -name: media-video-speaker +name: media-video-player-speaker-track-control components: - id: main capabilities: @@ -24,4 +24,3 @@ components: version: 1 categories: - name: Speaker - \ No newline at end of file diff --git a/drivers/SmartThings/matter-media/profiles/media-video-player-speaker.yml b/drivers/SmartThings/matter-media/profiles/media-video-player-speaker.yml new file mode 100644 index 0000000000..5fa0d725dc --- /dev/null +++ b/drivers/SmartThings/matter-media/profiles/media-video-player-speaker.yml @@ -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 diff --git a/drivers/SmartThings/matter-media/profiles/media-video-player-track-control.yml b/drivers/SmartThings/matter-media/profiles/media-video-player-track-control.yml new file mode 100644 index 0000000000..b53647d719 --- /dev/null +++ b/drivers/SmartThings/matter-media/profiles/media-video-player-track-control.yml @@ -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 \ No newline at end of file diff --git a/drivers/SmartThings/matter-media/profiles/media-video-player.yml b/drivers/SmartThings/matter-media/profiles/media-video-player.yml index 73fddecba7..fb091627a4 100644 --- a/drivers/SmartThings/matter-media/profiles/media-video-player.yml +++ b/drivers/SmartThings/matter-media/profiles/media-video-player.yml @@ -4,8 +4,6 @@ components: capabilities: - id: mediaPlayback version: 1 - - id: mediaTrackControl - version: 1 - id: keypadInput version: 1 - id: switch diff --git a/drivers/SmartThings/matter-media/src/init.lua b/drivers/SmartThings/matter-media/src/init.lua index 7017d55832..c15a6920e6 100644 --- a/drivers/SmartThings/matter-media/src/init.lua +++ b/drivers/SmartThings/matter-media/src/init.lua @@ -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 @@ -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({ @@ -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", @@ -70,6 +70,7 @@ local configure_handler = function(self, device) "NUMBER8", "NUMBER9", })) + end local function on_off_attr_handler(driver, device, ib, response) @@ -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) @@ -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 } }, }, diff --git a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua index 6f26e84cc5..e6ed9f851f 100644 --- a/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua +++ b/drivers/SmartThings/matter-media/src/test/test_matter_media_video_player.lua @@ -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" @@ -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}, @@ -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}, @@ -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 = { @@ -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) @@ -315,7 +336,7 @@ test.register_message_test( channel = "capability", direction = "receive", message = { - mock_device.id, + mock_device_track_control.id, { capability = "mediaTrackControl", component = "main", command = "previousTrack", args = { } } } }, @@ -323,15 +344,15 @@ test.register_message_test( 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 = { } } } }, @@ -339,8 +360,8 @@ test.register_message_test( 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) } }, } @@ -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" }) ) ) @@ -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", @@ -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" }) ) ) @@ -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 )