Skip to content

Commit cc85a2c

Browse files
authored
Merge pull request #2870 from SmartThingsCommunity/release/production-2026-04-01
Release/production 2026 04 01
2 parents 2949a9d + f351abc commit cc85a2c

32 files changed

+2712
-60
lines changed

drivers/Aqara/aqara-lock/src/credential_utils.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ local lockCredentialInfo = capabilities["stse.lockCredentialInfo"]
66

77
local credential_utils = {}
88
local HOST_COUNT = "__host_count"
9+
local PERSIST_DATA = "__persist_area"
10+
11+
credential_utils.eventResource = function(table)
12+
local credentialResource = {}
13+
for key, value in pairs(table) do
14+
credentialResource[key] = value
15+
end
16+
return credentialResource
17+
end
18+
19+
credential_utils.backup_data = function(device) -- Back up data the persistent
20+
local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID,
21+
lockCredentialInfo.credentialInfo.NAME, {}))
22+
device:set_field(PERSIST_DATA, credentialInfoTable, { persist = true })
23+
end
24+
25+
credential_utils.sync = function(driver, device)
26+
local table = device:get_field(PERSIST_DATA) or nil
27+
if table ~= nil then
28+
device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table),
29+
{ visibility = { displayed = false } }))
30+
else
31+
credential_utils.backup_data(device)
32+
end
33+
end
934

1035
credential_utils.save_data = function(driver)
1136
driver.datastore:save()
@@ -28,6 +53,7 @@ credential_utils.update_remote_control_status = function(driver, device, added)
2853
end
2954

3055
device:set_field(HOST_COUNT, host_cnt, { persist = true })
56+
credential_utils.backup_data(device)
3157
credential_utils.save_data(driver)
3258
end
3359

@@ -38,6 +64,7 @@ credential_utils.sync_all_credential_info = function(driver, device, command)
3864
end
3965
end
4066
device:emit_event(lockCredentialInfo.credentialInfo(command.args.credentialInfo, { visibility = { displayed = false } }))
67+
credential_utils.backup_data(device)
4168
credential_utils.save_data(driver)
4269
end
4370

@@ -73,6 +100,7 @@ credential_utils.upsert_credential_info = function(driver, device, command)
73100
end
74101

75102
device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } }))
103+
credential_utils.backup_data(device)
76104
credential_utils.save_data(driver)
77105
end
78106

@@ -95,6 +123,7 @@ credential_utils.delete_user = function(driver, device, command)
95123
end
96124

97125
device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } }))
126+
credential_utils.backup_data(device)
98127
credential_utils.save_data(driver)
99128
end
100129

@@ -116,6 +145,7 @@ credential_utils.delete_credential = function(driver, device, command)
116145
end
117146

118147
device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } }))
148+
credential_utils.backup_data(device)
119149
credential_utils.save_data(driver)
120150
end
121151

drivers/Aqara/aqara-lock/src/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ local function device_init(self, device)
8686
end
8787
device:emit_event(capabilities.battery.quantity(battery_quantity))
8888
device:emit_event(capabilities.batteryLevel.quantity(battery_quantity))
89+
credential_utils.sync(self, device)
8990
end
9091

9192
local function device_added(self, device)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
local test = require "integration_test"
2+
local t_utils = require "integration_test.utils"
3+
local capabilities = require "st.capabilities"
4+
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
5+
6+
local remoteControlStatus = capabilities.remoteControlStatus
7+
local antiLockStatus = capabilities["stse.antiLockStatus"]
8+
test.add_package_capability("antiLockStatus.yaml")
9+
local lockCredentialInfo = capabilities["stse.lockCredentialInfo"]
10+
test.add_package_capability("lockCredentialInfo.yaml")
11+
local lockAlarm = capabilities["lockAlarm"]
12+
test.add_package_capability("lockAlarm.yaml")
13+
local Battery = capabilities.battery
14+
local BatteryLevel = capabilities.batteryLevel
15+
local Lock = capabilities.lock
16+
17+
local PRI_CLU = 0xFCC0
18+
19+
local HOST_COUNT = "__host_count"
20+
local PERSIST_DATA = "__persist_area"
21+
22+
local mock_device = test.mock_device.build_test_zigbee_device(
23+
{
24+
profile = t_utils.get_profile_definition("aqara-lock-battery.yml"),
25+
fingerprinted_endpoint_id = 0x01,
26+
zigbee_endpoints = {
27+
[1] = {
28+
id = 1,
29+
manufacturer = "Lumi",
30+
model = "aqara.lock.akr001",
31+
server_clusters = { PRI_CLU }
32+
}
33+
}
34+
}
35+
)
36+
37+
zigbee_test_utils.prepare_zigbee_env_info()
38+
local function test_init()
39+
local SUPPORTED_ALARM_VALUES = { "damaged", "forcedOpeningAttempt", "unableToLockTheDoor", "notClosedForALongTime",
40+
"highTemperature", "attemptsExceeded" }
41+
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
42+
lockAlarm.supportedAlarmValues(SUPPORTED_ALARM_VALUES, { visibility = { displayed = false } })))
43+
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
44+
Lock.supportedUnlockDirections({"fromInside", "fromOutside"}, { visibility = { displayed = false } })))
45+
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.type("AA")))
46+
test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.type("AA")))
47+
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.quantity(6)))
48+
test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.quantity(6)))
49+
local credentialInfoData = {
50+
{ credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "june", userType = "host" }
51+
}
52+
mock_device:set_field(PERSIST_DATA, credentialInfoData, { persist = true })
53+
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
54+
lockCredentialInfo.credentialInfo(credentialInfoData, { visibility = { displayed = false } })))
55+
test.mock_device.add_test_device(mock_device)
56+
end
57+
test.set_test_init_function(test_init)
58+
59+
test.register_coroutine_test(
60+
"Handle added lifecycle - only regular user",
61+
function()
62+
mock_device:set_field(HOST_COUNT, 1, { persist = true })
63+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
64+
65+
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
66+
remoteControlStatus.remoteControlEnabled('true', { visibility = { displayed = false } })))
67+
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.battery(100)))
68+
test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.battery("normal")))
69+
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
70+
lockAlarm.alarm.clear({ visibility = { displayed = false } })))
71+
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
72+
antiLockStatus.antiLockStatus('unknown', { visibility = { displayed = false } })))
73+
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Lock.lock("locked")))
74+
end
75+
)
76+
77+
test.run_registered_tests()

