Skip to content

Commit ae3bd7f

Browse files
Use fanSpeedPercent for thermostats
This change adds the fanSpeedPercent capability to the thermostat-modular profile. Additionally, PercentSetting is subscribed in addition to 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 PercentCurrent is still subscribed to, but its attribute handler is gated on the current fan mode being AUTO, in which case PercentSetting is NULL on the device side and PercentCurrent is used as a fallback for setting the capability.
1 parent bcaa370 commit ae3bd7f

11 files changed

+130
-118
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: 17 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,8 @@ local subscribed_attributes = {
201198
clusters.FanControl.attributes.FanMode
202199
},
203200
[capabilities.fanSpeedPercent.ID] = {
204-
clusters.FanControl.attributes.PercentCurrent
201+
clusters.FanControl.attributes.PercentCurrent,
202+
clusters.FanControl.attributes.PercentSetting
205203
},
206204
[capabilities.windMode.ID] = {
207205
clusters.FanControl.attributes.WindSupport,
@@ -1012,6 +1010,7 @@ local function match_modular_profile_thermostat(driver, device)
10121010

10131011
if #fan_eps > 0 then
10141012
table.insert(main_component_capabilities, capabilities.fanMode.ID)
1013+
table.insert(main_component_capabilities, capabilities.fanSpeedPercent.ID)
10151014
end
10161015
if #rock_eps > 0 then
10171016
table.insert(main_component_capabilities, capabilities.fanOscillationMode.ID)
@@ -1613,11 +1612,19 @@ local function fan_mode_sequence_handler(driver, device, ib, response)
16131612
end
16141613

16151614
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)
1615+
local thermostat_mode = device:get_latest_state(
1616+
device:endpoint_to_component(ib.endpoint_id),
1617+
capabilities.thermostatMode.ID,
1618+
capabilities.thermostatMode.thermostatMode.NAME
1619+
)
1620+
if thermostat_mode == capabilities.thermostatMode.thermostatMode.auto() then
1621+
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(ib.data.value))
16191622
end
1620-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(speed))
1623+
end
1624+
1625+
local function fan_speed_setting_attr_handler(driver, device, ib, response)
1626+
if ib.data.value == nil then return end
1627+
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(ib.data.value))
16211628
end
16221629

