Skip to content

Commit f34c7dd

Browse files
Use fanSpeedPercent for thermostats
This change adds the fanSpeedPercent capability to the thermostat-modular profile. Additionally, PercentSetting is subscribed to over PercentCurrent to set this capability, since it provides an accurate representation of the speed of the fan while helping avoid the following situation: (1) The fan speed is changed in the app and PercentSetting is routed to the device (2) The fan reports back a value of PercentCurrent that doesn't match PercentSetting because the speed takes a little while to change (3) The fanSpeedPercent capability jumps to the value reported by PercentCurrent
1 parent bcaa370 commit f34c7dd

11 files changed

+120
-123
lines changed

drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ components:
99
- id: fanMode
1010
version: 1
1111
optional: true
12+
- id: fanSpeedPercent
13+
version: 1
14+
optional: true
1215
- id: fanOscillationMode
1316
version: 1
1417
optional: true

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@ local log = require "log"
1717
local clusters = require "st.matter.clusters"
1818
local embedded_cluster_utils = require "embedded-cluster-utils"
1919
local im = require "st.matter.interaction_model"
20-
2120
local MatterDriver = require "st.matter.driver"
2221
local utils = require "st.utils"
22+
local version = require "version"
2323

2424
local SUPPORTED_COMPONENT_CAPABILITIES = "__supported_component_capabilities"
2525
-- declare match_profile function for use throughout file
2626
local match_profile
2727

2828
-- Include driver-side definitions when lua libs api version is < 10
29-
local version = require "version"
3029
if version.api < 10 then
3130
clusters.HepaFilterMonitoring = require "HepaFilterMonitoring"
3231
clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring"
@@ -100,8 +99,6 @@ local HEAT_PUMP_DEVICE_TYPE_ID = 0x0309
10099
local THERMOSTAT_DEVICE_TYPE_ID = 0x0301
101100
local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510
102101

103-
local MIN_ALLOWED_PERCENT_VALUE = 0
104-
local MAX_ALLOWED_PERCENT_VALUE = 100
105102
local DEFAULT_REPORT_TIME_INTERVAL = 15 * 60 -- Report cumulative energy every 15 minutes
106103
local MAX_REPORT_TIMEOUT = 30 * 60
107104
local POLL_INTERVAL = 60 -- To read CumulativeEnergyImported every 60 seconds.
@@ -201,7 +198,7 @@ local subscribed_attributes = {
201198
clusters.FanControl.attributes.FanMode
202199
},
203200
[capabilities.fanSpeedPercent.ID] = {
204-
clusters.FanControl.attributes.PercentCurrent
201+
clusters.FanControl.attributes.PercentSetting
205202
},
206203
[capabilities.windMode.ID] = {
207204
clusters.FanControl.attributes.WindSupport,
@@ -1012,6 +1009,7 @@ local function match_modular_profile_thermostat(driver, device)
10121009

10131010
if #fan_eps > 0 then
10141011
table.insert(main_component_capabilities, capabilities.fanMode.ID)
1012+
table.insert(main_component_capabilities, capabilities.fanSpeedPercent.ID)
10151013
end
10161014
if #rock_eps > 0 then
10171015
table.insert(main_component_capabilities, capabilities.fanOscillationMode.ID)
@@ -1613,11 +1611,23 @@ local function fan_mode_sequence_handler(driver, device, ib, response)
16131611
end
16141612

16151613
local function fan_speed_percent_attr_handler(driver, device, ib, response)
1616-
local speed = 0
1617-
if ib.data.value ~= nil then
1618-
speed = utils.clamp_value(ib.data.value, MIN_ALLOWED_PERCENT_VALUE, MAX_ALLOWED_PERCENT_VALUE)
1614+
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(ib.data.value))
1615+
end
1616+
1617+
local function fan_speed_setting_attr_handler(driver, device, ib, response)
1618+
for _, resp in ipairs(response) do
1619+
if resp.info_block.attribute.ID == clusters.FanControl.attributes.PercentSetting.ID then
1620+
if resp.status == im.InteractionResponse.Status.SUCCESS then
1621+
break
1622+
else
1623+
-- If the fan was not able to be changed to the requested value, read PercentCurrent
1624+
-- in order to update the fanSpeedPercent capability to the current value.
1625+
device:send(clusters.FanControl.attributes.PercentCurrent:read(device))
1626+
return
1627+
end
1628+
end
16191629
end
1620-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(speed))
1630+
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(ib.data.value))
16211631
end
16221632

