Repository: make-all/tuya-local
Pull request #3017: Add support for Hornbill Y4 Smart Lock family
Author: login: jayPare
Created: 2025-02-27T15:09:19Z | Merged: 2025-03-21T10:23:20Z

Adding support for [ Hornbill Y4 Smart Lock family](https://hornbilllock.com/product/y4-fingerprint-door-lock/).

![image](https://github.com/user-attachments/assets/3018ee50-57af-4e39-8d8e-ede976fd0e61)

Here are the query results:
[query_things_data_model.json](https://github.com/user-attachments/files/19011589/query_things_data_model.json)
[query_device_details.json](https://github.com/user-attachments/files/19011595/query_devide_details.json)



Diff:
diff --git a/DEVICES.md b/DEVICES.md
index 414bed1ca49..e7dfed9c612 100644
--- a/DEVICES.md
+++ b/DEVICES.md
@@ -801,6 +801,7 @@ work reliably, even if listed below.
 - SmarDeer Lock33 smart lock
 - Tediton K7 smart lock
 - NovaDigital SL-06 smart lock
+- Hornbill Y4 Smart lock
 
 ### Sirens
 
diff --git a/custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml b/custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
new file mode 100644
index 00000000000..c9d2ed9177b
--- /dev/null
+++ b/custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
@@ -0,0 +1,476 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    name: Hornbill Y4 Smart Lock
+entities:
+  - entity: lock
+    dps:
+      # [Add unlocking method] After the device is online, add unlocking
+      # methods. Support adding fingerprint, password, card and face unlocking
+      # methods; support administrator tagging.
+      - id: 1
+        type: string
+        optional: true
+        sensitive: true
+        name: unlock_method_create
+      # [Delete unlocking method] After the device is online, delete the
+      # unlocking method. Supports deleting fingerprint, password, card and
+      # face unlocking methods. This dp appears in pairs with the added dp.
+      - id: 2
+        type: string
+        optional: true
+        sensitive: true
+        name: unlock_method_delete
+      # [Modify unlocking method] After the device is online, you can modify
+      # the name of the unlocking method and the content of the password.
+      - id: 3
+        type: string
+        optional: true
+        name: unlock_method_modify
+      # [Report unlocking record] is used for the device to report the record
+      # of fingerprint unlocking. This dp is only enabled when the smart lock
+      # product has a fingerprint recognition function in the hardware.
+      - id: 12
+        type: integer
+        readonly: true
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the record
+      # of unlocking with ordinary password. This dp is enabled only when the
+      # hardware of this smart lock product has a password button that can be
+      # used to unlock the door with a password.
+      - id: 13
+        name: unlock_password
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the door
+      # card unlocking record. This dp is enabled only when the smart lock
+      # product has a card swiping function in the hardware and can report the
+      # card swiping record.
+      - id: 15
+        name: unlock_card
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] If the device supports detection of door
+      # opening from the inside, it is used for the device to report the record
+      # of door opening from the inside. This dp is enabled only when the
+      # hardware of this smart lock product can detect and identify unlocking
+      # from the inside and can report the data.
+      - id: 18
+        name: open_inside
+        type: boolean
+        readonly: true
+        optional: true
+      # [Report unlocking record] is used for the device to report the door
+      # opening record generated by the mobile phone app Bluetooth unlocking.
+      # Generally, all Bluetooth locks support this function.
+      - id: 19
+        name: unlock_ble
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report lock record] is used for the device to report the lock record.
+      # If the lock is controlled by the mobile phone app or the lock is
+      # manually closed locally, this dp can be used to report.
+      - id: 20
+        type: string
+        optional: true
+        name: lock_record
+      # [Lock Operation] is used to trigger the device to perform the lock
+      # operation, which can be used for automatic lock and manual lock
+      # operation. To report the lock (close) record, you need to use dp=20
+      # lock record. You need to use 2 dp in combination to fully implement the
+      # lock operation and lock record reporting. In addition to these two dp,
+      # you also need to use dp47=lock status to report the lock status, so
+      # that the unlock/lock status displayed on the app is consistent with
+      # the local hardware.
+      - id: 46
+        type: boolean
+        name: lock
+        optional: true
+      # [Device Status] is used to report the status of the device clutch
+      # switch, that is, the switch status of the lock. This dp is enabled only
+      # when the product hardware supports the detection of the lock status.
+      - id: 47
+        type: boolean
+        optional: true
+        name: lock_motor_state
+        readonly: true
+      # [Online Temporary Password] is used to add an online temporary
+      # password, and supports setting time periods and recurring time limits.
+      - id: 51
+        type: string
+        optional: true
+        sensitive: true
+        name: temporary_password_create
+      # [Online temporary password] is used to delete the online temporary
+      # password.
+      - id: 52
+        type: string
+        optional: true
+        persist: false
+        name: temporary_password_delete
+      # [Online temporary password] is used to modify the password content and
+      # corresponding validity period of the online temporary password.
+      - id: 53
+        type: string
+        optional: true
+        sensitive: true
+        persist: false
+        name: temporary_password_modify
+      # [Synchronous unlocking method] is used to synchronize all unlocking
+      # methods on the local device with the mobile phone. It is strongly
+      # recommended to enable this function.
+      - id: 54
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: synch_method
+      # [Report unlocking record] is used for the device to report the record
+      # of online temporary password unlocking.
+      - id: 55
+        type: integer
+        name: unlock_temp_pwd
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Remote unlocking operation] is used to trigger the remote unlocking
+      # operation of the device. It is recommended to enable this DP and
+      # develop this function. Only when this function is supported and the
+      # door lock is connected to the Bluetooth gateway, can the voice control
+      # door opening and closing function be realized with smart speaker
+      # products such as Alexa or Google Home.
+      - id: 61
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: remote_no_dp_key
+      # [Report unlocking records] is used for the device to report the records
+      # of remote unlocking via mobile phone.
+      - id: 62
+        type: integer
+        name: unlock_app
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the record
+      # of voice unlocking. This function depends on dp61 = remote door opening
+      # (with verification code). It needs to be enabled with dp62 and pay for
+      # the voice door opening value-added service at the iot platform
+      # value-added service center before this function can be used.
+      - id: 63
+        type: integer
+        name: unlock_voice
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Offline Password] is used to synchronize the timestamp between the
+      # cloud and the device. If it is not selected or not synchronized, the
+      # offline password will not be available.
+      - id: 64
+        type: string
+        optional: true
+        persist: false
+        name: password_offline_time
+      # [Offline password clear] When a single offline password is cleared
+      # locally on the device, the clearing operation record is reported.
+      - id: 65
+        type: string
+        optional: true
+        persist: false
+        name: offline_pwd_cleared
+      # [Offline password clear] When all offline passwords are cleared locally
+      # on the device, the clearing operation record is reported.
+      - id: 66
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: unlock_offline_clear
+      # [Report unlocking record] is used for the device to report offline
+      # password unlocking records.
+      - id: 67
+        type: string
+        name: unlock_offline_pwd
+        optional: true
+        persist: false
+        sensitive: true
+      # [Bluetooth Unlock] is used to configure the verification information
+      # required when the device performs Bluetooth unlocking.
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      # [Bluetooth Unlock] is used to trigger the device to perform Bluetooth
+      # unlocking. The trigger source can be a mobile phone, an accessory, or
+      # proximity unlocking.
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      # [Remote unlock] is used to configure the key required for remote
+      # unlocking. If the device needs to be unlocked remotely, this DP is
+      # required.
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+      # [Alarm record] When an alarm occurs locally, it is used to report the
+      # alarm record of the device. Corresponding value description:
+      # 0=fingerprint trial error alarm
+      # 1=password trial error alarm
+      # 2=card trial error alarm
+      # 3=face trial error alarm
+      # 4=fake lock (lock tongue stuck)
+      # 5=high temperature alarm
+      # 6=timeout failure to close the door
+      # 7=electronic lock tongue not popped up
+      # 8=anti-pry alarm, 9=key insertion
+      # 10=low power alarm,
+      # 11=battery exhaustion alarm
+      # 12=vibration alarm
+      # 13=arming alarm
+      # If the device only uses a few items in the alarm dp, the redundant
+      # content does not need to be deleted. If deleted, the serial number of
+      # the MCU needs to be adjusted when reporting. It is recommended that
+      # developers do not change the enumeration value in the alarm. In special
+      # circumstances, please add, delete, and configure multiple languages
+      # under the guidance of pm.
+      - id: 21
+        type: string
+        readonly: true
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # [Doorbell] If the local doorbell ringtone of the device supports
+      # switching, you can use this DP to switch the local doorbell ringtone of
+      # the door lock on the app; this DP is only enabled if the hardware of
+      # this smart lock product supports doorbell ringtone switching and the
+      # user is allowed to switch on the app.
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  # [Device Status] is used to display the power level in percentage. The range
+  # is -1 to 100. When the device reports a value of -1, the power level is not
+  # displayed. (Choose one from DP9).
+  - entity: sensor
+    class: battery
+    dps:
+      - id: 8
+        type: integer
+        readonly: true
+        name: sensor
+        unit: "%"
+        optional: true
+  # [Language] is used to set the playback language of the local navigation
+  # voice of the device.
+  # 0 = Simplified Chinese
+  # 1 = English
+  # 2 = Japanese
+  # 3 = German
+  # 4 = Spanish
+  # 5 = Latin
+  # 6 = French
+  # 7 = Russian
+  # 8 = Italian
+  # 9 = Traditional Chinese
+  # 10 = Korean
+  # This dp is only enabled if the hardware of this smart lock product supports
+  # language switching and allows users to switch on the app.
+  - entity: select
+    translation_key: language
+    category: config
+    dps:
+      - id: 28
+        type: string
+        name: option
+        optional: true
+        mapping:
+          - dps_val: chinese_simplified
+            value: chinese
+          - dps_val: english
+            value: english
+  # [Volume] is used to set the volume of the local navigation voice of the
+  # device. This dp is only enabled when the hardware of this smart lock
+  # product supports door lock volume adjustment and the user is allowed to
+  # switch the operation on the app.
+  - entity: select
+    name: Volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        optional: true
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  # [Device Status] If the device has indoor anti-lock detection, it is used to
+  # report the switch status of indoor anti-lock. Only select this dp when the
+  # hardware of this smart lock product can detect the anti-lock status and
+  # report the data. This dp is only used to display the anti-lock status on
+  # the door lock home page interface, and cannot perform anti-lock setting
+  # operations.
+  # Commented since DP gets reported even if the device doesn't support it.
+  # - entity: switch
+  #   name: Indoor Anti-Lock
+  #   category: config
+  #   dps:
+  #     - id: 32
+  #       type: boolean
+  #       name: switch
+  #       optional: true
+  # [Automatic Lock] is used to set the switch of the automatic locking
+  # function. Through this dp, you can set "Enable automatic locking" and "No
+  # automatic locking", that is, the device will not automatically lock, and it
+  # can also be used as a normally open mode. Only when the door lock hardware
+  # supports this setting, enable this dp and dp36, "Automatic Lock Delay" can
+  # be used together to achieve "Automatic Lock after Unlocking xx Seconds" to
+  # set the waiting time for automatic locking after unlocking.
+  - entity: switch
+    name: Automatic Lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+        optional: true
+  # [Automatic Lock] is used to set the delay time from unlocking the device to
+  # automatically locking the device. The setting range is 1s to 30min.
+  - entity: number
+    name: Automatic Lock Delay
+    icon: "mdi:lock-clock"
+    category: config
+    dps:
+      - id: 36
+        type: integer
+        name: value
+        optional: true
+        unit: s
+        range:
+          min: 1
+          max: 1800
+  # [Device Status] If the device has door opening and closing status
+  # detection, it is used to report the door opening and closing status. Only
+  # enable this dp if this function is supported.
+  # Commented since DP gets reported even if the device doesn't support it.
+  # - entity: select
+  #   category: config
+  #   name: Door Status
+  #   dps:
+  #     - id: 40
+  #       type: string
+  #       name: option
+  #       readonly: true
+  #       optional: true
+  #       mapping:
+  #         - dps_val: unknown
+  #           value: Unknown
+  #         - dps_val: open
+  #           value: Open
+  #         - dps_val: closed
+  #           value: Closed
+  # [Approach Unlock] is used to set the delay time of automatic locking in the
+  # approach unlock scenario. If it is not enabled, the original automatic
+  # locking delay time will be retained.
+  # Commented since DP gets reported even if the device doesn't support it.
+  # - entity: select
+  #   name: Approach Unlock Delay
+  #   category: config
+  #   dps:
+  #     - id: 89
+  #       type: string
+  #       name: option
+  #       optional: true
+  #       mapping:
+  #         - dps_val: inactive
+  #           value: Inactive
+  #         - dps_val: 4s
+  #           value: 4s
+  #         - dps_val: 6s
+  #           value: 6s
+  #         - dps_val: 8s
+  #           value: 8s
+  #         - dps_val: 10s
+  #           value: 10s
+  #         - dps_val: 12s
+  #           value: 12s


