From ac106e025b0e994aa1a03e552b7f097366049f6a Mon Sep 17 00:00:00 2001 From: Econet-Controls-Inc Date: Tue, 26 May 2026 11:18:30 -0400 Subject: [PATCH 1/2] Add Econet Bulldog GateLock Matter Edge driver Adds a new Matter Edge driver for the Econet Bulldog GateLock (Vendor ID 0x1568 / Product ID 0x000A). The driver subscribes to: - DoorLock.LockState -> lock capability - DoorLock.DoorState -> contactSensor capability (reed switch via Matter) - PowerSource.BatPercentRemaining -> battery capability - GeneralDiagnostics.ActiveHardwareFaults -> tamperAlert capability (fires "tampered" when kTamperDetected is present in the list, "clear" when it is removed; firmware sets this on a 4-strike keypad brute-force trip and clears it when the lockout expires) - DoorLock.DoorLockAlarm event (legacy fallback for older firmware builds that only emit the event path) Implemented on st.matter.driver so the Matter secure session is established per device. --- drivers/Econet/bulldog-gatelock/README.md | 85 ++++++++ drivers/Econet/bulldog-gatelock/config.yml | 6 + .../Econet/bulldog-gatelock/fingerprints.yml | 7 + .../profiles/gatelock-matter.yml | 18 ++ drivers/Econet/bulldog-gatelock/src/init.lua | 184 ++++++++++++++++++ 5 files changed, 300 insertions(+) create mode 100644 drivers/Econet/bulldog-gatelock/README.md create mode 100644 drivers/Econet/bulldog-gatelock/config.yml create mode 100644 drivers/Econet/bulldog-gatelock/fingerprints.yml create mode 100644 drivers/Econet/bulldog-gatelock/profiles/gatelock-matter.yml create mode 100644 drivers/Econet/bulldog-gatelock/src/init.lua diff --git a/drivers/Econet/bulldog-gatelock/README.md b/drivers/Econet/bulldog-gatelock/README.md new file mode 100644 index 0000000000..d7c7de1c46 --- /dev/null +++ b/drivers/Econet/bulldog-gatelock/README.md @@ -0,0 +1,85 @@ +# 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: + +1. Adds `kTamperDetected (10)` to `GeneralDiagnostics.ActiveHardwareFaults` on endpoint 0 (also emits the `HardwareFaultChange` event). +2. Fires a legacy `DoorLockAlarm` event with `alarmCode = kWrongCodeEntryLimit (4)` for backwards compatibility. + +The driver subscribes to the `ActiveHardwareFaults` attribute and maps list membership directly to the **tamperAlert** capability — `tampered` while the list contains `10`, `clear` when the firmware removes it (which happens automatically when the lockout window expires). The legacy `DoorLockAlarm` event handler is retained so older firmware builds that only fire the event still surface a `tampered` 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 -C +smartthings edge:drivers:install --hub -C +``` + +## Re-deploy after edits + +After every code change: + +```bash +# Package + auto-assign + install +smartthings edge:drivers:package -C --hub + +# If the hub doesn't pick up the new version (cached), force re-install: +smartthings edge:drivers:install --hub -C +``` + +## Live logs + +```bash +smartthings edge:drivers:logcat +``` + +## 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. diff --git a/drivers/Econet/bulldog-gatelock/config.yml b/drivers/Econet/bulldog-gatelock/config.yml new file mode 100644 index 0000000000..d740be2bd9 --- /dev/null +++ b/drivers/Econet/bulldog-gatelock/config.yml @@ -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: {} diff --git a/drivers/Econet/bulldog-gatelock/fingerprints.yml b/drivers/Econet/bulldog-gatelock/fingerprints.yml new file mode 100644 index 0000000000..10309ad610 --- /dev/null +++ b/drivers/Econet/bulldog-gatelock/fingerprints.yml @@ -0,0 +1,7 @@ +matterManufacturer: + - id: "econet-gatelock" + deviceProfileName: gatelock-matter + vendorId: 0x1568 # Econet Controls Inc (5480) + productId: 0x000A # 10 (Bulldog GateLock) + deviceTypes: + - id: 0x000A # MA-doorlock diff --git a/drivers/Econet/bulldog-gatelock/profiles/gatelock-matter.yml b/drivers/Econet/bulldog-gatelock/profiles/gatelock-matter.yml new file mode 100644 index 0000000000..e9f038b73e --- /dev/null +++ b/drivers/Econet/bulldog-gatelock/profiles/gatelock-matter.yml @@ -0,0 +1,18 @@ +name: gatelock-matter +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 diff --git a/drivers/Econet/bulldog-gatelock/src/init.lua b/drivers/Econet/bulldog-gatelock/src/init.lua new file mode 100644 index 0000000000..e833bdf0af --- /dev/null +++ b/drivers/Econet/bulldog-gatelock/src/init.lua @@ -0,0 +1,184 @@ +-- 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 GeneralDiagnostics = clusters.GeneralDiagnostics + +local UNLATCHED_STATE = 0x3 +local HARDWARE_FAULT_TAMPER_DETECTED = 10 + +---------------------------------------------------------------------- +-- 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 + +-- GeneralDiagnostics.ActiveHardwareFaults is a list of HardwareFaultEnum values. +-- Firmware adds kTamperDetected (10) when the keypad 4-strike brute-force +-- limit trips and removes it when the lockout expires. Map list membership +-- directly to the tamperAlert capability so the SmartThings UI tracks the +-- attribute's full lifecycle (detected -> clear) instead of just the alarm +-- event edge. +local function hardware_faults_handler(driver, device, ib, response) + local list = ib.data and ib.data.elements + local tampered = false + if list ~= nil then + for _, entry in ipairs(list) do + if entry.value == HARDWARE_FAULT_TAMPER_DETECTED then + tampered = true + break + end + end + end + if tampered then + device:emit_event(capabilities.tamperAlert.tamper.detected()) + else + device:emit_event(capabilities.tamperAlert.tamper.clear()) + end +end + +---------------------------------------------------------------------- +-- EVENT HANDLERS +---------------------------------------------------------------------- + +-- Retained for compatibility with firmware that only fires the +-- DoorLockAlarm event (older builds without GeneralDiagnostics tamper +-- reporting). On builds that report both, the attribute handler above +-- supersedes this by also clearing the state. +local function door_lock_alarm_handler(driver, device, ib, response) + device:emit_event(capabilities.tamperAlert.tamper.detected()) +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, + }, + [GeneralDiagnostics.ID] = { + [GeneralDiagnostics.attributes.ActiveHardwareFaults.ID] = hardware_faults_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, + }, + [capabilities.tamperAlert.ID] = { + GeneralDiagnostics.attributes.ActiveHardwareFaults, + }, + }, + + 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() From 08567e1b2175afc941497096eb659f6a8f342fed Mon Sep 17 00:00:00 2001 From: Econet-Controls-Inc Date: Tue, 26 May 2026 11:25:40 -0400 Subject: [PATCH 2/2] Rename driver folder to drivers/EconetControlsInc/ --- drivers/{Econet => EconetControlsInc}/bulldog-gatelock/README.md | 0 drivers/{Econet => EconetControlsInc}/bulldog-gatelock/config.yml | 0 .../bulldog-gatelock/fingerprints.yml | 0 .../bulldog-gatelock/profiles/gatelock-matter.yml | 0 .../{Econet => EconetControlsInc}/bulldog-gatelock/src/init.lua | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename drivers/{Econet => EconetControlsInc}/bulldog-gatelock/README.md (100%) rename drivers/{Econet => EconetControlsInc}/bulldog-gatelock/config.yml (100%) rename drivers/{Econet => EconetControlsInc}/bulldog-gatelock/fingerprints.yml (100%) rename drivers/{Econet => EconetControlsInc}/bulldog-gatelock/profiles/gatelock-matter.yml (100%) rename drivers/{Econet => EconetControlsInc}/bulldog-gatelock/src/init.lua (100%) diff --git a/drivers/Econet/bulldog-gatelock/README.md b/drivers/EconetControlsInc/bulldog-gatelock/README.md similarity index 100% rename from drivers/Econet/bulldog-gatelock/README.md rename to drivers/EconetControlsInc/bulldog-gatelock/README.md diff --git a/drivers/Econet/bulldog-gatelock/config.yml b/drivers/EconetControlsInc/bulldog-gatelock/config.yml similarity index 100% rename from drivers/Econet/bulldog-gatelock/config.yml rename to drivers/EconetControlsInc/bulldog-gatelock/config.yml diff --git a/drivers/Econet/bulldog-gatelock/fingerprints.yml b/drivers/EconetControlsInc/bulldog-gatelock/fingerprints.yml similarity index 100% rename from drivers/Econet/bulldog-gatelock/fingerprints.yml rename to drivers/EconetControlsInc/bulldog-gatelock/fingerprints.yml diff --git a/drivers/Econet/bulldog-gatelock/profiles/gatelock-matter.yml b/drivers/EconetControlsInc/bulldog-gatelock/profiles/gatelock-matter.yml similarity index 100% rename from drivers/Econet/bulldog-gatelock/profiles/gatelock-matter.yml rename to drivers/EconetControlsInc/bulldog-gatelock/profiles/gatelock-matter.yml diff --git a/drivers/Econet/bulldog-gatelock/src/init.lua b/drivers/EconetControlsInc/bulldog-gatelock/src/init.lua similarity index 100% rename from drivers/Econet/bulldog-gatelock/src/init.lua rename to drivers/EconetControlsInc/bulldog-gatelock/src/init.lua