16231630
local function wind_support_handler(driver, device, ib, response)
@@ -2125,6 +2132,7 @@ local matter_driver_template = {
21252132
[clusters.FanControl.attributes.FanModeSequence.ID] = fan_mode_sequence_handler,
21262133
[clusters.FanControl.attributes.FanMode.ID] = fan_mode_handler,
21272134
[clusters.FanControl.attributes.PercentCurrent.ID] = fan_speed_percent_attr_handler,
2135+
[clusters.FanControl.attributes.PercentSetting.ID] = fan_speed_setting_attr_handler,
21282136
[clusters.FanControl.attributes.WindSupport.ID] = wind_support_handler,
21292137
[clusters.FanControl.attributes.WindSetting.ID] = wind_setting_handler,
21302138
[clusters.FanControl.attributes.RockSupport.ID] = rock_support_handler,

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

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ local cluster_subscribe_list = {
277277
clusters.FanControl.attributes.FanModeSequence,
278278
clusters.FanControl.attributes.FanMode,
279279
clusters.FanControl.attributes.PercentCurrent,
280+
clusters.FanControl.attributes.PercentSetting,
280281
clusters.FanControl.attributes.WindSupport,
281282
clusters.FanControl.attributes.WindSetting,
282283
clusters.HepaFilterMonitoring.attributes.ChangeIndication,
@@ -289,6 +290,7 @@ local cluster_subscribe_list_rock = {
289290
clusters.FanControl.attributes.FanModeSequence,
290291
clusters.FanControl.attributes.FanMode,
291292
clusters.FanControl.attributes.PercentCurrent,
293+
clusters.FanControl.attributes.PercentSetting,
292294
clusters.FanControl.attributes.WindSupport,
293295
clusters.FanControl.attributes.WindSetting,
294296
clusters.FanControl.attributes.RockSupport,
@@ -327,7 +329,8 @@ local cluster_subscribe_list_configured = {
327329
clusters.FanControl.attributes.FanMode
328330
},
329331
[capabilities.fanSpeedPercent.ID] = {
330-
clusters.FanControl.attributes.PercentCurrent
332+
clusters.FanControl.attributes.PercentCurrent,
333+
clusters.FanControl.attributes.PercentSetting
331334
},
332335
[capabilities.windMode.ID] = {
333336
clusters.FanControl.attributes.WindSupport,
@@ -581,7 +584,7 @@ test.register_message_test(
581584
direction = "receive",
582585
message = {
583586
mock_device.id,
584-
clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 10)
587+
clusters.FanControl.attributes.PercentSetting:build_test_report_data(mock_device, 1, 10)
585588
}
586589
},
587590
{
@@ -764,26 +767,6 @@ test.register_message_test(
764767
}
765768
)
766769

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-
787770
local supportedFanRock = {
788771
capabilities.fanOscillationMode.fanOscillationMode.off.NAME,
789772
capabilities.fanOscillationMode.fanOscillationMode.horizontal.NAME,

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

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ local cluster_subscribe_list = {
277277
clusters.FanControl.attributes.FanModeSequence,
278278
clusters.FanControl.attributes.FanMode,
279279
clusters.FanControl.attributes.PercentCurrent,
280+
clusters.FanControl.attributes.PercentSetting,
280281
clusters.FanControl.attributes.WindSupport,
281282
clusters.FanControl.attributes.WindSetting,
282283
clusters.HepaFilterMonitoring.attributes.ChangeIndication,
@@ -289,6 +290,7 @@ local cluster_subscribe_list_rock = {
289290
clusters.FanControl.attributes.FanModeSequence,
290291
clusters.FanControl.attributes.FanMode,
291292
clusters.FanControl.attributes.PercentCurrent,
293+
clusters.FanControl.attributes.PercentSetting,
292294
clusters.FanControl.attributes.WindSupport,
293295
clusters.FanControl.attributes.WindSetting,
294296
clusters.FanControl.attributes.RockSupport,
@@ -327,7 +329,8 @@ local cluster_subscribe_list_configured = {
327329
clusters.FanControl.attributes.FanMode
328330
},
329331
[capabilities.fanSpeedPercent.ID] = {
330-
clusters.FanControl.attributes.PercentCurrent
332+
clusters.FanControl.attributes.PercentCurrent,
333+
clusters.FanControl.attributes.PercentSetting
331334
},
332335
[capabilities.windMode.ID] = {
333336
clusters.FanControl.attributes.WindSupport,
@@ -581,7 +584,7 @@ test.register_message_test(
581584
direction = "receive",
582585
message = {
583586
mock_device.id,
584-
clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 10)
587+
clusters.FanControl.attributes.PercentSetting:build_test_report_data(mock_device, 1, 10)
585588
}
586589
},
587590
{
@@ -765,26 +768,6 @@ test.register_message_test(
765768
}
766769
)
767770

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-
788771
local supportedFanRock = {
789772
capabilities.fanOscillationMode.fanOscillationMode.off.NAME,
790773
capabilities.fanOscillationMode.fanOscillationMode.horizontal.NAME,

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ local cluster_subscribe_list = {
145145
clusters.FanControl.attributes.FanModeSequence,
146146
clusters.FanControl.attributes.FanMode,
147147
clusters.FanControl.attributes.PercentCurrent,
148+
clusters.FanControl.attributes.PercentSetting,
148149
clusters.FanControl.attributes.WindSupport,
149150
clusters.FanControl.attributes.WindSetting,
150151
clusters.HepaFilterMonitoring.attributes.ChangeIndication,
@@ -181,7 +182,8 @@ local cluster_subscribe_list_configured = {
181182
clusters.FanControl.attributes.FanMode
182183
},
183184
[capabilities.fanSpeedPercent.ID] = {
184-
clusters.FanControl.attributes.PercentCurrent
185+
clusters.FanControl.attributes.PercentCurrent,
186+
clusters.FanControl.attributes.PercentSetting
185187
},
186188
[capabilities.windMode.ID] = {
187189
clusters.FanControl.attributes.WindSupport,

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

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,79 +13,80 @@
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.PercentCurrent,
78+
clusters.FanControl.attributes.PercentSetting,
79+
clusters.FanControl.attributes.WindSupport,
80+
clusters.FanControl.attributes.WindSetting,
81+
clusters.FanControl.attributes.RockSupport,
82+
clusters.FanControl.attributes.RockSetting,
8383
}
8484

8585
local cluster_subscribe_list_generic = {
86-
clusters.FanControl.attributes.FanMode,
87-
clusters.FanControl.attributes.FanModeSequence,
88-
clusters.FanControl.attributes.PercentCurrent,
86+
clusters.FanControl.attributes.FanMode,
87+
clusters.FanControl.attributes.FanModeSequence,
88+
clusters.FanControl.attributes.PercentCurrent,
89+
clusters.FanControl.attributes.PercentSetting,
8990
}
9091

9192
local function test_init()

0 commit comments

Comments
 (0)