Reviews:
user: login: make-all
state: COMMENTED
author_association: OWNER
submitted_at: 2025-02-27T21:59:12Z
user: login: jayPare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T15:16:54Z
user: login: make-all
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-09T21:36:30Z
user: login: jayPare
body: I've addressed most of the comments, but there is still 1 TODO left. Also, to try it, I add th yaml file to `/homeassistant/custom_components/tuya_local/devices/`, but at the configuration, I'm not getting Hornbill Smart Lock in the list of proposed device. Anything else I need to do to test it locally?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:47:21Z
user: login: jayPare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:52:51Z
user: login: make-all
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-20T11:06:13Z
user: login: jayPare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T17:05:46Z

Review comments:
diff_hunk: @@ -0,0 +1,311 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity
+      - id: 18
+        name: open_inside
+        type: bool
path: custom_components/tuya_local/devices/hornbill_smart_lock.yaml
user: login: make-all
body: boolean
created_at: 2025-02-27T21:59:12Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,311 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity
+      - id: 18
+        name: open_inside
+        type: bool
path: custom_components/tuya_local/devices/hornbill_smart_lock.yaml
user: login: jayPare
body: Done!
created_at: 2025-02-28T15:16:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Rename to "unlock_temp_pwd"
created_at: 2025-03-09T14:55:32Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Does not need to be `persist: false`, as this has no special function, and keeping an old value when the device is not broadcasting one will not cause any undesired side effect.
created_at: 2025-03-09T14:57:15Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: These also do not benefit from being `persist: false`
created_at: 2025-03-09T15:00:17Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Change to `unlock_app`
created_at: 2025-03-09T15:01:37Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Change to `unlock_voice`
created_at: 2025-03-09T15:02:08Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Rename to `unlock_offline_pwd`
created_at: 2025-03-09T15:02:57Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+  - entity: number
+    name: auto_lock_time
+    class: duration
+    icon: "mdi:lock-clock"
+    category: config
+    dps:
+      - id: 36
+        type: integer
+        name: value
+        unit: s
+        range:
+          min: 1
+          max: 1800
+  - entity: select
+    name: closed_opened
+    category: config
+    dps:
+      - id: 40
+        type: string
+        name: option
+        mapping:
+          - dps_val: unknown
+            value: unknown
+          - dps_val: open
+            value: open
+          - dps_val: closed
+            value: closed
+  - entity: select
+    name: autolock_delay_level
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Entity name should be human readable: "Autolock delay..." The name also needs to make clear the difference between this setting and "Auto lock time" above.
created_at: 2025-03-09T21:30:58Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+  - entity: number
+    name: auto_lock_time
+    class: duration
+    icon: "mdi:lock-clock"
+    category: config
+    dps:
+      - id: 36
+        type: integer
+        name: value
+        unit: s
+        range:
+          min: 1
+          max: 1800
+  - entity: select
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Is this settable? it seems more like a sensor.
created_at: 2025-03-09T21:31:56Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+  - entity: number
+    name: auto_lock_time
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Name should be human readable. And time seems to mean "delay" here (not wall clock time).
created_at: 2025-03-09T21:34:50Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Name should be human readable
created_at: 2025-03-09T21:35:16Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-10T13:38:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-10T13:38:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-10T13:38:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-10T13:39:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-10T13:39:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+  - entity: number
+    name: auto_lock_time
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-10T13:43:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-10T13:45:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+  - entity: number
+    name: auto_lock_time
+    class: duration
+    icon: "mdi:lock-clock"
+    category: config
+    dps:
+      - id: 36
+        type: integer
+        name: value
+        unit: s
+        range:
+          min: 1
+          max: 1800
+  - entity: select
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: You are right, this is for the door state (closed/opened). However, this device doesn't have this feature, thus removing. It's weird that it was returned in query_things_data_model.json, isn't it?
created_at: 2025-03-10T14:10:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-11T19:44:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,312 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    model: Smart Lock
+    name: Key safe
+entities:
+  - entity: lock
+    dps:
+      - id: 1
+        type: string
+        name: unlock_method_create
+        optional: true
+        sensitive: true
+      - id: 2
+        type: string
+        name: unlock_method_delete
+        optional: true
+      - id: 3
+        type: string
+        name: unlock_method_modify
+        optional: true
+        sensitive: true
+      - id: 12
+        type: integer
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+      - id: 13
+        name: unlock_password
+        type: integer
+        optional: true
+        persist: false
+      - id: 15
+        name: unlock_card
+        type: integer
+        optional: true
+        persist: false
+      # TODO: Not sure it belongs in lock entity?
+      - id: 18
+        name: open_inside
+        type: boolean
+        # TODO: Not sure about those value
+        optional: true
+        persist: false
+      - id: 19
+        name: unlock_ble
+        type: integer
+        optional: true
+        persist: false
+      - id: 46
+        name: manual_lock
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 47  # Lock motor state
+        name: lock_motor_state
+        type: boolean
+        readonly: true
+        mapping:
+          - dps_val: true  # Unlocked
+            value: false
+          - dps_val: false  # Locked
+            value: true
+      - id: 51
+        type: string
+        name: temporary_password_creat
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 52
+        type: string
+        name: temporary_password_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 53
+        type: string
+        name: temporary_password_modify
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 54
+        type: string
+        name: temporary_passynch_methodword_delete
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 55
+        type: integer
+        name: unlock_temporary
+        optional: true
+        persist: false
+      - id: 61
+        type: string
+        name: remote_no_dp_key
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 62
+        type: integer
+        name: unlock_phone_remote
+        optional: true
+        persist: false
+      - id: 63
+        type: integer
+        name: unlock_voice_remote
+        optional: true
+        persist: false
+      - id: 64
+        type: string
+        name: password_offline_time
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 65
+        type: string
+        name: unlock_offline_clear_single
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 66
+        type: string
+        name: unlock_offline_clear
+        optional: true
+        persist: false
+        sensitive: true
+      # TODO: Why not working? PR#2824 also didn't include it.
+      # - id: 67
+      #   type: string
+      #   name: unlock_offline_pd
+      #   optional: true
+      #   persist: false
+      #   sensitive: true
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+    # TODO: Why not working? PR#2824 also didn't include it.
+    #  - id: 20
+    #    type: string
+    #    name: lock_record
+    #    optional: ?
+      # alarm_lock
+      - id: 21
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # doorbell_song
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  - entity: switch
+    name: reverse_lock
+    category: config
+    icon: "mdi:screen-lock-rotation"
+    dps:
+      - id: 32
+        type: boolean
+        name: switch
+  - entity: switch
+    name: automatic_lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+  - entity: number
+    name: auto_lock_time
+    class: duration
+    icon: "mdi:lock-clock"
+    category: config
+    dps:
+      - id: 36
+        type: integer
+        name: value
+        unit: s
+        range:
+          min: 1
+          max: 1800
+  - entity: select
+    name: closed_opened
+    category: config
+    dps:
+      - id: 40
+        type: string
+        name: option
+        mapping:
+          - dps_val: unknown
+            value: unknown
+          - dps_val: open
+            value: open
+          - dps_val: closed
+            value: closed
+  - entity: select
+    name: autolock_delay_level
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-12T16:52:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,481 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    name: Hornbill Y4 Smart Lock
+entities:
+  - entity: lock
+    dps:
+      # [Add unlocking method] After the device is online, add unlocking
+      # methods. Support adding fingerprint, password, card and face unlocking
+      # methods; support administrator tagging.
+      - id: 1
+        type: string
+        optional: true
+        sensitive: true
+        name: unlock_method_create
+      # [Delete unlocking method] After the device is online, delete the
+      # unlocking method. Supports deleting fingerprint, password, card and
+      # face unlocking methods. This dp appears in pairs with the added dp.
+      - id: 2
+        type: string
+        optional: true
+        sensitive: true
+        name: unlock_method_delete
+      # [Modify unlocking method] After the device is online, you can modify
+      # the name of the unlocking method and the content of the password.
+      - id: 3
+        type: string
+        optional: true
+        name: unlock_method_modify
+      # [Report unlocking record] is used for the device to report the record
+      # of fingerprint unlocking. This dp is only enabled when the smart lock
+      # product has a fingerprint recognition function in the hardware.
+      - id: 12
+        type: integer
+        readonly: true
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the record
+      # of unlocking with ordinary password. This dp is enabled only when the
+      # hardware of this smart lock product has a password button that can be
+      # used to unlock the door with a password.
+      - id: 13
+        name: unlock_password
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the door
+      # card unlocking record. This dp is enabled only when the smart lock
+      # product has a card swiping function in the hardware and can report the
+      # card swiping record.
+      - id: 15
+        name: unlock_card
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] If the device supports detection of door
+      # opening from the inside, it is used for the device to report the record
+      # of door opening from the inside. This dp is enabled only when the
+      # hardware of this smart lock product can detect and identify unlocking
+      # from the inside and can report the data.
+      - id: 18
+        name: open_inside
+        type: boolean
+        readonly: true
+        optional: true
+      # [Report unlocking record] is used for the device to report the door
+      # opening record generated by the mobile phone app Bluetooth unlocking.
+      # Generally, all Bluetooth locks support this function.
+      - id: 19
+        name: unlock_ble
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report lock record] is used for the device to report the lock record.
+      # If the lock is controlled by the mobile phone app or the lock is
+      # manually closed locally, this dp can be used to report.
+      - id: 20
+        type: string
+        optional: true
+        name: lock_record
+      # [Device Status] is used to report the status of the device clutch
+      # switch, that is, the switch status of the lock. This dp is enabled only
+      # when the product hardware supports the detection of the lock status.
+      - id: 47
+        type: boolean
+        optional: true
+        name: lock_motor_state
+        readonly: true
+      # [Online Temporary Password] is used to add an online temporary
+      # password, and supports setting time periods and recurring time limits.
+      - id: 51
+        type: string
+        optional: true
+        sensitive: true
+        name: temporary_password_create
+      # [Online temporary password] is used to delete the online temporary
+      # password.
+      - id: 52
+        type: string
+        optional: true
+        persist: false
+        name: temporary_password_delete
+      # [Online temporary password] is used to modify the password content and
+      # corresponding validity period of the online temporary password.
+      - id: 53
+        type: string
+        optional: true
+        sensitive: true
+        persist: false
+        name: temporary_password_modify
+      # [Synchronous unlocking method] is used to synchronize all unlocking
+      # methods on the local device with the mobile phone. It is strongly
+      # recommended to enable this function.
+      - id: 54
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: synch_method
+      # [Report unlocking record] is used for the device to report the record
+      # of online temporary password unlocking.
+      - id: 55
+        type: integer
+        name: unlock_temp_pwd
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Remote unlocking operation] is used to trigger the remote unlocking
+      # operation of the device. It is recommended to enable this DP and
+      # develop this function. Only when this function is supported and the
+      # door lock is connected to the Bluetooth gateway, can the voice control
+      # door opening and closing function be realized with smart speaker
+      # products such as Alexa or Google Home.
+      - id: 61
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: remote_no_dp_key
+      # [Report unlocking records] is used for the device to report the records
+      # of remote unlocking via mobile phone.
+      - id: 62
+        type: integer
+        name: unlock_app
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the record
+      # of voice unlocking. This function depends on dp61 = remote door opening
+      # (with verification code). It needs to be enabled with dp62 and pay for
+      # the voice door opening value-added service at the iot platform
+      # value-added service center before this function can be used.
+      - id: 63
+        type: integer
+        name: unlock_voice
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Offline Password] is used to synchronize the timestamp between the
+      # cloud and the device. If it is not selected or not synchronized, the
+      # offline password will not be available.
+      - id: 64
+        type: string
+        optional: true
+        persist: false
+        name: password_offline_time
+      # [Offline password clear] When a single offline password is cleared
+      # locally on the device, the clearing operation record is reported.
+      - id: 65
+        type: string
+        optional: true
+        persist: false
+        name: offline_pwd_cleared
+      # [Offline password clear] When all offline passwords are cleared locally
+      # on the device, the clearing operation record is reported.
+      - id: 66
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: unlock_offline_clear
+      # [Report unlocking record] is used for the device to report offline
+      # password unlocking records.
+      - id: 67
+        type: string
+        name: unlock_offline_pwd
+        optional: true
+        persist: false
+        sensitive: true
+      # [Bluetooth Unlock] is used to configure the verification information
+      # required when the device performs Bluetooth unlocking.
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      # [Bluetooth Unlock] is used to trigger the device to perform Bluetooth
+      # unlocking. The trigger source can be a mobile phone, an accessory, or
+      # proximity unlocking.
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      # [Remote unlock] is used to configure the key required for remote
+      # unlocking. If the device needs to be unlocked remotely, this DP is
+      # required.
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+      # [Alarm record] When an alarm occurs locally, it is used to report the
+      # alarm record of the device. Corresponding value description:
+      # 0=fingerprint trial error alarm
+      # 1=password trial error alarm
+      # 2=card trial error alarm
+      # 3=face trial error alarm
+      # 4=fake lock (lock tongue stuck)
+      # 5=high temperature alarm
+      # 6=timeout failure to close the door
+      # 7=electronic lock tongue not popped up
+      # 8=anti-pry alarm, 9=key insertion
+      # 10=low power alarm,
+      # 11=battery exhaustion alarm
+      # 12=vibration alarm
+      # 13=arming alarm
+      # If the device only uses a few items in the alarm dp, the redundant
+      # content does not need to be deleted. If deleted, the serial number of
+      # the MCU needs to be adjusted when reporting. It is recommended that
+      # developers do not change the enumeration value in the alarm. In special
+      # circumstances, please add, delete, and configure multiple languages
+      # under the guidance of pm.
+      - id: 21
+        type: string
+        readonly: true
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # [Doorbell] If the local doorbell ringtone of the device supports
+      # switching, you can use this DP to switch the local doorbell ringtone of
+      # the door lock on the app; this DP is only enabled if the hardware of
+      # this smart lock product supports doorbell ringtone switching and the
+      # user is allowed to switch on the app.
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  # [Device Status] is used to display the power level in percentage. The range
+  # is -1 to 100. When the device reports a value of -1, the power level is not
+  # displayed. (Choose one from DP9).
+  - entity: sensor
+    class: battery
+    dps:
+      - id: 8
+        type: integer
+        readonly: true
+        name: sensor
+        unit: "%"
+        optional: true
+  # [Language] is used to set the playback language of the local navigation
+  # voice of the device.
+  # 0 = Simplified Chinese
+  # 1 = English
+  # 2 = Japanese
+  # 3 = German
+  # 4 = Spanish
+  # 5 = Latin
+  # 6 = French
+  # 7 = Russian
+  # 8 = Italian
+  # 9 = Traditional Chinese
+  # 10 = Korean
+  # This dp is only enabled if the hardware of this smart lock product supports
+  # language switching and allows users to switch on the app.
+  - entity: select
+    translation_key: language
+    category: config
+    name: Door Lock Language
+    dps:
+      - id: 28
+        type: string
+        name: option
+        optional: true
+        mapping:
+          - dps_val: chinese_simplified
+            value: chinese
+          - dps_val: english
+            value: english
+  # [Volume] is used to set the volume of the local navigation voice of the
+  # device. This dp is only enabled when the hardware of this smart lock
+  # product supports door lock volume adjustment and the user is allowed to
+  # switch the operation on the app.
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        optional: true
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  # [Device Status] If the device has indoor anti-lock detection, it is used to
+  # report the switch status of indoor anti-lock. Only select this dp when the
+  # hardware of this smart lock product can detect the anti-lock status and
+  # report the data. This dp is only used to display the anti-lock status on
+  # the door lock home page interface, and cannot perform anti-lock setting
+  # operations.
+  # Commented since DP gets reported even if the device doesn't support it.
+  # - entity: switch
+  #   name: Indoor Anti-Lock
+  #   category: config
+  #   dps:
+  #     - id: 32
+  #       type: boolean
+  #       name: switch
+  #       optional: true
+  # [Automatic Lock] is used to set the switch of the automatic locking
+  # function. Through this dp, you can set "Enable automatic locking" and "No
+  # automatic locking", that is, the device will not automatically lock, and it
+  # can also be used as a normally open mode. Only when the door lock hardware
+  # supports this setting, enable this dp and dp36, "Automatic Lock Delay" can
+  # be used together to achieve "Automatic Lock after Unlocking xx Seconds" to
+  # set the waiting time for automatic locking after unlocking.
+  - entity: switch
+    name: Automatic Lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+        optional: true
+  # [Automatic Lock] is used to set the delay time from unlocking the device to
+  # automatically locking the device. The setting range is 1s to 30min.
+  - entity: number
+    name: Automatic Lock Delay
+    icon: "mdi:lock-clock"
+    category: config
+    dps:
+      - id: 36
+        type: integer
+        name: value
+        optional: true
+        unit: s
+        range:
+          min: 1
+          max: 1800
+  # [Device Status] If the device has door opening and closing status
+  # detection, it is used to report the door opening and closing status. Only
+  # enable this dp if this function is supported.
+  # Commented since DP gets reported even if the device doesn't support it.
+  # - entity: select
+  #   category: config
+  #   name: Door Status
+  #   dps:
+  #     - id: 40
+  #       type: string
+  #       name: option
+  #       readonly: true
+  #       optional: true
+  #       mapping:
+  #         - dps_val: unknown
+  #           value: Unknown
+  #         - dps_val: open
+  #           value: Open
+  #         - dps_val: closed
+  #           value: Closed
+  # [Lock Operation] is used to trigger the device to perform the lock
+  # operation, which can be used for automatic lock and manual lock operation.
+  # To report the lock (close) record, you need to use dp=20 lock record. You
+  # need to use 2 dp in combination to fully implement the lock operation and
+  # lock record reporting. In addition to these two dp, you also need to use
+  # dp47=lock status to report the lock status, so that the unlock/lock status
+  # displayed on the app is consistent with the local hardware.
+  - entity: lock
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: make-all
body: Why is this a separate entity from the lock entity above? The one above does not have a lock dp, so merging this in should be possible.
created_at: 2025-03-20T11:06:06Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,481 @@
+name: Keypad lock
+products:
+  - id: g7rboh0x
+    manufacturer: Hornbill
+    name: Hornbill Y4 Smart Lock
+entities:
+  - entity: lock
+    dps:
+      # [Add unlocking method] After the device is online, add unlocking
+      # methods. Support adding fingerprint, password, card and face unlocking
+      # methods; support administrator tagging.
+      - id: 1
+        type: string
+        optional: true
+        sensitive: true
+        name: unlock_method_create
+      # [Delete unlocking method] After the device is online, delete the
+      # unlocking method. Supports deleting fingerprint, password, card and
+      # face unlocking methods. This dp appears in pairs with the added dp.
+      - id: 2
+        type: string
+        optional: true
+        sensitive: true
+        name: unlock_method_delete
+      # [Modify unlocking method] After the device is online, you can modify
+      # the name of the unlocking method and the content of the password.
+      - id: 3
+        type: string
+        optional: true
+        name: unlock_method_modify
+      # [Report unlocking record] is used for the device to report the record
+      # of fingerprint unlocking. This dp is only enabled when the smart lock
+      # product has a fingerprint recognition function in the hardware.
+      - id: 12
+        type: integer
+        readonly: true
+        name: unlock_fingerprint
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the record
+      # of unlocking with ordinary password. This dp is enabled only when the
+      # hardware of this smart lock product has a password button that can be
+      # used to unlock the door with a password.
+      - id: 13
+        name: unlock_password
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the door
+      # card unlocking record. This dp is enabled only when the smart lock
+      # product has a card swiping function in the hardware and can report the
+      # card swiping record.
+      - id: 15
+        name: unlock_card
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] If the device supports detection of door
+      # opening from the inside, it is used for the device to report the record
+      # of door opening from the inside. This dp is enabled only when the
+      # hardware of this smart lock product can detect and identify unlocking
+      # from the inside and can report the data.
+      - id: 18
+        name: open_inside
+        type: boolean
+        readonly: true
+        optional: true
+      # [Report unlocking record] is used for the device to report the door
+      # opening record generated by the mobile phone app Bluetooth unlocking.
+      # Generally, all Bluetooth locks support this function.
+      - id: 19
+        name: unlock_ble
+        type: integer
+        readonly: true
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report lock record] is used for the device to report the lock record.
+      # If the lock is controlled by the mobile phone app or the lock is
+      # manually closed locally, this dp can be used to report.
+      - id: 20
+        type: string
+        optional: true
+        name: lock_record
+      # [Device Status] is used to report the status of the device clutch
+      # switch, that is, the switch status of the lock. This dp is enabled only
+      # when the product hardware supports the detection of the lock status.
+      - id: 47
+        type: boolean
+        optional: true
+        name: lock_motor_state
+        readonly: true
+      # [Online Temporary Password] is used to add an online temporary
+      # password, and supports setting time periods and recurring time limits.
+      - id: 51
+        type: string
+        optional: true
+        sensitive: true
+        name: temporary_password_create
+      # [Online temporary password] is used to delete the online temporary
+      # password.
+      - id: 52
+        type: string
+        optional: true
+        persist: false
+        name: temporary_password_delete
+      # [Online temporary password] is used to modify the password content and
+      # corresponding validity period of the online temporary password.
+      - id: 53
+        type: string
+        optional: true
+        sensitive: true
+        persist: false
+        name: temporary_password_modify
+      # [Synchronous unlocking method] is used to synchronize all unlocking
+      # methods on the local device with the mobile phone. It is strongly
+      # recommended to enable this function.
+      - id: 54
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: synch_method
+      # [Report unlocking record] is used for the device to report the record
+      # of online temporary password unlocking.
+      - id: 55
+        type: integer
+        name: unlock_temp_pwd
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Remote unlocking operation] is used to trigger the remote unlocking
+      # operation of the device. It is recommended to enable this DP and
+      # develop this function. Only when this function is supported and the
+      # door lock is connected to the Bluetooth gateway, can the voice control
+      # door opening and closing function be realized with smart speaker
+      # products such as Alexa or Google Home.
+      - id: 61
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: remote_no_dp_key
+      # [Report unlocking records] is used for the device to report the records
+      # of remote unlocking via mobile phone.
+      - id: 62
+        type: integer
+        name: unlock_app
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Report unlocking record] is used for the device to report the record
+      # of voice unlocking. This function depends on dp61 = remote door opening
+      # (with verification code). It needs to be enabled with dp62 and pay for
+      # the voice door opening value-added service at the iot platform
+      # value-added service center before this function can be used.
+      - id: 63
+        type: integer
+        name: unlock_voice
+        optional: true
+        persist: false
+        range:
+          min: 0
+          max: 999
+      # [Offline Password] is used to synchronize the timestamp between the
+      # cloud and the device. If it is not selected or not synchronized, the
+      # offline password will not be available.
+      - id: 64
+        type: string
+        optional: true
+        persist: false
+        name: password_offline_time
+      # [Offline password clear] When a single offline password is cleared
+      # locally on the device, the clearing operation record is reported.
+      - id: 65
+        type: string
+        optional: true
+        persist: false
+        name: offline_pwd_cleared
+      # [Offline password clear] When all offline passwords are cleared locally
+      # on the device, the clearing operation record is reported.
+      - id: 66
+        type: string
+        optional: true
+        persist: false
+        sensitive: true
+        name: unlock_offline_clear
+      # [Report unlocking record] is used for the device to report offline
+      # password unlocking records.
+      - id: 67
+        type: string
+        name: unlock_offline_pwd
+        optional: true
+        persist: false
+        sensitive: true
+      # [Bluetooth Unlock] is used to configure the verification information
+      # required when the device performs Bluetooth unlocking.
+      - id: 70
+        type: string
+        name: check_code_set
+        optional: true
+        persist: false
+        sensitive: true
+      # [Bluetooth Unlock] is used to trigger the device to perform Bluetooth
+      # unlocking. The trigger source can be a mobile phone, an accessory, or
+      # proximity unlocking.
+      - id: 71
+        type: string
+        name: ble_unlock_check
+        optional: true
+        persist: false
+        sensitive: true
+      # [Remote unlock] is used to configure the key required for remote
+      # unlocking. If the device needs to be unlocked remotely, this DP is
+      # required.
+      - id: 73
+        type: string
+        name: remote_pd_setkey_check
+        optional: true
+        persist: false
+        sensitive: true
+  - entity: event
+    name: Alert
+    dps:
+      # [Alarm record] When an alarm occurs locally, it is used to report the
+      # alarm record of the device. Corresponding value description:
+      # 0=fingerprint trial error alarm
+      # 1=password trial error alarm
+      # 2=card trial error alarm
+      # 3=face trial error alarm
+      # 4=fake lock (lock tongue stuck)
+      # 5=high temperature alarm
+      # 6=timeout failure to close the door
+      # 7=electronic lock tongue not popped up
+      # 8=anti-pry alarm, 9=key insertion
+      # 10=low power alarm,
+      # 11=battery exhaustion alarm
+      # 12=vibration alarm
+      # 13=arming alarm
+      # If the device only uses a few items in the alarm dp, the redundant
+      # content does not need to be deleted. If deleted, the serial number of
+      # the MCU needs to be adjusted when reporting. It is recommended that
+      # developers do not change the enumeration value in the alarm. In special
+      # circumstances, please add, delete, and configure multiple languages
+      # under the guidance of pm.
+      - id: 21
+        type: string
+        readonly: true
+        name: event
+        optional: true
+        mapping:
+          - dps_val: wrong_finger
+            value: wrong_finger
+          - dps_val: wrong_password
+            value: wrong_password
+          - dps_val: wrong_card
+            value: wrong_card
+          - dps_val: wrong_face
+            value: wrong_face
+          - dps_val: tongue_bad
+            value: tongue_bad
+          - dps_val: too_hot
+            value: too_hot
+          - dps_val: unclosed_time
+            value: unclosed_time
+          - dps_val: tongue_not_out
+            value: tongue_not_out
+          - dps_val: pry
+            value: pry
+          - dps_val: key_in
+            value: key_in
+          - dps_val: low_battery
+            value: low_battery
+          - dps_val: power_off
+            value: power_off
+          - dps_val: shock
+            value: shock
+          - dps_val: defense
+            value: defense
+          - dps_val: wrong_unlock
+            value: wrong_unlock
+      # [Doorbell] If the local doorbell ringtone of the device supports
+      # switching, you can use this DP to switch the local doorbell ringtone of
+      # the door lock on the app; this DP is only enabled if the hardware of
+      # this smart lock product supports doorbell ringtone switching and the
+      # user is allowed to switch on the app.
+      - id: 26
+        type: string
+        name: event
+        optional: true
+        mapping:
+          - dps_val: ding_0
+            value: ding_0
+          - dps_val: ding_1
+            value: ding_1
+  # [Device Status] is used to display the power level in percentage. The range
+  # is -1 to 100. When the device reports a value of -1, the power level is not
+  # displayed. (Choose one from DP9).
+  - entity: sensor
+    class: battery
+    dps:
+      - id: 8
+        type: integer
+        readonly: true
+        name: sensor
+        unit: "%"
+        optional: true
+  # [Language] is used to set the playback language of the local navigation
+  # voice of the device.
+  # 0 = Simplified Chinese
+  # 1 = English
+  # 2 = Japanese
+  # 3 = German
+  # 4 = Spanish
+  # 5 = Latin
+  # 6 = French
+  # 7 = Russian
+  # 8 = Italian
+  # 9 = Traditional Chinese
+  # 10 = Korean
+  # This dp is only enabled if the hardware of this smart lock product supports
+  # language switching and allows users to switch on the app.
+  - entity: select
+    translation_key: language
+    category: config
+    name: Door Lock Language
+    dps:
+      - id: 28
+        type: string
+        name: option
+        optional: true
+        mapping:
+          - dps_val: chinese_simplified
+            value: chinese
+          - dps_val: english
+            value: english
+  # [Volume] is used to set the volume of the local navigation voice of the
+  # device. This dp is only enabled when the hardware of this smart lock
+  # product supports door lock volume adjustment and the user is allowed to
+  # switch the operation on the app.
+  - entity: select
+    name: Lock volume
+    category: config
+    dps:
+      - id: 31
+        type: string
+        name: option
+        optional: true
+        mapping:
+          - dps_val: mute
+            value: Mute
+          - dps_val: low
+            value: Low
+          - dps_val: normal
+            value: Normal
+          - dps_val: high
+            value: High
+  # [Device Status] If the device has indoor anti-lock detection, it is used to
+  # report the switch status of indoor anti-lock. Only select this dp when the
+  # hardware of this smart lock product can detect the anti-lock status and
+  # report the data. This dp is only used to display the anti-lock status on
+  # the door lock home page interface, and cannot perform anti-lock setting
+  # operations.
+  # Commented since DP gets reported even if the device doesn't support it.
+  # - entity: switch
+  #   name: Indoor Anti-Lock
+  #   category: config
+  #   dps:
+  #     - id: 32
+  #       type: boolean
+  #       name: switch
+  #       optional: true
+  # [Automatic Lock] is used to set the switch of the automatic locking
+  # function. Through this dp, you can set "Enable automatic locking" and "No
+  # automatic locking", that is, the device will not automatically lock, and it
+  # can also be used as a normally open mode. Only when the door lock hardware
+  # supports this setting, enable this dp and dp36, "Automatic Lock Delay" can
+  # be used together to achieve "Automatic Lock after Unlocking xx Seconds" to
+  # set the waiting time for automatic locking after unlocking.
+  - entity: switch
+    name: Automatic Lock
+    category: config
+    icon: "mdi:lock-reset"
+    dps:
+      - id: 33
+        type: boolean
+        name: switch
+        optional: true
+  # [Automatic Lock] is used to set the delay time from unlocking the device to
+  # automatically locking the device. The setting range is 1s to 30min.
+  - entity: number
+    name: Automatic Lock Delay
+    icon: "mdi:lock-clock"
+    category: config
+    dps:
+      - id: 36
+        type: integer
+        name: value
+        optional: true
+        unit: s
+        range:
+          min: 1
+          max: 1800
+  # [Device Status] If the device has door opening and closing status
+  # detection, it is used to report the door opening and closing status. Only
+  # enable this dp if this function is supported.
+  # Commented since DP gets reported even if the device doesn't support it.
+  # - entity: select
+  #   category: config
+  #   name: Door Status
+  #   dps:
+  #     - id: 40
+  #       type: string
+  #       name: option
+  #       readonly: true
+  #       optional: true
+  #       mapping:
+  #         - dps_val: unknown
+  #           value: Unknown
+  #         - dps_val: open
+  #           value: Open
+  #         - dps_val: closed
+  #           value: Closed
+  # [Lock Operation] is used to trigger the device to perform the lock
+  # operation, which can be used for automatic lock and manual lock operation.
+  # To report the lock (close) record, you need to use dp=20 lock record. You
+  # need to use 2 dp in combination to fully implement the lock operation and
+  # lock record reporting. In addition to these two dp, you also need to use
+  # dp47=lock status to report the lock status, so that the unlock/lock status
+  # displayed on the app is consistent with the local hardware.
+  - entity: lock
path: custom_components/tuya_local/devices/hornbill_y4_smart_lock.yaml
user: login: jayPare
body: Done.
created_at: 2025-03-20T17:05:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: jayPare
created_at: 2025-02-27T15:20:07Z
body: This was based on #1830  and #2824, but I still need to understand some parts. Maybe @rudironsonijr and @josefelixh can chip in?
author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-02T15:38:03Z
body: @make-all I'm trying to run test_device_config.py locally but I always get this:
`ModuleNotFoundError: No module named 'custom_components.tuya_local'`

