From 127f4d6e89e07a9852269ba5cb636d4b3aced2cf Mon Sep 17 00:00:00 2001 From: chenpeixiang <101627956+chenpeixiang@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:05:34 +0800 Subject: [PATCH 1/2] Add AiDot Device For certification (#1301) --- drivers/SmartThings/matter-switch/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 180584609b..024bf6c8a6 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -268,6 +268,11 @@ matterManufacturer: vendorId: 0x1168 productId: 0x0405 deviceProfileName: light-color-level-1800K-6500K + - id: "AiDot/Smart/Light/Bulb/A19/RGBTW" + deviceLabel: AiDot Smart Light Bulb A19 RGBT + vendorId: 0x1168 + productId: 0x03f8 + deviceProfileName: light-color-level-1800K-6500K #WiZ - id: "WiZ A19" From bb449684905a20a11c410bc9a8b1927f59b26e6e Mon Sep 17 00:00:00 2001 From: seojune79 <119141897+seojune79@users.noreply.github.com> Date: Wed, 17 Apr 2024 03:08:46 +0900 Subject: [PATCH 2/2] [Aqara] Aqara doorlock K100 (#1296) * add aqara-lock package and K100 driver * modified code based on reviewer's comments * I modified this code based on the feedback. * update code * add aqara-provisioning * remove "aqara-provisioning" from aqara-feeder and add "aqara_provisioning" to aqara-lock * fix user management errors --------- Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- .../capabilities/lockCredentialInfo.yaml | 133 ++++++++++ drivers/Aqara/aqara-lock/config.yml | 7 + drivers/Aqara/aqara-lock/fingerprints.yml | 6 + .../lockCredentialInfo.presentation.yaml | 10 + .../profiles/aqara-lock-battery.yml | 21 ++ .../Aqara/aqara-lock/src/credential_utils.lua | 143 +++++++++++ drivers/Aqara/aqara-lock/src/init.lua | 241 ++++++++++++++++++ .../aqara-lock/src/test/test_aqara_lock.lua | 153 +++++++++++ 8 files changed, 714 insertions(+) create mode 100644 drivers/Aqara/aqara-lock/capabilities/lockCredentialInfo.yaml create mode 100644 drivers/Aqara/aqara-lock/config.yml create mode 100644 drivers/Aqara/aqara-lock/fingerprints.yml create mode 100644 drivers/Aqara/aqara-lock/presentation/lockCredentialInfo.presentation.yaml create mode 100644 drivers/Aqara/aqara-lock/profiles/aqara-lock-battery.yml create mode 100644 drivers/Aqara/aqara-lock/src/credential_utils.lua create mode 100644 drivers/Aqara/aqara-lock/src/init.lua create mode 100644 drivers/Aqara/aqara-lock/src/test/test_aqara_lock.lua diff --git a/drivers/Aqara/aqara-lock/capabilities/lockCredentialInfo.yaml b/drivers/Aqara/aqara-lock/capabilities/lockCredentialInfo.yaml new file mode 100644 index 0000000000..39e2182d34 --- /dev/null +++ b/drivers/Aqara/aqara-lock/capabilities/lockCredentialInfo.yaml @@ -0,0 +1,133 @@ +id: stse.lockCredentialInfo +version: 1 +status: proposed +name: Lock Credential Info +ephemeral: false +attributes: + credentialInfo: + schema: + type: object + properties: + value: + type: array + items: + title: Credential info + type: object + additionalProperties: false + properties: + credentialId: + type: integer + minimum: 0 + credentialType: + type: string + enum: + - keypad + - rfid + - fingerprint + userId: + type: string + maxLength: 255 + userLabel: + type: string + maxLength: 255 + userType: + type: string + enum: + - unknown + - administrator + - regularUser + - frequentVisitors + - host + required: + - credentialId + - credentialType + - userId + - userLabel + - userType + additionalProperties: false + required: + - value + enumCommands: [] +commands: + syncAll: + name: syncAll + arguments: + - name: credentialInfo + optional: false + schema: + title: Credential info + type: array + items: + properties: + credentialId: + type: integer + minimum: 0 + credentialType: + type: string + enum: + - keypad + - rfid + - fingerprint + userId: + type: string + maxLength: 255 + userLabel: + type: string + maxLength: 255 + userType: + type: string + enum: + - unknown + - administrator + - regularUser + - frequentVisitors + - host + upsert: + name: upsert + arguments: + - name: credentialInfo + optional: false + schema: + title: Credential info + type: array + items: + properties: + credentialId: + type: integer + minimum: 0 + credentialType: + type: string + enum: + - keypad + - rfid + - fingerprint + userId: + type: string + maxLength: 255 + userLabel: + type: string + maxLength: 255 + userType: + type: string + enum: + - unknown + - administrator + - regularUser + - frequentVisitors + - host + deleteUser: + name: deleteUser + arguments: + - name: userId + optional: false + schema: + type: integer + minimum: 0 + deleteCredential: + name: deleteCredential + arguments: + - name: credentialId + optional: false + schema: + type: integer + minimum: 0 diff --git a/drivers/Aqara/aqara-lock/config.yml b/drivers/Aqara/aqara-lock/config.yml new file mode 100644 index 0000000000..31c0c0b6a8 --- /dev/null +++ b/drivers/Aqara/aqara-lock/config.yml @@ -0,0 +1,7 @@ +name: 'Aqara Lock' +packageKey: 'aqara-lock' +permissions: + aqara_provisioning: {} + zigbee: {} +description: "SmartThings driver for Aqara Doorlock" +vendorSupportInformation: "https://www.aqara.com/en/support/" diff --git a/drivers/Aqara/aqara-lock/fingerprints.yml b/drivers/Aqara/aqara-lock/fingerprints.yml new file mode 100644 index 0000000000..c07633921d --- /dev/null +++ b/drivers/Aqara/aqara-lock/fingerprints.yml @@ -0,0 +1,6 @@ +zigbeeManufacturer: + - id: "LUMI/aqara.lock.akr011" + deviceLabel: "Aqara Doorlock K100" + manufacturer: Lumi + model: aqara.lock.akr011 + deviceProfileName: aqara-lock-battery diff --git a/drivers/Aqara/aqara-lock/presentation/lockCredentialInfo.presentation.yaml b/drivers/Aqara/aqara-lock/presentation/lockCredentialInfo.presentation.yaml new file mode 100644 index 0000000000..e4ebf46d4f --- /dev/null +++ b/drivers/Aqara/aqara-lock/presentation/lockCredentialInfo.presentation.yaml @@ -0,0 +1,10 @@ +{ + "dashboard": { + "states": [], + "actions": [], + "basicPlus": [] + }, + "id": "stse.lockCredentialInfo", + "version": 1 +} + diff --git a/drivers/Aqara/aqara-lock/profiles/aqara-lock-battery.yml b/drivers/Aqara/aqara-lock/profiles/aqara-lock-battery.yml new file mode 100644 index 0000000000..562cf024e6 --- /dev/null +++ b/drivers/Aqara/aqara-lock/profiles/aqara-lock-battery.yml @@ -0,0 +1,21 @@ +name: aqara-lock-battery +components: +- id: main + capabilities: + - id: lock + version: 1 + - id: battery + version: 1 + - id: tamperAlert + version: 1 + - id: remoteControlStatus + version: 1 + - id: firmwareUpdate + version: 1 + - id: stse.lockCredentialInfo + version: 1 + categories: + - name: SmartLock +metadata: + mnmn: "SolutionsEngineering" + vid: "SmartThings-smartthings-Aqara_K100" diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua new file mode 100644 index 0000000000..5725d5f55a --- /dev/null +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -0,0 +1,143 @@ +local capabilities = require "st.capabilities" +local utils = require "st.utils" + +local remoteControlStatus = capabilities.remoteControlStatus +local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] + +local credential_utils = {} +local HOST_COUNT = "__host_count" + +credential_utils.save_data = function(driver) + driver.datastore:save() + driver.datastore:commit() +end + +credential_utils.update_remote_control_status = function(driver, device, added) + local host_cnt = device:get_field(HOST_COUNT) or 0 + if added then + if host_cnt == 0 then + device:emit_event(remoteControlStatus.remoteControlEnabled('true', { visibility = { displayed = false } })) + end + host_cnt = host_cnt + 1 + else + if host_cnt > 0 then host_cnt = host_cnt - 1 end + + if host_cnt == 0 then + device:emit_event(remoteControlStatus.remoteControlEnabled('false', { visibility = { displayed = false } })) + end + end + + device:set_field(HOST_COUNT, host_cnt, { persist = true }) + credential_utils.save_data(driver) +end + +credential_utils.sync_all_credential_info = function(driver, device, command) + for _, credentialinfo in ipairs(command.args.credentialInfo) do + if credentialinfo.userType == "host" then + credential_utils.update_remote_control_status(driver, device, true) + end + end + device:emit_event(lockCredentialInfo.credentialInfo(command.args.credentialInfo, { visibility = { displayed = false } })) + credential_utils.save_data(driver) +end + +credential_utils.upsert_credential_info = function(driver, device, command) + if #command.args.credentialInfo == 0 then + return + end + + local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, + lockCredentialInfo.credentialInfo.NAME, {})) + + for _, credentialinfo in ipairs(command.args.credentialInfo) do + local exist = false + for i = 1, #credentialInfoTable, 1 do + if credentialInfoTable[i].credentialId == credentialinfo.credentialId then + credentialInfoTable[i] = utils.deep_copy(credentialinfo) + exist = true + break + end + end + + if exist == false then + if credentialinfo.userType == "host" then + credential_utils.update_remote_control_status(driver, device, true) + end + + table.insert(credentialInfoTable, credentialinfo) + end + end + + if credential_utils.is_exist_host(device) == false then + credential_utils.update_remote_control_status(driver, device, true) + end + + device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.save_data(driver) +end + +credential_utils.delete_user = function(driver, device, command) + local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, + lockCredentialInfo.credentialInfo.NAME, {})) + if #credentialInfoTable == 0 then + return + end + + local id = tostring(command.args.userId) + + for i = #credentialInfoTable, 1, -1 do + if id == credentialInfoTable[i].userId then + if credentialInfoTable[i].userType == "host" then + credential_utils.update_remote_control_status(driver, device, false) + end + table.remove(credentialInfoTable, i) + end + end + + device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.save_data(driver) +end + +credential_utils.delete_credential = function(driver, device, command) + local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, + lockCredentialInfo.credentialInfo.NAME, {})) + if #credentialInfoTable == 0 then + return + end + + for i = #credentialInfoTable, 1, -1 do + if command.args.credentialId == credentialInfoTable[i].credentialId then + if credentialInfoTable[i].userType == "host" then + credential_utils.update_remote_control_status(driver, device, false) + end + table.remove(credentialInfoTable, i) + break + end + end + + device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + credential_utils.save_data(driver) +end + +credential_utils.find_userLabel = function(driver, device, value) + local unlockCredentialId = value & 0xFFFF + local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, + lockCredentialInfo.credentialInfo.NAME, {})) + for _, credentialInfo in ipairs(credentialInfoTable) do + if credentialInfo.credentialId == unlockCredentialId then + return credentialInfo.userId, credentialInfo.userLabel + end + end + return nil, nil +end + +credential_utils.is_exist_host = function(device) + local host_cnt = device:get_field(HOST_COUNT) or 0 + return host_cnt > 0 and true or false +end + +credential_utils.set_host_count = function(device, value) + device:set_field(HOST_COUNT, 0, { persist = true }) +end + +return credential_utils diff --git a/drivers/Aqara/aqara-lock/src/init.lua b/drivers/Aqara/aqara-lock/src/init.lua new file mode 100644 index 0000000000..5d1ee6882e --- /dev/null +++ b/drivers/Aqara/aqara-lock/src/init.lua @@ -0,0 +1,241 @@ +local security = require "st.security" +local ZigbeeDriver = require "st.zigbee" +local data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" +local capabilities = require "st.capabilities" +local base64 = require "base64" +local credential_utils = require "credential_utils" +local utils = require "st.utils" + +local remoteControlStatus = capabilities.remoteControlStatus +local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] +local Battery = capabilities.battery +local Lock = capabilities.lock +local TamperAlert = capabilities.tamperAlert + +local PRI_CLU = 0xFCC0 +local PRI_ATTR = 0xFFF3 +local MFG_CODE = 0x115F + +local serial_num = 0 +local seq_num = 0 + +local SHARED_KEY = "__shared_key" +local CLOUD_PUBLIC_KEY = "__cloud_public_key" + +local function my_secret_data_handler(driver, device, secret_info) + if secret_info.secret_kind ~= "aqara" then return end + + local shared_key = secret_info.shared_key + local cloud_public_key = secret_info.cloud_public_key + + device:set_field(SHARED_KEY, shared_key, { persist = true }) + device:set_field(CLOUD_PUBLIC_KEY, cloud_public_key, { persist = true }) + credential_utils.save_data(driver) + + if cloud_public_key ~= nil then + local raw_data = base64.decode(cloud_public_key) + -- send cloud_pub_key + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRI_CLU, PRI_ATTR, MFG_CODE, data_types.OctetString, "\x3E" .. raw_data)) + end +end + +local function remoteControlShow(device) + if credential_utils.is_exist_host(device) then + device:emit_event(remoteControlStatus.remoteControlEnabled('true', { visibility = { displayed = false } })) + else + credential_utils.set_host_count(device, 0) + device:emit_event(remoteControlStatus.remoteControlEnabled('false', { visibility = { displayed = false } })) + end +end + +local function device_added(self, device) + remoteControlShow(device) + device:emit_event(Battery.battery(100)) + device:emit_event(TamperAlert.tamper.clear({ visibility = { displayed = false } })) + device:emit_event(Lock.lock.locked()) + credential_utils.save_data(self) +end + +local function toValue(payload, start, length) + return utils.deserialize_int(string.sub(payload, start, start + length - 1), length, false, false) +end + +local function toHex(value, length) + return utils.serialize_int(value, length, false, false) +end + +local function event_lock_handler(driver, device, evt_name, evt_value) + if evt_value == 1 then + device:emit_event(Lock.lock(evt_name)) + device:emit_event(TamperAlert.tamper.clear()) + remoteControlShow(device) + end +end + +local function event_unlock_handler(driver, device, evt_name, evt_value) + local id, label + id, label = credential_utils.find_userLabel(driver, device, evt_value) + device:emit_event(Lock.lock.unlocked({ data = { method = evt_name, codeId = id, codeName = label } })) + device:emit_event(remoteControlStatus.remoteControlEnabled('false', { visibility = { displayed = false } })) + device:emit_event(TamperAlert.tamper.clear()) +end + +local function event_door_handler(driver, device, evt_name, evt_value) + if evt_value == 2 then + device:emit_event(Lock.lock(evt_name)) + device:emit_event(TamperAlert.tamper.clear()) + elseif evt_value == 4 then + device:emit_event(TamperAlert.tamper.detected()) + end +end + +local function event_battery_handler(driver, device, evt_name, evt_value) + device:emit_event(Battery.battery(evt_value)) +end + +local function event_tamper_alert_handler(driver, device, evt_name, evt_value) + device:emit_event(TamperAlert.tamper.detected()) +end + +local METHOD = { + LOCKED = "locked", + NOT_FULLY_LOCKED = "not fully locked", + MANUAL = "manual", + FINGERPRINT = "fingerprint", + KEYPAD = "keypad", + RFID = "rfid", + BLUETOOTH = "bluetooth", + COMMAND = "command", + NO_USE = "" +} + +local resource_id = { + ["13.31.85"] = { event_name = METHOD.LOCKED, event_handler = event_lock_handler }, + ["13.17.85"] = { event_name = METHOD.NOT_FULLY_LOCKED, event_handler = event_door_handler }, + ["13.48.85"] = { event_name = METHOD.MANUAL, event_handler = event_unlock_handler }, + ["13.51.85"] = { event_name = METHOD.MANUAL, event_handler = event_unlock_handler }, + ["13.42.85"] = { event_name = METHOD.FINGERPRINT, event_handler = event_unlock_handler }, + ["13.43.85"] = { event_name = METHOD.KEYPAD, event_handler = event_unlock_handler }, + ["13.44.85"] = { event_name = METHOD.RFID, event_handler = event_unlock_handler }, + ["13.45.85"] = { event_name = METHOD.BLUETOOTH, event_handler = event_unlock_handler }, + ["13.90.85"] = { event_name = METHOD.COMMAND, event_handler = event_unlock_handler }, + ["13.46.85"] = { event_name = METHOD.KEYPAD, event_handler = event_unlock_handler }, + ["13.56.85"] = { event_name = METHOD.NO_USE, event_handler = event_battery_handler }, + ["13.32.85"] = { event_name = METHOD.NO_USE, event_handler = event_tamper_alert_handler } +} + +local function request_generate_shared_key(device) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRI_CLU, PRI_ATTR, MFG_CODE, data_types.OctetString, "\x2B")) +end + +local function lock_state_handler(driver, device, value, zb_rx) + local shared_key = device:get_field(SHARED_KEY) + local param = value.value + local command = string.sub(param, 0, 1) + + if command == "\x3E" then + -- recv lock_pub_key + local locks_pub_key = string.sub(param, 2, string.len(param)) + local mn_id = "Id3A" + local setup_id = "006" + local product_id = "" + local res, _ = security.get_aqara_secret(device.zigbee_eui, locks_pub_key, "", mn_id, setup_id, + product_id) + if res then + print(res) + end + elseif shared_key == nil then + request_generate_shared_key(device) + elseif command == "\x93" then + local opts = { cipher = "aes256-ecb", padding = false } + local raw_key = base64.decode(shared_key) + local raw_data = string.sub(param, 2, string.len(param)) + local msg = security.decrypt_bytes(raw_data, raw_key, opts) + local text = string.sub(msg, 5, string.len(msg)) + local payload = string.sub(text, 4, string.len(text)) + local func_id = toValue(payload, 1, 1) .. "." .. toValue(payload, 2, 1) .. "." .. toValue(payload, 3, 2) + serial_num = toValue(msg, 3, 2) + seq_num = string.byte(text, 3) + + if resource_id[func_id] then + resource_id[func_id].event_handler(driver, device, resource_id[func_id].event_name, + toValue(payload, 6, string.byte(payload, 5))) + end + + if serial_num >= 0xFFFF then + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRI_CLU, PRI_ATTR, MFG_CODE, data_types.OctetString, "\x2B")) + end + end +end + +local function send_msg(device, funcA, funcB, funcC, op_code, length, value) + local shared_key = device:get_field(SHARED_KEY) + if shared_key == nil then + request_generate_shared_key(device) + else + local payload = toHex(funcA, 1) .. toHex(funcB, 1) .. toHex(funcC, 2) .. toHex(length, 1) .. toHex(value, length) + local text = "\x00" .. toHex(op_code, 1) .. toHex(seq_num+1, 1) .. payload + local raw_data = "\x5B" .. toHex(string.len(text), 1) .. toHex(serial_num+1, 2) .. text + for i = 1, 4 - (string.len(raw_data) % 4) do + raw_data = raw_data .. "\x00" + end + + local opts = { cipher = "aes256-ecb", padding = false } + local raw_key = base64.decode(shared_key) + if raw_key ~= nil then + local result = security.encrypt_bytes(raw_data, raw_key, opts) + if result ~= nil then + local msg = "\x93" .. result + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRI_CLU, PRI_ATTR, MFG_CODE, data_types.OctetString, msg)) + seq_num = seq_num + 1 + serial_num = serial_num + 1 + end + end + end +end + +local function unlock_cmd_handler(driver, device, cmd) + send_msg(device, 4, 17, 85, 2, 1, 1) +end + +local aqara_locks_handler = { + NAME = "Aqara Doorlock K100", + supported_capabilities = { + Lock, + Battery, + lockCredentialInfo, + capabilities.refresh, + }, + zigbee_handlers = { + attr = { + [PRI_CLU] = { + [PRI_ATTR] = lock_state_handler + } + } + }, + capability_handlers = { + [lockCredentialInfo.ID] = { + [lockCredentialInfo.commands.syncAll.NAME] = credential_utils.sync_all_credential_info, + [lockCredentialInfo.commands.upsert.NAME] = credential_utils.upsert_credential_info, + [lockCredentialInfo.commands.deleteUser.NAME] = credential_utils.delete_user, + [lockCredentialInfo.commands.deleteCredential.NAME] = credential_utils.delete_credential + }, + [capabilities.lock.ID] = { + [capabilities.lock.commands.unlock.NAME] = unlock_cmd_handler + } + }, + lifecycle_handlers = { + added = device_added + }, + secret_data_handlers = { + [security.SECRET_KIND_AQARA] = my_secret_data_handler + } +} + +local aqara_locks_driver = ZigbeeDriver("aqara_locks_k100", aqara_locks_handler) +aqara_locks_driver:run() diff --git a/drivers/Aqara/aqara-lock/src/test/test_aqara_lock.lua b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock.lua new file mode 100644 index 0000000000..9cee9fc0c0 --- /dev/null +++ b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock.lua @@ -0,0 +1,153 @@ +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local remoteControlStatus = capabilities.remoteControlStatus +local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] +test.add_package_capability("lockCredentialInfo.yaml") +local Battery = capabilities.battery +local Lock = capabilities.lock +local TamperAlert = capabilities.tamperAlert + +local PRI_CLU = 0xFCC0 +local PRI_ATTR = 0xFFF3 +local MFG_CODE = 0x115F + +local HOST_COUNT = "__host_count" + +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.akr011", + server_clusters = { PRI_CLU } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle - no host user", + function() + mock_device:set_field(HOST_COUNT, 0, { 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('false', { 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", + TamperAlert.tamper("clear", { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Lock.lock("locked"))) + end +) + +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", + TamperAlert.tamper("clear", { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Lock.lock("locked"))) + end +) + +test.register_coroutine_test( + "credential_utils.sync_all_credential_info", + function() + local credentialInfoData = { + { + { credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "user1", userType = "host" }, + { credentialId = 2, credentialType = "fingerprint", userId = "2", userLabel = "user2", userType = "regularUser" } + } + } + local credentialInfoData_copy = { + { credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "user1", userType = "host" }, + { credentialId = 2, credentialType = "fingerprint", userId = "2", userLabel = "user2", userType = "regularUser" } + } + mock_device:set_field(HOST_COUNT, 0, { persist = true }) + test.socket.capability:__queue_receive({ mock_device.id, + { capability = lockCredentialInfo.ID, component = "main", command = "syncAll", args = credentialInfoData } + }) + 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", + lockCredentialInfo.credentialInfo(credentialInfoData_copy, { visibility = { displayed = false } }))) + end +) + +test.register_coroutine_test( + "credential_utils.upsert_credential_info(host user)", + function() + local credentialInfoData = { + { credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "user1", userType = "host" } + } + test.socket.capability:__queue_receive({ mock_device.id, + { capability = lockCredentialInfo.ID, component = "main", command = "upsert", args = { credentialInfoData } } }) + 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", + lockCredentialInfo.credentialInfo(credentialInfoData, { visibility = { displayed = false } }))) + end +) + +test.register_coroutine_test( + "credential_utils.upsert_credential_info(regular user)", + function() + local credentialInfoData = { + { credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "user1", userType = "regularUser" } + } + test.socket.capability:__queue_receive({ mock_device.id, + { capability = lockCredentialInfo.ID, component = "main", command = "upsert", args = credentialInfoData } }) + end +) + +test.register_coroutine_test( + "credential_utils.delete_user", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = lockCredentialInfo.ID, component = "main", command = "deleteUser", args = { userId = "3" } } }) + end +) + +test.register_coroutine_test( + "credential_utils.delete_credential", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = lockCredentialInfo.ID, component = "main", command = "deleteCredential", args = { credentialId = "1" } } }) + end +) + +test.register_coroutine_test( + "lock_state_handler - shared_key is nil", + function() + local attr_report_data = { + { PRI_ATTR, data_types.OctetString.ID, "\x93\x15\xAA\xFE\x78\xEE\x3E\x81\x9A\x06\xE3\x9A\x62\xD3\xB1\xF1\xD4\x64\x8C\x16\x66\xAB\xE1\x69\x89\x8C\x04\x56\x8D\xAD\xEA\xDE\xF8" } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRI_CLU, attr_report_data, MFG_CODE) + }) + test.socket.zigbee:__expect_send({ mock_device.id, cluster_base.write_manufacturer_specific_attribute(mock_device, + PRI_CLU, PRI_ATTR, MFG_CODE, data_types.OctetString, "\x2B") }) + end +) + +test.run_registered_tests()