drivers/SmartThings/matter-switch/profiles/ikea-2-button-battery.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ deviceConfig:
3636
- component: main
3737
capability: battery
3838
version: 1
39+
- component: main
40+
capability: refresh
41+
version: 1
3942
- component: button2
4043
capability: button
4144
version: 1

drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -33,49 +33,6 @@ components:
3333
version: 1
3434
categories:
3535
- name: Button
36-
deviceConfig:
37-
icons:
38-
- group: main
39-
iconUrl: 'icon://button_wheel'
40-
dashboard:
41-
states:
42-
- component: main
43-
capability: button
44-
version: 1
45-
detailView:
46-
- component: main
47-
capability: button
48-
version: 1
49-
- component: main
50-
capability: knob
51-
version: 1
52-
- component: main
53-
capability: battery
54-
version: 1
55-
- component: group2
56-
capability: button
57-
version: 1
58-
- component: group2
59-
capability: knob
60-
version: 1
61-
- component: group3
62-
capability: button
63-
version: 1
64-
- component: group3
65-
capability: knob
66-
version: 1
67-
automation:
68-
conditions:
69-
- component: main
70-
capability: button
71-
version: 1
72-
- component: main
73-
capability: battery
74-
version: 1
75-
- component: group2
76-
capability: button
77-
version: 1
78-
- component: group3
79-
capability: button
80-
version: 1
81-
actions: []
36+
metadata:
37+
mnmn: SmartThingsEdge
38+
vid: ikea-scroll

drivers/SmartThings/zigbee-carbon-monoxide-detector/fingerprints.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ zigbeeManufacturer:
1414
manufacturer: HEIMAN
1515
model: COSensor-EM
1616
deviceProfileName: carbonMonoxide-battery
17+
- id: "frient A/S/SCAZB-143"
18+
deviceLabel: Frient Carbon Monoxide Detector
19+
manufacturer: frient A/S
20+
model: SCAZB-143
21+
deviceProfileName: frient-smoke-co-temperature-battery
22+
- id: "frient A/S/SCAZB-141"
23+
deviceLabel: Frient Carbon Monoxide Detector
24+
manufacturer: frient A/S
25+
model: SCAZB-141
26+
deviceProfileName: frient-smoke-co-temperature-battery
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: frient-smoke-co-temperature-battery
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: smokeDetector
6+
version: 1
7+
- id: carbonMonoxideDetector
8+
version: 1
9+
- id: carbonMonoxideMeasurement
10+
version: 1
11+
- id: tamperAlert
12+
version: 1
13+
- id: temperatureMeasurement
14+
version: 1
15+
- id: battery
16+
version: 1
17+
- id: firmwareUpdate
18+
version: 1
19+
- id: refresh
20+
version: 1
21+
- id: alarm
22+
version: 1
23+
config:
24+
values:
25+
- key: "alarm.value"
26+
enabledValues:
27+
- off
28+
- siren
29+
- key: "{{enumCommands}}"
30+
enabledValues:
31+
- off
32+
- siren
33+
categories:
34+
- name: SmokeDetector
35+
preferences:
36+
- title: "Max alarm duration (s)"
37+
name: maxWarningDuration
38+
description: "After how many seconds should the alarm turn off"
39+
required: false
40+
preferenceType: integer
41+
definition:
42+
minimum: 0
43+
maximum: 65534
44+
default: 240
45+
- preferenceId: tempOffset
46+
explicit: true
47+
- title: "Temperature Sensitivity (°C)"
48+
name: temperatureSensitivity
49+
description: "Minimum change in temperature to report"
50+
required: false
51+
preferenceType: number
52+
definition:
53+
minimum: 0.1
54+
maximum: 2.0
55+
default: 1.0
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Copyright 2026 SmartThings, Inc.
2+
-- Licensed under the Apache License, Version 2.0
3+
4+
local is_frient_smoke_carbon_monoxide = function(opts, driver, device)
5+
local FINGERPRINTS = require("frient.fingerprints")
6+
for _, fingerprint in ipairs(FINGERPRINTS) do
7+
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
8+
return true, require("frient")
9+
end
10+
end
11+
12+
return false
13+
end
14+
15+
return is_frient_smoke_carbon_monoxide
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Copyright 2026 SmartThings, Inc.
2+
-- Licensed under the Apache License, Version 2.0
3+
4+
local FRIENT_SMOKE_CARBON_MONOXIDE_FINGERPRINTS = {
5+
{ mfr = "frient A/S", model = "SCAZB-141" },
6+
{ mfr = "frient A/S", model = "SCAZB-143" }
7+
}
8+
9+
return FRIENT_SMOKE_CARBON_MONOXIDE_FINGERPRINTS

0 commit comments

Comments
 (0)