Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions drivers/Aqara/aqara-lock/src/credential_utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ local lockCredentialInfo = capabilities["stse.lockCredentialInfo"]

local credential_utils = {}
local HOST_COUNT = "__host_count"
local PERSIST_DATA = "__persist_area"

credential_utils.eventResource = function(table)
local credentialResource = {}
for key, value in pairs(table) do
credentialResource[key] = value
end
return credentialResource
end

credential_utils.backup_data = function(device) -- Back up data the persistent
local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID,
lockCredentialInfo.credentialInfo.NAME, {}))
device:set_field(PERSIST_DATA, credentialInfoTable, { persist = true })
end

credential_utils.sync = function(driver, device)
local table = device:get_field(PERSIST_DATA) or nil
if table ~= nil then
device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table),
{ visibility = { displayed = false } }))
else
credential_utils.backup_data(device)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be an else clause where we call backup_data in the case that there is nothing persisted? I think this will help ensure existing devices have the state for this capability persisted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate on this part a bit more? Are you suggesting that adding a check for backup_data would be beneficial if capability information could be deleted during normal use? I'd appreciate further explanation to ensure my understanding is correct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sync function is called on device_init. Devices out there right now, do not have PERSIST_DATA set on the device object yet. We want any device that does not yet have this persisted, to have it persisted as soon as we detect that it hasnt been persisted. So my suggestion is, if table is nil, then we should read the current state and persist it in the driver datastore with credential_utils.backup_data. If we do not detect and persist in the device_init handler, then we are waiting for a capability command to come in that will cause the persistence, which may or may not happen before a hub failover.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your kind explanation; I now fully understand the situation. As per your suggestion, I have modified the code to read and set the current value if PERSIST_DATA is nil.

end

credential_utils.save_data = function(driver)
driver.datastore:save()
Expand All @@ -28,6 +53,7 @@ credential_utils.update_remote_control_status = function(driver, device, added)
end

device:set_field(HOST_COUNT, host_cnt, { persist = true })
credential_utils.backup_data(device)
credential_utils.save_data(driver)
end

Expand All @@ -38,6 +64,7 @@ credential_utils.sync_all_credential_info = function(driver, device, command)
end
end
device:emit_event(lockCredentialInfo.credentialInfo(command.args.credentialInfo, { visibility = { displayed = false } }))
credential_utils.backup_data(device)
credential_utils.save_data(driver)
end

Expand Down Expand Up @@ -73,6 +100,7 @@ credential_utils.upsert_credential_info = function(driver, device, command)
end

device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } }))
credential_utils.backup_data(device)
credential_utils.save_data(driver)
end

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

device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } }))
credential_utils.backup_data(device)
credential_utils.save_data(driver)
end

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

device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } }))
credential_utils.backup_data(device)
credential_utils.save_data(driver)
end

Expand Down
1 change: 1 addition & 0 deletions drivers/Aqara/aqara-lock/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ local function device_init(self, device)
end
device:emit_event(capabilities.battery.quantity(battery_quantity))
device:emit_event(capabilities.batteryLevel.quantity(battery_quantity))
credential_utils.sync(self, device)
end

local function device_added(self, device)
Expand Down
77 changes: 77 additions & 0 deletions drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
local test = require "integration_test"
local t_utils = require "integration_test.utils"
local capabilities = require "st.capabilities"
local zigbee_test_utils = require "integration_test.zigbee_test_utils"

local remoteControlStatus = capabilities.remoteControlStatus
local antiLockStatus = capabilities["stse.antiLockStatus"]
test.add_package_capability("antiLockStatus.yaml")
local lockCredentialInfo = capabilities["stse.lockCredentialInfo"]
test.add_package_capability("lockCredentialInfo.yaml")
local lockAlarm = capabilities["lockAlarm"]
test.add_package_capability("lockAlarm.yaml")
local Battery = capabilities.battery
local BatteryLevel = capabilities.batteryLevel
local Lock = capabilities.lock

local PRI_CLU = 0xFCC0

local HOST_COUNT = "__host_count"
local PERSIST_DATA = "__persist_area"

local mock_device = test.mock_device.build_test_zigbee_device(
{
profile = t_utils.get_profile_definition("aqara-lock-battery.yml"),
fingerprinted_endpoint_id = 0x01,
zigbee_endpoints = {
[1] = {
id = 1,
manufacturer = "Lumi",
model = "aqara.lock.akr001",
server_clusters = { PRI_CLU }
}
}
}
)

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
local SUPPORTED_ALARM_VALUES = { "damaged", "forcedOpeningAttempt", "unableToLockTheDoor", "notClosedForALongTime",
"highTemperature", "attemptsExceeded" }
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
lockAlarm.supportedAlarmValues(SUPPORTED_ALARM_VALUES, { visibility = { displayed = false } })))
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
Lock.supportedUnlockDirections({"fromInside", "fromOutside"}, { visibility = { displayed = false } })))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.type("AA")))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.type("AA")))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.quantity(6)))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.quantity(6)))
local credentialInfoData = {
{ credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "june", userType = "host" }
}
mock_device:set_field(PERSIST_DATA, credentialInfoData, { persist = true })
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
lockCredentialInfo.credentialInfo(credentialInfoData, { visibility = { displayed = false } })))
test.mock_device.add_test_device(mock_device)
end
test.set_test_init_function(test_init)

test.register_coroutine_test(
"Handle added lifecycle - only regular user",
function()
mock_device:set_field(HOST_COUNT, 1, { persist = true })
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })

test.socket.capability:__expect_send(mock_device:generate_test_message("main",
remoteControlStatus.remoteControlEnabled('true', { visibility = { displayed = false } })))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.battery(100)))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.battery("normal")))
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
lockAlarm.alarm.clear({ visibility = { displayed = false } })))
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
antiLockStatus.antiLockStatus('unknown', { visibility = { displayed = false } })))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", Lock.lock("locked")))
end
)

test.run_registered_tests()
Loading