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 )