Skip to content

Commit 064c7a6

Browse files
Matter Switch: Add subdriver for Third Reality MK1
Add new profile and a subdriver to support the Third Reality keyboard, which contains 12 matter-enabled buttons.
1 parent 39bd9cf commit 064c7a6

File tree

4 files changed

+471
-3
lines changed

4 files changed

+471
-3
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: 12-button-keyboard
2+
components:
3+
- id: F1
4+
capabilities:
5+
- id: button
6+
version: 1
7+
- id: firmwareUpdate
8+
version: 1
9+
- id: refresh
10+
version: 1
11+
categories:
12+
- name: RemoteController
13+
- id: F2
14+
capabilities:
15+
- id: button
16+
version: 1
17+
categories:
18+
- name: RemoteController
19+
- id: F3
20+
capabilities:
21+
- id: button
22+
version: 1
23+
categories:
24+
- name: RemoteController
25+
- id: F4
26+
capabilities:
27+
- id: button
28+
version: 1
29+
categories:
30+
- name: RemoteController
31+
- id: F5
32+
capabilities:
33+
- id: button
34+
version: 1
35+
categories:
36+
- name: RemoteController
37+
- id: F6
38+
capabilities:
39+
- id: button
40+
version: 1
41+
categories:
42+
- name: RemoteController
43+
- id: F7
44+
capabilities:
45+
- id: button
46+
version: 1
47+
categories:
48+
- name: RemoteController
49+
- id: F8
50+
capabilities:
51+
- id: button
52+
version: 1
53+
categories:
54+
- name: RemoteController
55+
- id: F9
56+
capabilities:
57+
- id: button
58+
version: 1
59+
categories:
60+
- name: RemoteController
61+
- id: F10
62+
capabilities:
63+
- id: button
64+
version: 1
65+
categories:
66+
- name: RemoteController
67+
- id: F11
68+
capabilities:
69+
- id: button
70+
version: 1
71+
categories:
72+
- name: RemoteController
73+
- id: F12
74+
capabilities:
75+
- id: button
76+
version: 1
77+
categories:
78+
- name: RemoteController

