Skip to content

Commit eb2feac

Browse files
authored
Merge pull request #2464 from SmartThingsCommunity/main
2 parents a2f59f9 + 07532ad commit eb2feac

File tree

52 files changed

+847
-275
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+847
-275
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ lua_libs-api_*
77
tools/test_output/*
88
tools/coverage_output/*
99
.DS_Store
10+
.venv/

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

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ if version.api < 12 then
3434
end
3535

3636
local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map"
37-
local SUPPORTED_EVSE_MODES_MAP = "__supported_evse_modes_map"
38-
local SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP = "__supported_device_energy_management_modes_map"
37+
local SUPPORTED_EVSE_MODES = "__supported_evse_modes"
38+
local SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES = "__supported_device_energy_management_modes"
3939

4040
local CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported"
4141
local LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp"
@@ -47,6 +47,11 @@ local TOTAL_CUMULATIVE_ENERGY_IMPORTED = "__total_cumulative_energy_imported"
4747
local TOTAL_CUMULATIVE_ENERGY_EXPORTED = "__total_cumulative_energy_exported"
4848
local TOTAL_ACTIVE_POWER = "__total_active_power"
4949

50+
local updated_fields = {
51+
{ current_field_name = "__supported_evse_modes_map", updated_field_name = nil },
52+
{ current_field_name = "__supported_device_energy_management_modes_map", updated_field_name = nil }
53+
}
54+
5055
local MAX_CHARGING_CURRENT_CONSTRAINT = 80000 -- In v1.3 release of stack, this check for 80 A is performed.
5156

5257
local EVSE_DEVICE_TYPE_ID = 0x050C
@@ -55,7 +60,6 @@ local BATTERY_STORAGE_DEVICE_TYPE_ID = 0x0018
5560
local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510
5661
local DEVICE_ENERGY_MANAGEMENT_DEVICE_TYPE_ID = 0x050D
5762

58-
5963
local function get_endpoints_for_dt(device, device_type)
6064
local endpoints = {}
6165
for _, ep in ipairs(device.endpoints) do
@@ -100,6 +104,25 @@ local function component_to_endpoint(device, component)
100104
end
101105
end
102106

107+
local function get_field_for_endpoint(device, field, endpoint)
108+
return device:get_field(string.format("%s_%d", field, endpoint))
109+
end
110+
111+
local function set_field_for_endpoint(device, field, endpoint, value, additional_params)
112+
device:set_field(string.format("%s_%d", field, endpoint), value, additional_params)
113+
end
114+
115+
local function check_field_name_updates(device)
116+
for _, field in ipairs(updated_fields) do
117+
if device:get_field(field.current_field_name) then
118+
if field.updated_field_name ~= nil then
119+
device:set_field(field.updated_field_name, device:get_field(field.current_field_name), {persist = true})
120+
end
121+
device:set_field(field.current_field_name, nil)
122+
end
123+
end
124+
end
125+
103126
local function time_zone_offset()
104127
return os.difftime(os.time(), os.time(os.date("!*t", os.time())))
105128
end
@@ -193,6 +216,7 @@ local BATTERY_CHARGING_STATE_MAP = {
193216

194217
-- Lifecycle Handlers --
195218
local function device_init(driver, device)
219+
check_field_name_updates(device)
196220
device:subscribe()
197221
device:set_endpoint_to_component_fn(endpoint_to_component)
198222
device:set_component_to_endpoint_fn(component_to_endpoint)
@@ -386,27 +410,22 @@ local function power_mode_handler(driver, device, ib, response)
386410
end
387411

388412
local function energy_evse_supported_modes_attr_handler(driver, device, ib, response)
389-
local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) or {}
390413
local supportedEvseModes = {}
391414
for _, mode in ipairs(ib.data.elements) do
392415
if version.api < 11 then
393416
clusters.EnergyEvseMode.types.ModeOptionStruct:augment_type(mode)
394417
end
395418
table.insert(supportedEvseModes, mode.elements.label.value)
396419
end
397-
supportedEvseModesMap[ib.endpoint_id] = supportedEvseModes
398-
device:set_field(SUPPORTED_EVSE_MODES_MAP, supportedEvseModesMap, { persist = true })
420+
set_field_for_endpoint(device, SUPPORTED_EVSE_MODES, ib.endpoint_id, supportedEvseModes, { persist = true })
399421
local event = capabilities.mode.supportedModes(supportedEvseModes, { visibility = { displayed = false } })
400422
device:emit_event_for_endpoint(ib.endpoint_id, event)
401423
event = capabilities.mode.supportedArguments(supportedEvseModes, { visibility = { displayed = false } })
402424
device:emit_event_for_endpoint(ib.endpoint_id, event)
403425
end
404426

405427
local function energy_evse_mode_attr_handler(driver, device, ib, response)
406-
device.log.info(string.format("energy_evse_modes_attr_handler currentMode: %s", ib.data.value))
407-
408-
local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) or {}
409-
local supportedEvseModes = supportedEvseModesMap[ib.endpoint_id] or {}
428+
local supportedEvseModes = get_field_for_endpoint(device, SUPPORTED_EVSE_MODES, ib.endpoint_id) or {}
410429
local currentMode = ib.data.value
411430
for i, mode in ipairs(supportedEvseModes) do
412431
if i - 1 == currentMode then
@@ -417,27 +436,22 @@ local function energy_evse_mode_attr_handler(driver, device, ib, response)
417436
end
418437

419438
local function device_energy_mgmt_supported_modes_attr_handler(driver, device, ib, response)
420-
local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) or {}
421439
local supportedDeviceEnergyMgmtModes = {}
422440
for _, mode in ipairs(ib.data.elements) do
423441
if version.api < 12 then
424442
clusters.DeviceEnergyManagementMode.types.ModeOptionStruct:augment_type(mode)
425443
end
426444
table.insert(supportedDeviceEnergyMgmtModes, mode.elements.label.value)
427445
end
428-
supportedDeviceEnergyMgmtModesMap[ib.endpoint_id] = supportedDeviceEnergyMgmtModes
429-
device:set_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP, supportedDeviceEnergyMgmtModesMap, { persist = true })
446+
set_field_for_endpoint(device, SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES, ib.endpoint_id, supportedDeviceEnergyMgmtModes, { persist = true })
430447
local event = capabilities.mode.supportedModes(supportedDeviceEnergyMgmtModes, { visibility = { displayed = false } })
431448
device:emit_event_for_endpoint(ib.endpoint_id, event)
432449
event = capabilities.mode.supportedArguments(supportedDeviceEnergyMgmtModes, { visibility = { displayed = false } })
433450
device:emit_event_for_endpoint(ib.endpoint_id, event)
434451
end
435452

436453
local function device_energy_mgmt_mode_attr_handler(driver, device, ib, response)
437-
device.log.info(string.format("device_energy_mgmt_mode_attr_handler currentMode: %s", ib.data.value))
438-
439-
local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) or {}
440-
local supportedDeviceEnergyMgmtModes = supportedDeviceEnergyMgmtModesMap[ib.endpoint_id] or {}
454+
local supportedDeviceEnergyMgmtModes = get_field_for_endpoint(device, SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES, ib.endpoint_id) or {}
441455
local currentMode = ib.data.value
442456
for i, mode in ipairs(supportedDeviceEnergyMgmtModes) do
443457
if i - 1 == currentMode then
@@ -603,8 +617,7 @@ local function handle_set_mode_command(driver, device, cmd)
603617
local set_mode_handlers = {
604618
["main"] = function( ... )
605619
local ep = component_to_endpoint(device, cmd.component)
606-
local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP)
607-
local supportedEvseModes = supportedEvseModesMap[ep] or {}
620+
local supportedEvseModes = get_field_for_endpoint(device, SUPPORTED_EVSE_MODES, ep) or {}
608621
for i, mode in ipairs(supportedEvseModes) do
609622
if cmd.args.mode == mode then
610623
device:send(clusters.EnergyEvseMode.commands.ChangeToMode(device, ep, i - 1))
@@ -615,8 +628,7 @@ local function handle_set_mode_command(driver, device, cmd)
615628
end,
616629
["deviceEnergyManagement"] = function( ... )
617630
local ep = component_to_endpoint(device, cmd.component)
618-
local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP)
619-
local supportedDeviceEnergyMgmtModes = supportedDeviceEnergyMgmtModesMap[ep] or {}
631+
local supportedDeviceEnergyMgmtModes = get_field_for_endpoint(device, SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES, ep) or {}
620632
for i, mode in ipairs(supportedDeviceEnergyMgmtModes) do
621633
if cmd.args.mode == mode then
622634
device:send(clusters.DeviceEnergyManagementMode.commands.ChangeToMode(device, ep, i - 1))

drivers/SmartThings/matter-window-covering/profiles/window-covering-battery.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,5 @@ components:
1717
categories:
1818
- name: Blind
1919
preferences:
20-
- preferenceId: presetPosition
21-
explicit: true
2220
- preferenceId: reverse
2321
explicit: true

drivers/SmartThings/matter-window-covering/profiles/window-covering-batteryLevel.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,5 @@ components:
1717
categories:
1818
- name: Blind
1919
preferences:
20-
- preferenceId: presetPosition
21-
explicit: true
2220
- preferenceId: reverse
2321
explicit: true

drivers/SmartThings/matter-window-covering/profiles/window-covering-profile.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,5 @@ components:
1818
categories:
1919
- name: Blind
2020
preferences:
21-
- preferenceId: presetPosition
22-
explicit: true
2321
- preferenceId: reverse
2422
explicit: true

drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt-battery.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,5 @@ components:
1919
categories:
2020
- name: Blind
2121
preferences:
22-
- preferenceId: presetPosition
23-
explicit: true
2422
- preferenceId: reverse
2523
explicit: true

drivers/SmartThings/matter-window-covering/profiles/window-covering-tilt.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,5 @@ components:
1717
categories:
1818
- name: Blind
1919
preferences:
20-
- preferenceId: presetPosition
21-
explicit: true
2220
- preferenceId: reverse
2321
explicit: true

drivers/SmartThings/matter-window-covering/profiles/window-covering.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,5 @@ components:
1515
categories:
1616
- name: Blind
1717
preferences:
18-
- preferenceId: presetPosition
19-
explicit: true
2018
- preferenceId: reverse
2119
explicit: true

drivers/SmartThings/matter-window-covering/src/init.lua

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ local battery_support = {
2828
BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE"
2929
}
3030
local REVERSE_POLARITY = "__reverse_polarity"
31+
local PRESET_LEVEL_KEY = "__preset_level_key"
32+
local PRESET_LEVEL = 50
3133

3234
local function find_default_endpoint(device, cluster)
3335
local res = device.MATTER_DEFAULT_ENDPOINT
@@ -69,6 +71,17 @@ end
6971

7072
local function device_init(driver, device)
7173
device:set_component_to_endpoint_fn(component_to_endpoint)
74+
if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
75+
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME) == nil then
76+
-- These should only ever be nil once (and at the same time) for already-installed devices
77+
-- It can be removed after migration is complete
78+
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed = false}}))
79+
local preset_position = device:get_field(PRESET_LEVEL_KEY) or
80+
(device.preferences ~= nil and device.preferences.presetPosition) or
81+
PRESET_LEVEL
82+
device:emit_event(capabilities.windowShadePreset.position(preset_position, {visibility = {displayed = false}}))
83+
device:set_field(PRESET_LEVEL_KEY, preset_position, {persist = true})
84+
end
7285
device:subscribe()
7386
end
7487

@@ -122,22 +135,29 @@ local function handle_preset(driver, device, cmd)
122135
local lift_eps = device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.LIFT})
123136
local tilt_eps = device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.TILT})
124137
if #lift_eps > 0 then
125-
local lift_value = 100 - device.preferences.presetPosition
126-
local hundredths_lift_percent = lift_value * 100
138+
local lift_value = device:get_latest_state(
139+
"main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME
140+
) or PRESET_LEVEL
141+
local hundredths_lift_percent = (100 - lift_value) * 100
127142
local req = clusters.WindowCovering.server.commands.GoToLiftPercentage(
128143
device, endpoint_id, hundredths_lift_percent
129144
)
130145
device:send(req)
131146
end
132147
if #tilt_eps > 0 then
133-
-- Use default preset tilt percentage to 50 until a canonical preference is created for preset tilt position
134148
local req = clusters.WindowCovering.server.commands.GoToTiltPercentage(
135-
device, endpoint_id, 50 * 100
149+
device, endpoint_id, PRESET_LEVEL * 100
136150
)
137151
device:send(req)
138152
end
139153
end
140154

155+
local function handle_set_preset(driver, device, cmd)
156+
local endpoint_id = device:component_to_endpoint(cmd.component)
157+
device:set_field(PRESET_LEVEL_KEY, cmd.args.position)
158+
device:emit_event_for_endpoint(endpoint_id, capabilities.windowShadePreset.position(cmd.args.position))
159+
end
160+
141161
-- close covering
142162
local function handle_close(driver, device, cmd)
143163
local endpoint_id = device:component_to_endpoint(cmd.component)
@@ -345,6 +365,7 @@ local matter_driver_template = {
345365
capability_handlers = {
346366
[capabilities.windowShadePreset.ID] = {
347367
[capabilities.windowShadePreset.commands.presetPosition.NAME] = handle_preset,
368+
[capabilities.windowShadePreset.commands.setPresetPosition.NAME] = handle_set_preset,
348369
},
349370
[capabilities.windowShade.ID] = {
350371
[capabilities.windowShade.commands.close.NAME] = handle_close,

drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ local mock_device = test.mock_device.build_test_matter_device(
2626
{
2727
profile = t_utils.get_profile_definition("window-covering-tilt-battery.yml"),
2828
manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000},
29-
preferences = { presetPosition = 30 },
3029
endpoints = {
3130
{
3231
endpoint_id = 2,
@@ -58,7 +57,6 @@ local mock_device_mains_powered = test.mock_device.build_test_matter_device(
5857
{
5958
profile = t_utils.get_profile_definition("window-covering.yml"),
6059
manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000},
61-
preferences = { presetPosition = 30 },
6260
endpoints = {
6361
{
6462
endpoint_id = 2,
@@ -100,6 +98,19 @@ local CLUSTER_SUBSCRIBE_LIST_NO_BATTERY = {
10098
WindowCovering.server.attributes.OperationalStatus,
10199
}
102100

101+
local function set_preset(device)
102+
test.socket.capability:__expect_send(
103+
device:generate_test_message(
104+
"main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed = false}})
105+
)
106+
)
107+
test.socket.capability:__expect_send(
108+
device:generate_test_message(
109+
"main", capabilities.windowShadePreset.position(50, {visibility = {displayed = false}})
110+
)
111+
)
112+
end
113+
103114
local function test_init()
104115
test.mock_device.add_test_device(mock_device)
105116
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
@@ -111,6 +122,7 @@ local function test_init()
111122
)
112123

113124
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" })
125+
set_preset(mock_device)
114126
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
115127
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
116128
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
@@ -134,6 +146,7 @@ local function test_init_mains_powered()
134146
)
135147

136148
test.socket.device_lifecycle:__queue_receive({ mock_device_mains_powered.id, "init" })
149+
set_preset(mock_device_mains_powered)
137150
local subscribe_request = CLUSTER_SUBSCRIBE_LIST_NO_BATTERY[1]:subscribe(mock_device_mains_powered)
138151
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_NO_BATTERY) do
139152
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_mains_powered)) end
@@ -756,20 +769,31 @@ test.register_coroutine_test("OperationalStatus report contains current position
756769
)
757770
end)
758771

759-
test.register_coroutine_test("Handle windowcoveringPreset", function()
760-
test.socket.capability:__queue_receive(
761-
{
772+
test.register_coroutine_test(
773+
"Handle preset commands",
774+
function()
775+
local PRESET_LEVEL = 30
776+
test.socket.capability:__queue_receive({
777+
mock_device.id,
778+
{capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = { PRESET_LEVEL }},
779+
})
780+
test.socket.capability:__expect_send(
781+
mock_device:generate_test_message(
782+
"main", capabilities.windowShadePreset.position(PRESET_LEVEL)
783+
)
784+
)
785+
test.socket.capability:__queue_receive({
762786
mock_device.id,
763787
{capability = "windowShadePreset", component = "main", command = "presetPosition", args = {}},
764-
}
765-
)
766-
test.socket.matter:__expect_send(
767-
{mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 7000)}
768-
)
769-
test.socket.matter:__expect_send(
770-
{mock_device.id, WindowCovering.server.commands.GoToTiltPercentage(mock_device, 10, 5000)}
771-
)
772-
end)
788+
})
789+
test.socket.matter:__expect_send(
790+
{mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, (100 - PRESET_LEVEL) * 100)}
791+
)
792+
test.socket.matter:__expect_send(
793+
{mock_device.id, WindowCovering.server.commands.GoToTiltPercentage(mock_device, 10, 5000)}
794+
)
795+
end
796+
)
773797

774798
test.register_coroutine_test(
775799
"Test profile change to window-covering-battery when battery percent remaining attribute (attribute ID 12) is available",

0 commit comments

Comments
 (0)