I'm in a venv and installed all the modules using requirements-dev.txt. I also tried to add `custom_components/` and `custom_components/tuya_local/` in PYTHONPATH without any success.

What am I missing?
author_association: CONTRIBUTOR
user: login: make-all
created_at: 2025-03-02T15:55:16Z
body: I don't need to add anything to my PYTHONPATH to run pytest. To run the utils with python, I need to add . to PYTHONPATH though.
author_association: OWNER
user: login: jayPare
created_at: 2025-03-02T15:59:50Z
body: > I don't need to add anything to my PYTHONPATH to run pytest. To run the utils with python, I need to add . to PYTHONPATH though.

So you simply run `python3 tests/test_device_config.py`?
author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-02T16:02:01Z
body: > I don't need to add anything to my PYTHONPATH to run pytest. To run the utils with python, I need to add . to PYTHONPATH though.

Adding `.` seems to get me further, but now I get:

```
Traceback (most recent call last):
  File "/home/jpare/git/tuya-local/tests/test_device_config.py", line 22, in <module>
    from .const import GPPH_HEATER_PAYLOAD, KOGAN_HEATER_PAYLOAD
ImportError: attempted relative import with no known parent package
```
author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-03T22:51:55Z
body: > I don't need to add anything to my PYTHONPATH to run pytest. To run the utils with python, I need to add . to PYTHONPATH though.

