-
Notifications
You must be signed in to change notification settings - Fork 543
Add econet bulldog gatelock #2998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Econet-Controls-Inc
wants to merge
5
commits into
SmartThingsCommunity:main
Choose a base branch
from
Econet-Controls-Inc:add-econet-bulldog-gatelock
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ac106e0
Add Econet Bulldog GateLock Matter Edge driver
Econet-Controls-Inc 08567e1
Rename driver folder to drivers/EconetControlsInc/
Econet-Controls-Inc 489bf0e
Sync driver source with working channel-published version
Econet-Controls-Inc a143b33
driver(tamper): auto-clear tamperAlert 15 s after DoorLockAlarm event
Econet-Controls-Inc b1e5dba
driver(fingerprints): add required deviceLabel field
Econet-Controls-Inc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Econet GateLock — SmartThings Edge Driver (Matter) | ||
|
|
||
| Custom SmartThings Edge driver for the Econet Bulldog GateLock. Built on the Matter-specific `st.matter.driver` class so the secure Matter session (`matter_channel`) is established per device. | ||
|
|
||
| ## Capabilities Exposed | ||
|
|
||
| | SmartThings Capability | Matter Source | What it shows | | ||
| |---|---|---| | ||
| | **lock** | DoorLock cluster, LockState (attr 0x0000) | Locked / Unlocked / Not Fully Locked | | ||
| | **contactSensor** | DoorLock cluster, DoorState (attr 0x0003) | Door Open / Closed (driven by reed switch) | | ||
| | **tamperAlert** | DoorLock cluster, DoorLockAlarm event | Tampered when 4-strike PIN limit hit on the keypad | | ||
| | **battery** | PowerSource cluster, BatPercentRemaining (attr 0x000C) | 0–100% | | ||
| | **firmwareUpdate** | (infrastructure) | Required for Matter device handshake | | ||
| | **refresh** | (infrastructure) | Manual re-subscribe from the SmartThings app | | ||
|
|
||
| PIN management and auto-relock configuration are not exposed by this driver. PINs are managed on the lock's keypad in admin mode; auto-relock can be set via Matter's standard cluster from any other Matter controller (or the firmware shell). | ||
|
|
||
| ## Reed-switch contact sensor | ||
|
|
||
| The reed switch on GPIO0.28 triggers `sendDoorStateChangeAlarmEvent()` in firmware, which updates the Matter `DoorState` attribute. This driver maps it to the SmartThings **Contact Sensor** tile: | ||
| - `DoorClosed (1)` → **closed** | ||
| - Anything else → **open** | ||
|
|
||
| ## Tamper alert | ||
|
|
||
| When the keypad's 4-strikes-in-20-seconds brute-force protection trips, the firmware fires a `DoorLockAlarm` event with `alarmCode = kWrongCodeEntryLimit (4)`. The driver maps this to the standard **tamperAlert** capability ("tampered" badge in the app) and auto-clears the state after 15 seconds, slightly longer than the firmware's ~10-second keypad lockout window. On `device_added` the driver also emits `clear` to ensure a known initial state. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - SmartThings Hub with Matter support (v46+ firmware) | ||
| - SmartThings CLI (`@smartthings/cli`) | ||
| - Personal Access Token from https://account.smartthings.com/tokens (set as `SMARTTHINGS_TOKEN` env var) | ||
|
|
||
| ## Build & Deploy | ||
|
|
||
| ```bash | ||
| cd smartthings-edge-driver | ||
| smartthings edge:drivers:package | ||
| # Returns a driver ID | ||
|
|
||
| smartthings edge:channels:assign <driver-id> <version> -C <channel-id> | ||
| smartthings edge:drivers:install <driver-id> --hub <hub-id> -C <channel-id> | ||
| ``` | ||
|
|
||
| ## Re-deploy after edits | ||
|
|
||
| After every code change: | ||
|
|
||
| ```bash | ||
| # Package + auto-assign + install | ||
| smartthings edge:drivers:package -C <channel-id> --hub <hub-id> | ||
|
|
||
| # If the hub doesn't pick up the new version (cached), force re-install: | ||
| smartthings edge:drivers:install <driver-id> --hub <hub-id> -C <channel-id> | ||
| ``` | ||
|
|
||
| ## Live logs | ||
|
|
||
| ```bash | ||
| smartthings edge:drivers:logcat <driver-id> | ||
| ``` | ||
|
|
||
| ## File structure | ||
|
|
||
| ``` | ||
| smartthings-edge-driver/ | ||
| ├── config.yml # Driver metadata | ||
| ├── fingerprints.yml # Matter vendor 5480 / product 10 match | ||
| ├── profiles/ | ||
| │ └── gatelock-matter.yml # Capability list | ||
| ├── src/ | ||
| │ └── init.lua # Driver code (uses st.matter.driver) | ||
| └── README.md | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| - The driver MUST use `MatterDriver = require "st.matter.driver"` and instantiate via `MatterDriver(packageKey, driverTable)`. The generic `st.driver` does not establish the Matter secure session and `device:subscribe()` will fail with `matter_channel nil`. | ||
| - `subscribed_attributes` is keyed by SmartThings capability ID, with values being arrays of cluster attribute object refs (not raw numeric IDs). | ||
| - `subscribed_events` follows the same pattern keyed by capability ID. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| name: "Econet GateLock Matter" | ||
| packageKey: "econet-gatelock-matter" | ||
| description: "SmartThings Edge driver for the Econet Bulldog GateLock (Matter). Exposes lock control, door open/close contact sensor, tamper alert, and battery." | ||
| permissions: | ||
| matter: {} | ||
| lifecycle: {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| matterManufacturer: | ||
| - id: "econet-gatelock" | ||
| deviceLabel: Bulldog GateLock | ||
| deviceProfileName: matter-lock-contact-tamper | ||
| vendorId: 0x1568 # Econet Controls Inc (5480) | ||
| productId: 0x000A # 10 (Bulldog GateLock) | ||
| deviceTypes: | ||
| - id: 0x000A # MA-doorlock | ||
18 changes: 18 additions & 0 deletions
18
drivers/EconetControlsInc/bulldog-gatelock/profiles/matter-lock-contact-tamper.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| name: matter-lock-contact-tamper | ||
| components: | ||
| - id: main | ||
| capabilities: | ||
| - id: lock | ||
| version: 1 | ||
| - id: contactSensor | ||
| version: 1 | ||
| - id: tamperAlert | ||
| version: 1 | ||
| - id: battery | ||
| version: 1 | ||
| - id: firmwareUpdate | ||
| version: 1 | ||
| - id: refresh | ||
| version: 1 | ||
| categories: | ||
| - name: SmartLock |
156 changes: 156 additions & 0 deletions
156
drivers/EconetControlsInc/bulldog-gatelock/src/init.lua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| -- Econet GateLock Matter Edge Driver. | ||
| -- | ||
| -- Built on st.matter.driver (NOT the generic st.driver) — this is the | ||
| -- Matter-specific driver class that actually attaches the secure | ||
| -- matter_channel session to each device. Using the generic Driver class | ||
| -- causes "matter_channel nil" because no Matter subsystem hookup happens. | ||
|
|
||
| local MatterDriver = require "st.matter.driver" | ||
| local clusters = require "st.matter.clusters" | ||
| local capabilities = require "st.capabilities" | ||
|
|
||
| local DoorLock = clusters.DoorLock | ||
| local PowerSource = clusters.PowerSource | ||
|
|
||
| local UNLATCHED_STATE = 0x3 | ||
|
|
||
| ---------------------------------------------------------------------- | ||
| -- ATTRIBUTE HANDLERS | ||
| ---------------------------------------------------------------------- | ||
|
|
||
| local function lock_state_handler(driver, device, ib, response) | ||
| local LockState = DoorLock.attributes.LockState | ||
| local attr = capabilities.lock.lock | ||
| local map = { | ||
| [LockState.NOT_FULLY_LOCKED] = attr.not_fully_locked(), | ||
| [LockState.LOCKED] = attr.locked(), | ||
| [LockState.UNLOCKED] = attr.unlocked(), | ||
| [UNLATCHED_STATE] = attr.unlocked(), | ||
| } | ||
| if ib.data.value ~= nil and map[ib.data.value] then | ||
| device:emit_event(map[ib.data.value]) | ||
| else | ||
| device:emit_event(attr.not_fully_locked()) | ||
| end | ||
| end | ||
|
|
||
| local function door_state_handler(driver, device, ib, response) | ||
| local val = ib.data.value | ||
| if val == nil then return end | ||
| if val == 1 then | ||
| device:emit_event(capabilities.contactSensor.contact.closed()) | ||
| else | ||
| device:emit_event(capabilities.contactSensor.contact.open()) | ||
| end | ||
| end | ||
|
|
||
| local function battery_percent_handler(driver, device, ib, response) | ||
| if ib.data.value ~= nil then | ||
| device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) | ||
| end | ||
| end | ||
|
|
||
| ---------------------------------------------------------------------- | ||
| -- EVENT HANDLERS | ||
| ---------------------------------------------------------------------- | ||
|
|
||
| -- Firmware locks out the keypad for ~10 s on the 4-strike brute-force trip. | ||
| -- Auto-clear after 15 s (10 s lockout + 5 s buffer) so SmartThings tracks | ||
| -- a real detected->clear transition without waiting for a driver restart. | ||
| local TAMPER_CLEAR_DELAY_S = 15 | ||
|
|
||
| local function door_lock_alarm_handler(driver, device, ib, response) | ||
| device:emit_event(capabilities.tamperAlert.tamper.detected()) | ||
| device.thread:call_with_delay(TAMPER_CLEAR_DELAY_S, function() | ||
| device:emit_event(capabilities.tamperAlert.tamper.clear()) | ||
| end) | ||
| end | ||
|
|
||
| ---------------------------------------------------------------------- | ||
| -- COMMAND HANDLERS | ||
| ---------------------------------------------------------------------- | ||
|
|
||
| local function handle_lock(driver, device, command) | ||
| local ep = device:component_to_endpoint(command.component) | ||
| device:send(DoorLock.server.commands.LockDoor(device, ep)) | ||
| end | ||
|
|
||
| local function handle_unlock(driver, device, command) | ||
| local ep = device:component_to_endpoint(command.component) | ||
| device:send(DoorLock.server.commands.UnlockDoor(device, ep)) | ||
| end | ||
|
|
||
| local function handle_refresh(driver, device, command) | ||
| device:refresh() | ||
| end | ||
|
|
||
| ---------------------------------------------------------------------- | ||
| -- LIFECYCLE | ||
| ---------------------------------------------------------------------- | ||
|
|
||
| local function device_init(driver, device) | ||
| device:subscribe() | ||
| end | ||
|
|
||
| local function device_added(driver, device) | ||
| device:emit_event(capabilities.tamperAlert.tamper.clear()) | ||
| end | ||
|
|
||
| ---------------------------------------------------------------------- | ||
| -- DRIVER TABLE (passed as 2nd arg to MatterDriver) | ||
| ---------------------------------------------------------------------- | ||
|
|
||
| local matter_lock_driver = { | ||
| lifecycle_handlers = { | ||
| init = device_init, | ||
| added = device_added, | ||
| }, | ||
|
|
||
| matter_handlers = { | ||
| attr = { | ||
| [DoorLock.ID] = { | ||
| [DoorLock.attributes.LockState.ID] = lock_state_handler, | ||
| [DoorLock.attributes.DoorState.ID] = door_state_handler, | ||
| }, | ||
| [PowerSource.ID] = { | ||
| [PowerSource.attributes.BatPercentRemaining.ID] = battery_percent_handler, | ||
| }, | ||
| }, | ||
| event = { | ||
| [DoorLock.ID] = { | ||
| [DoorLock.events.DoorLockAlarm.ID] = door_lock_alarm_handler, | ||
| }, | ||
| }, | ||
| }, | ||
|
|
||
| subscribed_attributes = { | ||
| [capabilities.lock.ID] = { | ||
| DoorLock.attributes.LockState, | ||
| }, | ||
| [capabilities.contactSensor.ID] = { | ||
| DoorLock.attributes.DoorState, | ||
| }, | ||
| [capabilities.battery.ID] = { | ||
| PowerSource.attributes.BatPercentRemaining, | ||
| }, | ||
| }, | ||
|
|
||
| subscribed_events = { | ||
| [capabilities.tamperAlert.ID] = { | ||
| DoorLock.events.DoorLockAlarm, | ||
| }, | ||
| }, | ||
|
|
||
| capability_handlers = { | ||
| [capabilities.lock.ID] = { | ||
| [capabilities.lock.commands.lock.NAME] = handle_lock, | ||
| [capabilities.lock.commands.unlock.NAME] = handle_unlock, | ||
| }, | ||
| [capabilities.refresh.ID] = { | ||
| [capabilities.refresh.commands.refresh.NAME] = handle_refresh, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| local matter_driver = MatterDriver("econet-gatelock-matter", matter_lock_driver) | ||
| matter_driver:run() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
deviceTypesare not needed here since we are matching directly on thevendorIdandproductId, so they can be removed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ctowns! One quick clarifying question before I refactor:
I noticed your example sub-driver includes
[capabilities.contactSensor.ID] = { DoorLock.attributes.DoorState }in
subscribed_attributes. The Bulldog GateLock exposes the standardMatter
DoorLock.DoorStateattribute from a physical reed switch onthe gate frame, and I'd like to keep surfacing it as
contactSensorin the SmartThings app so users can see whether the gate is actually
open or closed (separate from the locked/unlocked).
To make sure I refactor correctly, can you confirm:
DoorState->contactSensor.contactautomatically, or should thesub-driver include a small handler for that mapping?
matter-lock profiles include
contactSensor. Should I add a newprofile (e.g.
lock-user-pin-contact-tamper-battery) undermatter-lock/profiles/, or is there a preferred way to ship a
contactSensor-capable lock profile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question! I hadn't realized the
contactSensorhandling was unique to this device and I had accidentally removed it from the example above. To answer your question, we are currently working on implementing a capability specifically for handlingDoorState, and that implementation would be handled in the parent driver and would not require additional implementation in the sub driver. You can see the proposed capability here! https://developer.smartthings.com/docs/devices/capabilities/proposed#doorStateLet me get back to you on the timeline, but I believe
doorStatemay be a better fit for your use case than thecontactSensorcapability. Let me know if you have any questions!In using the new
doorStatecapability, we would add support for that capability to the variouslock-modularprofiles, which would allow for all devices (including this one) to support that capability dynamically as needed. This support would be handled by the parent driver, so you wouldn't need to implement any changes to create a new profile for use in the sub driver once the parent driver changes have landed.So, once the
doorStatecapability support as been added to our parent driver, I think the scope of this sub driver would be limited to thedoor_lock_alarm_handlerfunctionality above that has special handling just for this device. I believe thedoorStatehandling would be generic enough to use a generic handler in the parent driver once that is available (I will follow up on this).Let me know if you have any questions!