drivers/SmartThings/matter-switch/src/init.lua

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -703,8 +703,8 @@ local function device_init(driver, device)
703703
end
704704
for _, attr in pairs(device_type_attribute_map[id] or {}) do
705705
if id == GENERIC_SWITCH_ID and
706-
attr ~= clusters.PowerSource.attributes.BatPercentRemaining and
707-
attr ~= clusters.PowerSource.attributes.BatChargeLevel then
706+
attr ~= clusters.PowerSource.attributes.BatPercentRemaining and
707+
attr ~= clusters.PowerSource.attributes.BatChargeLevel then
708708
device:add_subscribed_event(attr)
709709
else
710710
device:add_subscribed_attribute(attr)
@@ -1641,7 +1641,8 @@ local matter_driver_template = {
16411641
},
16421642
sub_drivers = {
16431643
require("eve-energy"),
1644-
require("aqara-cube")
1644+
require("aqara-cube"),
1645+
require("third-reality-mk1")
16451646
}
16461647
}
16471648

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
-- Copyright 2025 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
local capabilities = require "st.capabilities"
16+
local clusters = require "st.matter.clusters"
17+
local dkjson = require "dkjson"
18+
local t_utils = require "integration_test.utils"
19+
local test = require "integration_test"
20+
local utils = require "st.utils"
21+
22+
local mock_device = test.mock_device.build_test_matter_device({
23+
profile = t_utils.get_profile_definition("12-button-keyboard.yml"),
24+
manufacturer_info = {vendor_id = 0x1407, product_id = 0x1388},
25+
endpoints = {
26+
{
27+
endpoint_id = 0,
28+
clusters = {
29+
{ cluster_id = clusters.Basic.ID, cluster_type = "SERVER" },
30+
},
31+
device_types = {
32+
{ device_type_id = 0x0016, device_type_revision = 1 } -- RootNode
33+
}
34+
},
35+
{
36+
endpoint_id = 1,
37+
clusters = {
38+
{
39+
cluster_id = clusters.Switch.ID,
40+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
41+
cluster_type = "SERVER"
42+
}
43+
},
44+
device_types = {
45+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
46+
}
47+
},
48+
{
49+
endpoint_id = 2,
50+
clusters = {
51+
{
52+
cluster_id = clusters.Switch.ID,
53+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
54+
cluster_type = "SERVER"
55+
}
56+
},
57+
device_types = {
58+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
59+
}
60+
},
61+
{
62+
endpoint_id = 3,
63+
clusters = {
64+
{
65+
cluster_id = clusters.Switch.ID,
66+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
67+
cluster_type = "SERVER"
68+
}
69+
},
70+
device_types = {
71+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
72+
}
73+
},
74+
{
75+
endpoint_id = 4,
76+
clusters = {
77+
{
78+
cluster_id = clusters.Switch.ID,
79+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
80+
cluster_type = "SERVER"
81+
}
82+
},
83+
device_types = {
84+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
85+
}
86+
},
87+
{
88+
endpoint_id = 5,
89+
clusters = {
90+
{
91+
cluster_id = clusters.Switch.ID,
92+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
93+
cluster_type = "SERVER"
94+
}
95+
},
96+
device_types = {
97+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
98+
}
99+
},
100+
{
101+
endpoint_id = 6,
102+
clusters = {
103+
{
104+
cluster_id = clusters.Switch.ID,
105+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
106+
cluster_type = "SERVER"
107+
}
108+
},
109+
device_types = {
110+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
111+
}
112+
},
113+
{
114+
endpoint_id = 7,
115+
clusters = {
116+
{
117+
cluster_id = clusters.Switch.ID,
118+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
119+
cluster_type = "SERVER"
120+
}
121+
},
122+
device_types = {
123+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
124+
}
125+
},
126+
{
127+
endpoint_id = 8,
128+
clusters = {
129+
{
130+
cluster_id = clusters.Switch.ID,
131+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
132+
cluster_type = "SERVER"
133+
}
134+
},
135+
device_types = {
136+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
137+
}
138+
},
139+
{
140+
endpoint_id = 9,
141+
clusters = {
142+
{
143+
cluster_id = clusters.Switch.ID,
144+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
145+
cluster_type = "SERVER"
146+
}
147+
},
148+
device_types = {
149+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
150+
}
151+
},
152+
{
153+
endpoint_id = 10,
154+
clusters = {
155+
{
156+
cluster_id = clusters.Switch.ID,
157+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
158+
cluster_type = "SERVER"
159+
}
160+
},
161+
device_types = {
162+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
163+
}
164+
},
165+
{
166+
endpoint_id = 11,
167+
clusters = {
168+
{
169+
cluster_id = clusters.Switch.ID,
170+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
171+
cluster_type = "SERVER"
172+
}
173+
},
174+
device_types = {
175+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
176+
}
177+
},
178+
{
179+
endpoint_id = 12,
180+
clusters = {
181+
{
182+
cluster_id = clusters.Switch.ID,
183+
feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH,
184+
cluster_type = "SERVER"
185+
}
186+
},
187+
device_types = {
188+
{device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch
189+
}
190+
}
191+
}
192+
})
193+
194+
local function configure_buttons()
195+
for key = 1, 12 do
196+
local component = "F" .. key
197+
test.socket.capability:__expect_send(mock_device:generate_test_message(component, capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}})))
198+
test.socket.capability:__expect_send(mock_device:generate_test_message(component, capabilities.button.button.pushed({state_change = false})))
199+
end
200+
end
201+
202+
local function test_init()
203+
local cluster_subscribe_list = {
204+
clusters.Switch.events.InitialPress
205+
}
206+
local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device)
207+
for i, clus in ipairs(cluster_subscribe_list) do
208+
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
209+
end
210+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
211+
mock_device:expect_metadata_update({ profile = "12-button-keyboard" })
212+
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
213+
configure_buttons()
214+
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
215+
test.mock_device.add_test_device(mock_device)
216+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
217+
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
218+
local device_info_copy = utils.deep_copy(mock_device.raw_st_data)
219+
device_info_copy.profile.id = "12-buttons-keyboard"
220+
local device_info_json = dkjson.encode(device_info_copy)
221+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json })
222+
configure_buttons()
223+
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
224+
end
225+
226+
test.set_test_init_function(test_init)
227+
228+
test.register_coroutine_test(
229+
"Handle single press sequence",
230+
function()
231+
for key = 1, 12 do
232+
test.socket.matter:__queue_receive({
233+
mock_device.id,
234+
clusters.Switch.events.InitialPress:build_test_event_report(mock_device, key, {new_position = 1})
235+
})
236+
test.socket.capability:__expect_send(
237+
mock_device:generate_test_message("F" .. key, capabilities.button.button.pushed({state_change = true}))
238+
)
239+
end
240+
end
241+
)
242+
243+
-- run the tests
244+
test.run_registered_tests()

0 commit comments

Comments
 (0)