My bad, I need to use `pytest tests/test_device_config.py`. Still a couple of TODO in `hornbill_y4_smart_lock.yaml` I need help with.
author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-11T19:57:42Z
body: > I've addressed most of the comments, but there is still 1 TODO left. Also, to try it, I add th yaml file to `/homeassistant/custom_components/tuya_local/devices/`, but at the configuration, I'm not getting Hornbill Smart Lock in the list of proposed device. Anything else I need to do to test it locally?

Adding what I see in HA logs:
```
[33m2025-03-11 15:51:59.879 WARNING (MainThread) [custom_components.tuya_local.config_flow] Adding Bluetooth gateway device with product id yy2bmcoh
[33m2025-03-11 15:52:00.301 WARNING (MainThread) [custom_components.tuya_local.config_flow] Device matches None with quality of 0%. DPS: {"updated_at": 1741722712.6835952, "61": "AAAB"}
[33m2025-03-11 15:52:00.301 WARNING (MainThread) [custom_components.tuya_local.config_flow] Include the previous log messages with any new device request to https://github.com/make-all/tuya-local/issues/
```
author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-12T18:11:47Z
body: > > I've addressed most of the comments, but there is still 1 TODO left. Also, to try it, I add th yaml file to `/homeassistant/custom_components/tuya_local/devices/`, but at the configuration, I'm not getting Hornbill Smart Lock in the list of proposed device. Anything else I need to do to test it locally?
> 
> Adding what I see in HA logs:
> 
> ```
> [33m2025-03-11 15:51:59.879 WARNING (MainThread) [custom_components.tuya_local.config_flow] Adding Bluetooth gateway device with product id yy2bmcoh
> [33m2025-03-11 15:52:00.301 WARNING (MainThread) [custom_components.tuya_local.config_flow] Device matches None with quality of 0%. DPS: {"updated_at": 1741722712.6835952, "61": "AAAB"}
> [33m2025-03-11 15:52:00.301 WARNING (MainThread) [custom_components.tuya_local.config_flow] Include the previous log messages with any new device request to https://github.com/make-all/tuya-local/issues/
> ```

