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
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ def getEnvName() {
if (branch == "origin/main") {return "ALPHA"}
else if (branch == "origin/beta") {return "BETA"}
else if (branch == "origin/production") {return "PROD"}
else if (branch == "origin/un-migrate-zigbee-locks") {return "TEST"}
}

def getChangedDrivers() {
Expand Down
40 changes: 30 additions & 10 deletions drivers/SmartThings/zigbee-lock/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ local capability_handlers = require "lock_handlers.capabilities"
local LockLifecycle = {}

function LockLifecycle.device_added(driver, device)
if device:supports_capability(capabilities.lockCodes) and device._provisioning_state == "TYPED" then
-- set the migrated field to true so new devices use lockCredentials/lockUsers from the start.
-- auto-migration is only run for typed devices, as provisioned devices have already been onboarded,
-- and should be migrated manually by the user.
device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) -- persist the migration event in the datastore
end
-- Note: We should not auto-migrate for the time being
-- if device:supports_capability(capabilities.lockCodes) and device._provisioning_state == "TYPED" then
-- -- set the migrated field to true so new devices use lockCredentials/lockUsers from the start.
-- -- auto-migration is only run for typed devices, as provisioned devices have already been onboarded,
-- -- and should be migrated manually by the user.
-- device:emit_event(capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
-- device:set_field(consts.DRIVER_STATE.SLGA_MIGRATED, true, { persist = true }) -- persist the migration event in the datastore
-- end
-- set initial state
driver:inject_capability_command(device, {
capability = capabilities.refresh.ID,
Expand All @@ -31,16 +32,35 @@ function LockLifecycle.device_added(driver, device)
})
end

local function revert_migration(driver, device)
local legacy_lock_utils = require "legacy-handlers.legacy_lock_utils"
local json = require "st.json"

local latest_users = table_utils.get_state(device, "users") or {}
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 } }))
device:set_field(legacy_lock_utils.LOCK_CODES, 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)
elseif not lock_pins_supported_by_profile then
-- generically fingerprinted profiles do not have any codes/users/credentials capabilities.
-- We should check its PIN users if it should be re-profiled.
Expand Down
219 changes: 111 additions & 108 deletions drivers/SmartThings/zigbee-lock/src/test/test_init_lifecycle_handlers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,50 +99,51 @@ end
-- added (device_added)
-- ============================================================================

test.register_coroutine_test(
"added: TYPED device with lockCodes emits migrated event, persists SLGA_MIGRATED, and injects refresh",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" })

-- Migrated event is emitted before the injected refresh
test.socket.capability:__expect_send(
mock_device_typed:generate_test_message("main",
capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
)
-- inject_capability_command calls the refresh handler inline
test.socket.zigbee:__expect_send({
mock_device_typed.id,
PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.LockState:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
Alarms.attributes.AlarmCount:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.MaxPINCodeLength:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.MinPINCodeLength:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_typed),
})
test.wait_for_events()

assert(
mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true,
"SLGA_MIGRATED must be true after added fires for a TYPED device"
)
end,
{ test_init = make_test_init(mock_device_typed) }
)
-- Note: Remove test for the time being, since auto-migration on added is no longer the default behavior
-- test.register_coroutine_test(
-- "added: TYPED device with lockCodes emits migrated event, persists SLGA_MIGRATED, and injects refresh",
-- function()
-- test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" })

-- -- Migrated event is emitted before the injected refresh
-- test.socket.capability:__expect_send(
-- mock_device_typed:generate_test_message("main",
-- capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
-- )
-- -- inject_capability_command calls the refresh handler inline
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.LockState:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- Alarms.attributes.AlarmCount:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.MaxPINCodeLength:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.MinPINCodeLength:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_typed),
-- })
-- test.wait_for_events()

-- assert(
-- mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true,
-- "SLGA_MIGRATED must be true after added fires for a TYPED device"
-- )
-- end,
-- { test_init = make_test_init(mock_device_typed) }
-- )

test.register_coroutine_test(
"added: non-TYPED (PROVISIONED) device with lockCodes does NOT emit migrated but still injects refresh",
Expand Down Expand Up @@ -381,24 +382,25 @@ end
-- 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) }
)
-- Note: Remove test since un-migration on init is the default behavior here, and that screws up this test
-- 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",
Expand Down Expand Up @@ -433,52 +435,53 @@ test.register_coroutine_test(
-- driverSwitched (LockLifecycle.driver_switched)
-- ============================================================================

test.register_coroutine_test(
"driver_switched: device with lockCodes and migrated=true persists SLGA_MIGRATED and updates metadata",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" })

-- Migrated event is emitted before the injected refresh
test.socket.capability:__expect_send(
mock_device_typed:generate_test_message("main",
capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
)
-- inject_capability_command calls the refresh handler inline
test.socket.zigbee:__expect_send({
mock_device_typed.id,
PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.LockState:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
Alarms.attributes.AlarmCount:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.MaxPINCodeLength:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.MinPINCodeLength:read(mock_device_typed),
})
test.socket.zigbee:__expect_send({
mock_device_typed.id,
DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_typed),
})
test.wait_for_events()

-- driverSwitched occurs after added, so migrated=true is already set in the capability state cache
test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "driver_switched" })
mock_device_typed:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()

assert(mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true)
end,
{ test_init = make_test_init(mock_device_typed) }
)
-- Note: Remove test for the time being, since auto-migration on added is no longer the default behavior, and that screws up this test
-- test.register_coroutine_test(
-- "driver_switched: device with lockCodes and migrated=true persists SLGA_MIGRATED and updates metadata",
-- function()
-- test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "added" })

-- -- Migrated event is emitted before the injected refresh
-- test.socket.capability:__expect_send(
-- mock_device_typed:generate_test_message("main",
-- capabilities.lockCodes.migrated(true, { visibility = { displayed = false } }))
-- )
-- -- inject_capability_command calls the refresh handler inline
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.LockState:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- Alarms.attributes.AlarmCount:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.MaxPINCodeLength:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.MinPINCodeLength:read(mock_device_typed),
-- })
-- test.socket.zigbee:__expect_send({
-- mock_device_typed.id,
-- DoorLock.attributes.NumberOfPINUsersSupported:read(mock_device_typed),
-- })
-- test.wait_for_events()

-- -- driverSwitched occurs after added, so migrated=true is already set in the capability state cache
-- test.socket.device_lifecycle:__queue_receive({ mock_device_typed.id, "driver_switched" })
-- mock_device_typed:expect_metadata_update({ provisioning_state = "PROVISIONED" })
-- test.wait_for_events()

-- assert(mock_device_typed:get_field(constants.DRIVER_STATE.SLGA_MIGRATED) == true)
-- end,
-- { test_init = make_test_init(mock_device_typed) }
-- )

-- ============================================================================
test.run_registered_tests()
Loading
Loading