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
26 changes: 23 additions & 3 deletions drivers/SmartThings/zwave-lock/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,36 @@ function LockLifecycle.device_added(driver, device)
})
end

local function revert_migration(driver, device)
local json = require "st.json"

local latest_users = table_utils.get_state(device, "users") or {}
print("Latest users: ", #latest_users)
local lock_codes = {}
for _, user in ipairs(latest_users) do
lock_codes[string.format("%s", user.userIndex)] = user.userName or ("Code " .. user.userIndex)
end
device:emit_event(capabilities.lockCodes.lockCodes(json.encode(lock_codes), { visibility = { displayed = false } }))
local lock_codes_key = "_lock_codes"
device:set_field(lock_codes_key, lock_codes, { persist = true })

local max_code_length = device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.maxPinCodeLen.NAME)
if max_code_length then
device:emit_event(capabilities.lockCodes.codeLength(max_code_length, { visibility = { displayed = false } }))
end

device:emit_event(capabilities.lockCodes.migrated(false, { visibility = { displayed = false } }))
device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, nil, { persist = true }) -- persist the un-migrated state to the datastore
end

function LockLifecycle.init(driver, device)
-- Restore users/credentials capability state from the persistent store in case
-- the capability state cache was wiped since the last driver run.
table_utils.restore_from_persistent_store(device)

local lock_pins_supported_by_profile = device:supports_capability(capabilities.lockCodes)
if lock_pins_supported_by_profile and device:get_field(consts.DRIVER_STATE.SLGA_MIGRATED) == true then
-- ensure lockCodes capability state is reflected correctly for already migrated devices
device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
device:emit_event(capabilities.lockCredentials.supportedCredentials({ consts.CRED_TYPE_PIN }, { visibility = { displayed = false } }))
revert_migration(driver, device)
end

if device:supports_capability(capabilities.tamperAlert) then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

local test = require "integration_test"
local t_utils = require "integration_test.utils"
local capabilities = require "st.capabilities"
local zw = require "st.zwave"
local constants = require "lock_utils.constants"

Expand Down Expand Up @@ -91,25 +90,6 @@ test.register_coroutine_test(
-- init (LockLifecycle.init)
-- ============================================================================

test.register_coroutine_test(
"init: device with lockCodes and SLGA_MIGRATED=true emits migrated + supportedCredentials",
function()
mock_device_base:set_field(constants.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true })

test.socket.device_lifecycle:__queue_receive({ mock_device_base.id, "init" })
test.socket.capability:__expect_send(
mock_device_base:generate_test_message("main",
capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
)
test.socket.capability:__expect_send(
mock_device_base:generate_test_message("main",
capabilities.lockCredentials.supportedCredentials({ "pin" }, { visibility = { displayed = false } }))
)
test.wait_for_events()
end,
{ test_init = make_test_init(mock_device_base) }
)

test.register_coroutine_test(
"init: device with lockCodes but SLGA_MIGRATED not set does nothing",
function()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local test = require "integration_test"
local t_utils = require "integration_test.utils"
local capabilities = require "st.capabilities"
local zw = require "st.zwave"
local st_utils = require "st.utils"
local table_utils = require "lock_utils.tables"
local constants = require "lock_utils.constants"
local UserCode = (require "st.zwave.CommandClass.UserCode")({ version = 1 })
Expand Down Expand Up @@ -702,4 +703,92 @@ test.register_coroutine_test(
end
)

-- ============================================================================
-- revert Migration on init
-- ============================================================================

test.register_coroutine_test(
"Revert Migration on init, after added",
function()
seed_users({
{ userIndex = 1, userName = "Alice", userType = "guest" },
{ userIndex = 2, userName = "Bob", userType = "guest" },
{ userIndex = 5, userName = "Charlie", userType = "guest" },
})
seed_credentials({
{ userIndex = 1, credentialIndex = 1, credentialType = "pin" },
{ userIndex = 2, credentialIndex = 2, credentialType = "pin" },
{ userIndex = 5, credentialIndex = 5, credentialType = "pin" },
})

test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" })
test.socket.capability:__set_channel_ordering("relaxed")

test.socket.capability:__expect_send(
mock_device:generate_test_message("main",
capabilities.lockUsers.users(
{
{ userIndex = 1, userName = "Alice", userType = "guest" },
{ userIndex = 2, userName = "Bob", userType = "guest" },
{ userIndex = 5, userName = "Charlie", userType = "guest" },
},
{ visibility = { displayed = false } }
))
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main",
capabilities.lockCredentials.credentials(
{
{ userIndex = 1, credentialIndex = 1, credentialType = "pin" },
{ userIndex = 2, credentialIndex = 2, credentialType = "pin" },
{ userIndex = 5, credentialIndex = 5, credentialType = "pin" },
},
{ visibility = { displayed = false } }
))
)

-- Reversion of Migration should be handled for the device on initialization
test.socket.capability:__expect_send(
mock_device:generate_test_message("main",
capabilities.lockCodes.lockCodes(json.encode({
["1"] = "Alice", ["2"] = "Bob", ["5"] = "Charlie"
}), { visibility = { displayed = false } })
)
)

test.socket.capability:__expect_send(
mock_device:generate_test_message("main",
capabilities.lockCodes.migrated(false, { visibility = { displayed = false } })
)
)
test.wait_for_events()
assert(mock_device:get_field(constants.SLGA_MIGRATED) == nil, "Device should not be marked as migrated")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
assert(mock_device:get_field(constants.SLGA_MIGRATED) == nil, "Device should not be marked as migrated")
assert(mock_device:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == nil, "Device should not be marked as migrated")

local stored_codes = st_utils.deep_copy(mock_device:get_field("_lock_codes"))
assert(stored_codes["1"] == "Alice")
assert(stored_codes["2"] == "Bob")
assert(stored_codes["5"] == "Charlie")

test.wait_for_events()

-- ensure codeChanged now triggers correctly after setting a new code
test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 3, "1234", "test" } } })
test.socket.zwave:__expect_send(UserCode:Set({user_identifier = 3, user_code = "1234", user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}):build_test_tx(mock_device.id) )
test.wait_for_events()
test.socket.zwave:__queue_receive({mock_device.id, UserCode:Report({user_identifier = 3, user_id_status = UserCode.user_id_status.ENABLED_GRANT_ACCESS}) })
test.socket.capability:__expect_send(
mock_device:generate_test_message("main",
capabilities.lockCodes.lockCodes(json.encode({
["1"] = "Alice", ["2"] = "Bob", ["3"] = "test", ["5"] = "Charlie"
}), { visibility = { displayed = false } })
)
)
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
capabilities.lockCodes.codeChanged("3 set", { data = { codeName = "test"}, state_change = true }))
)
end,
{
min_api_version = 17
}
)

test.run_registered_tests()
Loading