Here is a screenshot of the choices I'm getting:
![image](https://github.com/user-attachments/assets/d63a7b77-7fd5-400a-ad16-301fa0ef1d7a)

author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-12T18:24:23Z
body: After activating debug, here is what I see:
```
2025-03-12 14:13:11.193 DEBUG (SyncWorker_4) [custom_components.tuya_local.helpers.device_config] Loaded device config hornbill_y4_smart_lock.yaml
2025-03-12 14:13:11.194 DEBUG (SyncWorker_4) [custom_components.tuya_local.helpers.device_config] Not match for Keypad lock, missing required DPs: [{'31': 'str'}, {'32': 'bool'}, {'33': 'bool'}, {'36': 'int'}, {'89': 'str'}]
```

@make-all it's weird it seems those DP require str, bool and int instead of string, boolean and integer. If I try to replace 31, 32, 33, 36 and 89 with string, boolean, boolean, integer and string, I get `Unknown error occurred` and in the log I have:

```
2025-03-12 14:26:42.567 DEBUG (SyncWorker_2) [custom_components.tuya_local.helpers.device_config] Loaded device config hornbill_y4_smart_lock.yaml
2025-03-12 14:26:42.568 ERROR (MainThread) [aiohttp.server] Error handling request from 192.168.0.13
Traceback (most recent call last):
  File "/usr/local/lib/python3.13/site-packages/aiohttp/web_protocol.py", line 480, in _handle_request
    resp = await request_handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/aiohttp/web_app.py", line 569, in _handle
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/aiohttp/web_middlewares.py", line 117, in impl
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/security_filter.py", line 92, in security_filter_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/forwarded.py", line 210, in forwarded_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/request_context.py", line 26, in request_context_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 86, in ban_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 242, in auth_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/headers.py", line 32, in headers_middleware
    response = await handler(request)
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/http.py", line 73, in handle
    result = await handler(request, **request.match_info)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/decorators.py", line 81, in with_admin
    return await func(self, request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/config/config_entries.py", line 232, in post
    return await super().post(request, flow_id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/data_validator.py", line 74, in wrapper
    return await method(view, request, data, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/data_entry_flow.py", line 121, in post
    result = await self._flow_mgr.async_configure(flow_id, data)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 340, in async_configure
    result = await self._async_configure(flow_id, user_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 387, in _async_configure
    result = await self._async_handle_step(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        flow, cur_step["step_id"], user_input
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 490, in _async_handle_step
    result: _FlowResultT = await getattr(flow, method)(user_input)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/tuya_local/config_flow.py", line 346, in async_step_local
    return await self.async_step_select_type()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/tuya_local/config_flow.py", line 384, in async_step_select_type
    for type in await self.device.async_possible_types():
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/tuya_local/device.py", line 410, in async_possible_types
    return await self._hass.async_add_executor_job(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<3 lines>...
    )
    ^
  File "/usr/local/lib/python3.13/concurrent/futures/thread.py", line 59, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/custom_components/tuya_local/device.py", line 38, in _collect_possible_matches
    return list(possible_matches(cached_state, product_ids))
  File "/config/custom_components/tuya_local/helpers/device_config.py", line 1076, in possible_matches
    if parsed.matches(dps, product_ids):
       ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/tuya_local/helpers/device_config.py", line 164, in matches
    [{dp.id: dp.type.__name__} for dp in missing_dps],
             ^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute '__name__'. Did you mean: '__ne__'
[2025-03-12 14:27:06,190] [customer-mq] _on_log: Sending PINGREQ
```
author_association: CONTRIBUTOR
user: login: make-all
created_at: 2025-03-12T18:38:26Z
body: I think your types were OK to start with, the error message is auto-generated by Python, so using Python's internal names for the types. But those dps need to be marked `optional: true` because they are missing from what the device is reporting (at least sometimes)
author_association: OWNER
user: login: jayPare
created_at: 2025-03-14T16:26:24Z
body: Result as of now: 
![image](https://github.com/user-attachments/assets/3f7b262b-7829-4ddb-b9b7-784ab2258ebe)

author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-15T15:27:03Z
body: @make-all have you seen some lock where the lock state is reported correctly, but when I hit unlock, the device stays locked since the motor is going in the lock direction. Same goes for when I hit lock, the device stays unlocked since the motor is going in the unlock direction. In Smart Life I have a "Unlock direction" option (left/right), but it is set correctly since when I lock/unlock from Smart Life app, it's working correclty.

I thought maybe the DP 46 needs a lock direction DP to know which side go, but I don't have any DP corresponding to lock direction. There is a DP called reverse_lock, but when I change the  "Unlock direction" option in Smart Life and update device status in Tuya Developer Platform (debug section), it has no effect on the reverse_lock switch. Same for when I trigger reverse_lock from Tuya Developer Platform, the "Unlock direction" option doesn't change in Smart Life.

I don't see any other DP that could match the lock_direction. I even try to see in Tuya Developer Platform if anything was changing when updating "Unlock direction" and Smart Life and I found nothing. Maybe there is another way to have DP46 go in a different direction?
author_association: CONTRIBUTOR
user: login: make-all
created_at: 2025-03-16T14:30:08Z
body: reverse_lock I think corresponds to a "snib" function, where you can lock the lock in an unlocked (maybe also locked) state.

I don't think you can compare lock/unlock functionality to the app the device is paired with, as the way secure Tuya locks work is the app shares a key with the lock when pairing so that only that app can be used to lock/unlock the lock (it is a separate key from the local_key used for communication). The lock/unlock process involves the remote_no_dp_key and remote_pd_setkey_check dps.
author_association: OWNER
user: login: jayPare
created_at: 2025-03-16T15:02:26Z
body: Ok so do you know what could cause the lock to go in opposite direction when locking/unlocking it?
author_association: CONTRIBUTOR
user: login: jayPare
created_at: 2025-03-20T17:08:20Z
body: Correct me if I'm wrong, but from what I understood, it's not really possible to add the unlock function to tuya-local. I was mainly adding support for this lock in hope of getting the unlock function available in HA, but it was a good exercise to play with tuya-local and add available support for this lock in tuya-local. I hope with my last patch everything is fine.
author_association: CONTRIBUTOR