-
Notifications
You must be signed in to change notification settings - Fork 522
CHAD-16364 & CHAD-16365: Add lockUsers and lockCredentials capability #2416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pegor-karoglanian
wants to merge
16
commits into
main
Choose a base branch
from
pegor/edgedrivers/lock_changes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
9c7d6f9
CHAD-16364: SLGA Zigbee: Add lockUsers and lockCredentials capabilities
pegor-karoglanian 16cd903
Add reload command to samsung subdriver
pegor-karoglanian 906e247
Add a way to test the old capabilities.
pegor-karoglanian 631cd3a
SLGA: Z-Wave lock update
greens 94de818
Zwave - Functioning base driver
pegor-karoglanian 95279be
Add table load check
pegor-karoglanian 27a6a01
Z-Wave Lock: first pass at sub-drivers
greens 032e950
add zigbee yale new capabilities unit test
pegor-karoglanian 6425a8c
add more zigbee yale test coverage
pegor-karoglanian 890902e
updates to z-wave sub-drivers and tests
greens 7a8439a
finish sub-driver unit tests
greens 470f666
lazy load sub drivers
pegor-karoglanian 439f357
Add supportedCredentials event
pegor-karoglanian 6331728
breakout can_handle for new and old capabilites
pegor-karoglanian f9dd904
breakout sub driver for yale
pegor-karoglanian 966847a
increase init delay to wait for radio up
pegor-karoglanian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
15 changes: 15 additions & 0 deletions
15
drivers/SmartThings/zigbee-lock/src/lazy_load_subdriver.lua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| -- Copyright 2025 SmartThings, Inc. | ||
| -- Licensed under the Apache License, Version 2.0 | ||
|
|
||
| return function(sub_driver_name) | ||
| -- gets the current lua libs api version | ||
| local version = require "version" | ||
| local ZigbeeDriver = require "st.zigbee" | ||
| if version.api >= 16 then | ||
| return ZigbeeDriver.lazy_load_sub_driver_v2(sub_driver_name) | ||
| elseif version.api >= 9 then | ||
| return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name)) | ||
| else | ||
| return require(sub_driver_name) | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,313 @@ | ||
| -- Copyright 2025 SmartThings | ||
| -- | ||
| -- Licensed under the Apache License, Version 2.0 (the "License"); | ||
| -- you may not use this file except in compliance with the License. | ||
| -- You may obtain a copy of the License at | ||
| -- | ||
| -- http://www.apache.org/licenses/LICENSE-2.0 | ||
| -- | ||
| -- Unless required by applicable law or agreed to in writing, software | ||
| -- distributed under the License is distributed on an "AS IS" BASIS, | ||
| -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| -- See the License for the specific language governing permissions and | ||
| -- limitations under the License. | ||
| local capabilities = require "st.capabilities" | ||
| local INITIAL_INDEX = 1 | ||
|
|
||
| local new_lock_utils = { | ||
| -- Constants | ||
| ADD_CREDENTIAL = "addCredential", | ||
| ADD_USER = "addUser", | ||
| BUSY = "busy", | ||
| COMMAND_NAME = "commandName", | ||
| CREDENTIAL_TYPE = "pin", | ||
| CHECKING_CODE = "checkingCode", | ||
| DELETE_ALL_CREDENTIALS = "deleteAllCredentials", | ||
| DELETE_ALL_USERS = "deleteAllUsers", | ||
| DELETE_CREDENTIAL = "deleteCredential", | ||
| DELETE_USER = "deleteUser", | ||
| LOCK_CREDENTIALS = "lockCredentials", | ||
| LOCK_USERS = "lockUsers", | ||
| ACTIVE_CREDENTIAL = "activeCredential", | ||
| STATUS_BUSY = "busy", | ||
| STATUS_DUPLICATE = "duplicate", | ||
| STATUS_FAILURE = "failure", | ||
| STATUS_INVALID_COMMAND = "invalidCommand", | ||
| STATUS_OCCUPIED = "occupied", | ||
| STATUS_RESOURCE_EXHAUSTED = "resourceExhausted", | ||
| STATUS_SUCCESS = "success", | ||
| TABLES_LOADED = "tablesLoaded", | ||
| UPDATE_CREDENTIAL = "updateCredential", | ||
| UPDATE_USER = "updateUser", | ||
| USER_INDEX = "userIndex", | ||
| USER_NAME = "userName", | ||
| USER_TYPE = "userType" | ||
| } | ||
|
|
||
| -- check if we are currently busy performing a task. | ||
| -- if we aren't then set as busy. | ||
| new_lock_utils.busy_check_and_set = function (device, command, override_busy_check) | ||
| if override_busy_check then | ||
| -- the function was called by an injected command. | ||
| return false | ||
| end | ||
| local c_time = os.time() | ||
| local busy_state = device:get_field(new_lock_utils.BUSY) or false | ||
|
|
||
| if busy_state == false or c_time - busy_state > 10 then | ||
| device:set_field(new_lock_utils.COMMAND_NAME, command) | ||
| device:set_field(new_lock_utils.BUSY, c_time) | ||
| return false | ||
| else | ||
| local command_result_info = { | ||
| commandName = command.name, | ||
| statusCode = new_lock_utils.STATUS_BUSY | ||
| } | ||
| if command.type == new_lock_utils.LOCK_USERS then | ||
| device:emit_event(capabilities.lockUsers.commandResult( | ||
| command_result_info, { state_change = true, visibility = { displayed = true } } | ||
| )) | ||
| else | ||
| device:emit_event(capabilities.lockCredentials.commandResult( | ||
| command_result_info, { state_change = true, visibility = { displayed = true } } | ||
| )) | ||
| end | ||
| return true | ||
| end | ||
| end | ||
|
|
||
| new_lock_utils.clear_busy_state = function(device, status, override_busy_check) | ||
| if override_busy_check then | ||
| return | ||
| end | ||
| local command = device:get_field(new_lock_utils.COMMAND_NAME) | ||
| local active_credential = device:get_field(new_lock_utils.ACTIVE_CREDENTIAL) | ||
| if command ~= nil then | ||
| local command_result_info = { | ||
| commandName = command.name, | ||
| statusCode = status | ||
| } | ||
| if command.type == new_lock_utils.LOCK_USERS then | ||
| if active_credential ~= nil and active_credential.userIndex ~= nil then | ||
| command_result_info.userIndex = active_credential.userIndex | ||
| end | ||
| device:emit_event(capabilities.lockUsers.commandResult( | ||
| command_result_info, { state_change = true, visibility = { displayed = true } } | ||
| )) | ||
| else | ||
| if active_credential ~= nil and active_credential.userIndex ~= nil then | ||
| command_result_info.userIndex = active_credential.userIndex | ||
| end | ||
| if active_credential ~= nil and active_credential.credentialIndex ~= nil then | ||
| command_result_info.credentialIndex = active_credential.credentialIndex | ||
| end | ||
| device:emit_event(capabilities.lockCredentials.commandResult( | ||
| command_result_info, { state_change = true, visibility = { displayed = true } } | ||
| )) | ||
| end | ||
| end | ||
|
|
||
| device:set_field(new_lock_utils.ACTIVE_CREDENTIAL, nil) | ||
| device:set_field(new_lock_utils.COMMAND_NAME, nil) | ||
| device:set_field(new_lock_utils.BUSY, false) | ||
| end | ||
|
|
||
| new_lock_utils.reload_tables = function(device) | ||
| local users = device:get_latest_state("main", capabilities.lockUsers.ID, capabilities.lockUsers.users.NAME, {}) | ||
| local credentials = device:get_latest_state("main", capabilities.lockCredentials.ID, capabilities.lockCredentials.credentials.NAME, {}) | ||
|
|
||
| if next(users) ~= nil then | ||
| device:set_field(new_lock_utils.LOCK_USERS, users) | ||
| end | ||
| if next(credentials) ~= nil then | ||
| device:set_field(new_lock_utils.LOCK_CREDENTIALS, credentials) | ||
| end | ||
| device:set_field(new_lock_utils.TABLES_LOADED, true) | ||
| end | ||
|
|
||
| new_lock_utils.get_users = function(device) | ||
| if not device:get_field(new_lock_utils.TABLES_LOADED) then | ||
| new_lock_utils.reload_tables(device) | ||
| end | ||
|
|
||
| local users = device:get_field(new_lock_utils.LOCK_USERS) | ||
| return users ~= nil and users or {} | ||
| end | ||
|
|
||
| new_lock_utils.get_user = function(device, user_index) | ||
| for _, user in pairs(new_lock_utils.get_users(device)) do | ||
| if user.userIndex == user_index then | ||
| return user | ||
| end | ||
| end | ||
|
|
||
| return nil | ||
| end | ||
|
|
||
| new_lock_utils.get_available_user_index = function(device) | ||
| local max = device:get_latest_state("main", capabilities.lockUsers.ID, | ||
| capabilities.lockUsers.totalUsersSupported.NAME, 0) | ||
| local current_users = new_lock_utils.get_users(device) | ||
| local available_index = nil | ||
| local used_index = {} | ||
| for _, user in pairs(current_users) do | ||
| used_index[user.userIndex] = true | ||
| end | ||
| if current_users ~= {} then | ||
| for index = 1, max do | ||
| if used_index[index] == nil then | ||
| available_index = index | ||
| break | ||
| end | ||
| end | ||
| else | ||
| available_index = INITIAL_INDEX | ||
| end | ||
| return available_index | ||
| end | ||
|
|
||
| new_lock_utils.get_credentials = function(device) | ||
| if not device:get_field(new_lock_utils.TABLES_LOADED) then | ||
| new_lock_utils.reload_tables(device) | ||
| end | ||
|
|
||
| local credentials = device:get_field(new_lock_utils.LOCK_CREDENTIALS) | ||
| return credentials ~= nil and credentials or {} | ||
| end | ||
|
|
||
| new_lock_utils.get_credential = function(device, credential_index) | ||
| for _, credential in pairs(new_lock_utils.get_credentials(device)) do | ||
| if credential.credentialIndex == credential_index then | ||
| return credential | ||
| end | ||
| end | ||
| return nil | ||
| end | ||
|
|
||
| new_lock_utils.get_credential_by_user_index = function(device, user_index) | ||
| for _, credential in pairs(new_lock_utils.get_credentials(device)) do | ||
| if credential.userIndex == user_index then | ||
| return credential | ||
| end | ||
| end | ||
|
|
||
| return nil | ||
| end | ||
|
|
||
| new_lock_utils.get_available_credential_index = function(device) | ||
| local max = device:get_latest_state("main", capabilities.lockCredentials.ID, | ||
| capabilities.lockCredentials.pinUsersSupported.NAME, 0) | ||
| local current_credentials = new_lock_utils.get_credentials(device) | ||
| local available_index = nil | ||
| local used_index = {} | ||
| for _, credential in pairs(current_credentials) do | ||
| used_index[credential.credentialIndex] = true | ||
| end | ||
| if current_credentials ~= {} then | ||
| for index = 1, max do | ||
| if used_index[index] == nil then | ||
| available_index = index | ||
| break | ||
| end | ||
| end | ||
| else | ||
| available_index = INITIAL_INDEX | ||
| end | ||
| return available_index | ||
| end | ||
|
|
||
| new_lock_utils.create_user = function(device, user_name, user_type, user_index) | ||
| if user_name == nil then | ||
| user_name = "Guest" .. user_index | ||
| end | ||
|
|
||
| local current_users = new_lock_utils.get_users(device) | ||
| table.insert(current_users, { userIndex = user_index, userType = user_type, userName = user_name }) | ||
| device:set_field(new_lock_utils.LOCK_USERS, current_users) | ||
| end | ||
|
|
||
| new_lock_utils.delete_user = function(device, user_index) | ||
| local current_users = new_lock_utils.get_users(device) | ||
| local status_code = new_lock_utils.STATUS_FAILURE | ||
|
|
||
| for index, user in pairs(current_users) do | ||
| if user.userIndex == user_index then | ||
| -- table.remove causes issues if we are removing while iterating. | ||
| -- instead set the value as nil and let `prep_table` handle removing it. | ||
| current_users[index] = nil | ||
| device:set_field(new_lock_utils.LOCK_USERS, current_users) | ||
| status_code = new_lock_utils.STATUS_SUCCESS | ||
| break | ||
| end | ||
| end | ||
| return status_code | ||
| end | ||
|
|
||
| new_lock_utils.add_credential = function(device, user_index, credential_type, credential_index) | ||
| local credentials = new_lock_utils.get_credentials(device) | ||
| table.insert(credentials, | ||
| { userIndex = user_index, credentialIndex = credential_index, credentialType = credential_type }) | ||
| device:set_field(new_lock_utils.LOCK_CREDENTIALS, credentials) | ||
| return new_lock_utils.STATUS_SUCCESS | ||
| end | ||
|
|
||
| new_lock_utils.delete_credential = function(device, credential_index) | ||
| local credentials = new_lock_utils.get_credentials(device) | ||
| local status_code = new_lock_utils.STATUS_FAILURE | ||
|
|
||
| for index, credential in pairs(credentials) do | ||
| if credential.credentialIndex == credential_index then | ||
| new_lock_utils.delete_user(device, credential.userIndex) | ||
| -- table.remove causes issues if we are removing while iterating. | ||
| -- instead set the value as nil and let `prep_table` handle removing it. | ||
| credentials[index] = nil | ||
| device:set_field(new_lock_utils.LOCK_CREDENTIALS, credentials) | ||
| status_code = new_lock_utils.STATUS_SUCCESS | ||
| break | ||
| end | ||
| end | ||
|
|
||
| return status_code | ||
| end | ||
|
|
||
| new_lock_utils.update_credential = function(device, credential_index, user_index, credential_type) | ||
| local credentials = new_lock_utils.get_credentials(device) | ||
| local status_code = new_lock_utils.STATUS_FAILURE | ||
|
|
||
| for _, credential in pairs(credentials) do | ||
| if credential.credentialIndex == credential_index then | ||
| credential.credentialType = credential_type | ||
| credential.userIndex = user_index | ||
| device:set_field(new_lock_utils.LOCK_CREDENTIALS, credentials) | ||
| status_code = new_lock_utils.STATUS_SUCCESS | ||
| break | ||
| end | ||
| end | ||
| return status_code | ||
| end | ||
|
|
||
| -- emit_event doesn't like having `nil` values in the table. Remove any if they are present. | ||
| new_lock_utils.prep_table = function(data) | ||
| local clean_table = {} | ||
| for _, value in pairs(data) do | ||
| if value ~= nil then | ||
| clean_table[#clean_table + 1] = value -- Append to the end of the new array | ||
| end | ||
| end | ||
| return clean_table | ||
| end | ||
|
|
||
| new_lock_utils.send_events = function(device, type) | ||
| if type == nil or type == new_lock_utils.LOCK_USERS then | ||
| local current_users = new_lock_utils.prep_table(new_lock_utils.get_users(device)) | ||
| device:emit_event(capabilities.lockUsers.users(current_users, | ||
| {state_change = true, visibility = { displayed = true } })) | ||
| end | ||
| if type == nil or type == new_lock_utils.LOCK_CREDENTIALS then | ||
| local credentials = new_lock_utils.prep_table(new_lock_utils.get_credentials(device)) | ||
| device:emit_event(capabilities.lockCredentials.credentials(credentials, | ||
| { state_change = true, visibility = { displayed = true } })) | ||
| end | ||
| end | ||
|
|
||
| return new_lock_utils |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| -- Copyright 2025 SmartThings, Inc. | ||
| -- Licensed under the Apache License, Version 2.0 | ||
|
|
||
| local lazy_load_if_possible = require "lazy_load_subdriver" | ||
| local sub_drivers = { | ||
| lazy_load_if_possible("using-old-capabilities"), | ||
| lazy_load_if_possible("using-new-capabilities"), | ||
| } | ||
| return sub_drivers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.