16231633
local function wind_support_handler(driver, device, ib, response)
@@ -2125,6 +2135,7 @@ local matter_driver_template = {
21252135
[clusters.FanControl.attributes.FanModeSequence.ID] = fan_mode_sequence_handler,
21262136
[clusters.FanControl.attributes.FanMode.ID] = fan_mode_handler,
21272137
[clusters.FanControl.attributes.PercentCurrent.ID] = fan_speed_percent_attr_handler,
2138+
[clusters.FanControl.attributes.PercentSetting.ID] = fan_speed_setting_attr_handler,
21282139
[clusters.FanControl.attributes.WindSupport.ID] = wind_support_handler,
21292140
[clusters.FanControl.attributes.WindSetting.ID] = wind_setting_handler,
21302141
[clusters.FanControl.attributes.RockSupport.ID] = rock_support_handler,

drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ local mock_device_ap_thermo_aqs_preconfigured = test.mock_device.build_test_matt
276276
local cluster_subscribe_list = {
277277
clusters.FanControl.attributes.FanModeSequence,
278278
clusters.FanControl.attributes.FanMode,
279-
clusters.FanControl.attributes.PercentCurrent,
279+
clusters.FanControl.attributes.PercentSetting,
280280
clusters.FanControl.attributes.WindSupport,
281281
clusters.FanControl.attributes.WindSetting,
282282
clusters.HepaFilterMonitoring.attributes.ChangeIndication,
@@ -288,7 +288,7 @@ local cluster_subscribe_list = {
288288
local cluster_subscribe_list_rock = {
289289
clusters.FanControl.attributes.FanModeSequence,
290290
clusters.FanControl.attributes.FanMode,
291-
clusters.FanControl.attributes.PercentCurrent,
291+
clusters.FanControl.attributes.PercentSetting,
292292
clusters.FanControl.attributes.WindSupport,
293293
clusters.FanControl.attributes.WindSetting,
294294
clusters.FanControl.attributes.RockSupport,
@@ -327,7 +327,7 @@ local cluster_subscribe_list_configured = {
327327
clusters.FanControl.attributes.FanMode
328328
},
329329
[capabilities.fanSpeedPercent.ID] = {
330-
clusters.FanControl.attributes.PercentCurrent
330+
clusters.FanControl.attributes.PercentSetting
331331
},
332332
[capabilities.windMode.ID] = {
333333
clusters.FanControl.attributes.WindSupport,
@@ -581,7 +581,7 @@ test.register_message_test(
581581
direction = "receive",
582582
message = {
583583
mock_device.id,
584-
clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 10)
584+
clusters.FanControl.attributes.PercentSetting:build_test_report_data(mock_device, 1, 10)
585585
}
586586
},
587587
{
@@ -764,26 +764,6 @@ test.register_message_test(
764764
}
765765
)
766766

767-
test.register_message_test(
768-
"Set percent command should clamp invalid percentage values",
769-
{
770-
{
771-
channel = "matter",
772-
direction = "receive",
773-
message = {
774-
mock_device.id,
775-
clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 255)
776-
}
777-
},
778-
{
779-
channel = "capability",
780-
direction = "send",
781-
message = mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(100))
782-
},
783-
}
784-
)
785-
786-
787767
local supportedFanRock = {
788768
capabilities.fanOscillationMode.fanOscillationMode.off.NAME,
789769
capabilities.fanOscillationMode.fanOscillationMode.horizontal.NAME,

drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ local mock_device_ap_thermo_aqs_preconfigured = test.mock_device.build_test_matt
276276
local cluster_subscribe_list = {
277277
clusters.FanControl.attributes.FanModeSequence,
278278
clusters.FanControl.attributes.FanMode,
279-
clusters.FanControl.attributes.PercentCurrent,
279+
clusters.FanControl.attributes.PercentSetting,
280280
clusters.FanControl.attributes.WindSupport,
281281
clusters.FanControl.attributes.WindSetting,
282282
clusters.HepaFilterMonitoring.attributes.ChangeIndication,
@@ -288,7 +288,7 @@ local cluster_subscribe_list = {
288288
local cluster_subscribe_list_rock = {
289289
clusters.FanControl.attributes.FanModeSequence,
290290
clusters.FanControl.attributes.FanMode,
291-
clusters.FanControl.attributes.PercentCurrent,
291+
clusters.FanControl.attributes.PercentSetting,
292292
clusters.FanControl.attributes.WindSupport,
293293
clusters.FanControl.attributes.WindSetting,
294294
clusters.FanControl.attributes.RockSupport,
@@ -327,7 +327,7 @@ local cluster_subscribe_list_configured = {
327327
clusters.FanControl.attributes.FanMode
328328
},
329329
[capabilities.fanSpeedPercent.ID] = {
330-
clusters.FanControl.attributes.PercentCurrent
330+
clusters.FanControl.attributes.PercentSetting
331331
},
332332
[capabilities.windMode.ID] = {
333333
clusters.FanControl.attributes.WindSupport,
@@ -581,7 +581,7 @@ test.register_message_test(
581581
direction = "receive",
582582
message = {
583583
mock_device.id,
584-
clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 10)
584+
clusters.FanControl.attributes.PercentSetting:build_test_report_data(mock_device, 1, 10)
585585
}
586586
},
587587
{
@@ -765,26 +765,6 @@ test.register_message_test(
765765
}
766766
)
767767

768-
test.register_message_test(
769-
"Set percent command should clamp invalid percentage values",
770-
{
771-
{
772-
channel = "matter",
773-
direction = "receive",
774-
message = {
775-
mock_device.id,
776-
clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 255)
777-
}
778-
},
779-
{
780-
channel = "capability",
781-
direction = "send",
782-
message = mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(100))
783-
}
784-
}
785-
)
786-
787-
788768
local supportedFanRock = {
789769
capabilities.fanOscillationMode.fanOscillationMode.off.NAME,
790770
capabilities.fanOscillationMode.fanOscillationMode.horizontal.NAME,

drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ local mock_device_ap_thermo_aqs = test.mock_device.build_test_matter_device({
144144
local cluster_subscribe_list = {
145145
clusters.FanControl.attributes.FanModeSequence,
146146
clusters.FanControl.attributes.FanMode,
147-
clusters.FanControl.attributes.PercentCurrent,
147+
clusters.FanControl.attributes.PercentSetting,
148148
clusters.FanControl.attributes.WindSupport,
149149
clusters.FanControl.attributes.WindSetting,
150150
clusters.HepaFilterMonitoring.attributes.ChangeIndication,
@@ -181,7 +181,7 @@ local cluster_subscribe_list_configured = {
181181
clusters.FanControl.attributes.FanMode
182182
},
183183
[capabilities.fanSpeedPercent.ID] = {
184-
clusters.FanControl.attributes.PercentCurrent
184+
clusters.FanControl.attributes.PercentSetting
185185
},
186186
[capabilities.windMode.ID] = {
187187
clusters.FanControl.attributes.WindSupport,

drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,79 +13,78 @@
1313
-- limitations under the License.
1414
local test = require "integration_test"
1515
local t_utils = require "integration_test.utils"
16-
1716
local clusters = require "st.matter.clusters"
1817

1918
local mock_device = test.mock_device.build_test_matter_device({
20-
profile = t_utils.get_profile_definition("fan-rock-wind.yml"),
21-
manufacturer_info = {
22-
vendor_id = 0x0000,
23-
product_id = 0x0000,
19+
profile = t_utils.get_profile_definition("fan-rock-wind.yml"),
20+
manufacturer_info = {
21+
vendor_id = 0x0000,
22+
product_id = 0x0000,
23+
},
24+
endpoints = {
25+
{
26+
endpoint_id = 0,
27+
clusters = {
28+
{cluster_id = clusters.Basic.ID, cluster_type = "SERVER"},
29+
},
30+
device_types = {
31+
{device_type_id = 0x0016, device_type_revision = 1,} -- RootNode
32+
}
2433
},
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+
endpoint_id = 1,
36+
clusters = {
37+
{cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15},
3438
},
35-
{
36-
endpoint_id = 1,
37-
clusters = {
38-
{cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15},
39-
},
40-
device_types = {
41-
{device_type_id = 0x002B, device_type_revision = 1,} -- Fan
42-
}
39+
device_types = {
40+
{device_type_id = 0x002B, device_type_revision = 1,} -- Fan
4341
}
4442
}
43+
}
4544
})
4645

4746
local mock_device_generic = test.mock_device.build_test_matter_device({
48-
profile = t_utils.get_profile_definition("fan-generic.yml"),
49-
manufacturer_info = {
50-
vendor_id = 0x0000,
51-
product_id = 0x0000,
47+
profile = t_utils.get_profile_definition("fan-generic.yml"),
48+
manufacturer_info = {
49+
vendor_id = 0x0000,
50+
product_id = 0x0000,
51+
},
52+
endpoints = {
53+
{
54+
endpoint_id = 0,
55+
clusters = {
56+
{cluster_id = clusters.Basic.ID, cluster_type = "SERVER"},
57+
},
58+
device_types = {
59+
{device_type_id = 0x0016, device_type_revision = 1,} -- RootNode
60+
}
5261
},
53-
endpoints = {
54-
{
55-
endpoint_id = 0,
56-
clusters = {
57-
{cluster_id = clusters.Basic.ID, cluster_type = "SERVER"},
58-
},
59-
device_types = {
60-
{device_type_id = 0x0016, device_type_revision = 1,} -- RootNode
61-
}
62+
{
63+
endpoint_id = 1,
64+
clusters = {
65+
{cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 0},
6266
},
63-
{
64-
endpoint_id = 1,
65-
clusters = {
66-
{cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 0},
67-
},
68-
device_types = {
69-
{device_type_id = 0x002B, device_type_revision = 1,} -- Fan
70-
}
67+
device_types = {
68+
{device_type_id = 0x002B, device_type_revision = 1,} -- Fan
7169
}
7270
}
71+
}
7372
})
7473

7574
local cluster_subscribe_list = {
76-
clusters.FanControl.attributes.FanMode,
77-
clusters.FanControl.attributes.FanModeSequence,
78-
clusters.FanControl.attributes.PercentCurrent,
79-
clusters.FanControl.attributes.WindSupport,
80-
clusters.FanControl.attributes.WindSetting,
81-
clusters.FanControl.attributes.RockSupport,
82-
clusters.FanControl.attributes.RockSetting,
75+
clusters.FanControl.attributes.FanMode,
76+
clusters.FanControl.attributes.FanModeSequence,
77+
clusters.FanControl.attributes.PercentSetting,
78+
clusters.FanControl.attributes.WindSupport,
79+
clusters.FanControl.attributes.WindSetting,
80+
clusters.FanControl.attributes.RockSupport,
81+
clusters.FanControl.attributes.RockSetting,
8382
}
8483

8584
local cluster_subscribe_list_generic = {
86-
clusters.FanControl.attributes.FanMode,
87-
clusters.FanControl.attributes.FanModeSequence,
88-
clusters.FanControl.attributes.PercentCurrent,
85+
clusters.FanControl.attributes.FanMode,
86+
clusters.FanControl.attributes.FanModeSequence,
87+
clusters.FanControl.attributes.PercentSetting,
8988
}
9089

9190
local function test_init()

0 commit comments

Comments
 (0)