GEM driver for Schlage Wi-Fi smart locks via the Allegion (Yonomi-derived) partner cloud REST API. Covers the Encode Wi-Fi family (Encode, Encode Plus, Encode Smart) and Sense+ when paired to a Wi-Fi bridge. Supports remote lock/unlock and periodic state polling; battery level is mirrored onto the zone when reported.
Prerequisites
- A Schlage Home account with the lock already enrolled in the Schlage Home iOS / Android app. The lock must be online and respond to the app before integrating with GEM.
- An access token (Cognito IdToken) minted from your Schlage Home credentials.
- Optionally, a refresh token from the same Cognito login. Without it, the access token expires after ~60 minutes and has to be re-pasted manually.
The Allegion cloud sits behind AWS Cognito with USER_SRP_AUTH; there is no public OAuth flow. The SRP login is not implemented inside the driver to keep its scope small. Two options to mint a token:
- Run a small one-shot helper that talks to Cognito directly (
pycognito, boto3.client('cognito-idp').initiate_auth, or the community project pyschlage). Use the Cognito Client ID 26ujbcfppen6t3afts2ad7vfgs in the us-west-2 user pool. The response carries IdToken and RefreshToken under AuthenticationResult.
- Capture the
Authorization: Bearer … header from a Schlage Home app request using Charles / mitmproxy. Less reliable but works in a pinch.
Paste the resulting IdToken as the access_token attribute. If you have the RefreshToken, paste it as refresh_token and the driver will renew the access token silently.
Setup steps
- Mint tokens. Use one of the bootstrap recipes above to produce an
IdToken (and ideally a RefreshToken) for your Schlage Home account.
- Add the device. In Devices, create a new device with driver
schlage_encode. Paste access_token (secure) and, if available, refresh_token (secure). Leave the other attributes on their defaults.
- Discover locks. Open the device's Script Console tab and run
await gem.command({device: <device_id>, action: 'get_devices'}). The response is an array of locks, each with a deviceId UUID and a friendly name.
- Create zones. For each lock you want to control from GEM, create a zone under a lock / security subsystem. Set
zone.address to the lock's deviceId UUID. The zone state will populate as locked or unlocked once the next poll runs.
- Verify. Use the Script Console to send
lock and unlock and confirm the lock responds within a few seconds.
Attributes
Device — required
| Name | Type | Description |
|---|
access_token | string (secure) | Cognito IdToken minted for your Schlage Home account. |
Device — optional
| Name | Type | Default | Description |
|---|
refresh_token | string (secure) | — | Cognito RefreshToken. When set, the driver auto-refreshes the access token before each session and on 401 responses. Strongly recommended. |
cognito_client_id | string | 26ujbcfppen6t3afts2ad7vfgs | AWS Cognito app client id. Community-derived constant. |
cognito_region | string | us-west-2 | AWS region for the Cognito user pool. |
api_base | string | https://api.allegion.yonomi.cloud | Allegion partner cloud endpoint. |
api_key | string (secure) | well-known constant | x-api-key header value. Defaults to the value shipped in community projects and the Schlage Home iOS app. |
status_interval | int (ms) | 60000 | How often to refresh lock state from the cloud. Minimum 15s. |
request_timeout | int (ms) | 15000 | HTTP request timeout. |
Zone — address
| Field | Description |
|---|
address | The Schlage cloud deviceId UUID for the lock (e.g. 11111111-2222-3333-4444-555555555555). Discover via get_devices. |
Commands
| Name | Args | Description |
|---|
lock | address | Remotely lock the specified lock. |
unlock | address | Remotely unlock the specified lock. |
get_status | address | Return the current lock state with the full cloud detail payload. |
get_devices | — | List all locks visible to the account. |
get_device_detail | address | Return the full cloud detail payload for a lock (battery, firmware, last action). |
refresh_token | — | Force a Cognito token refresh now (requires refresh_token to be set). |
Zone state attributes
The driver writes these attributes onto the GEM zone:
| Attribute | Values |
|---|
state | locked, unlocked, or unchanged if the cloud reports unknown. |
battery_level | 0-100 percent, when the lock reports a numeric batteryLevel. Firmware variant. |
What we don't yet support
- Cognito SRP login from inside GEM. Tokens are minted out-of-band; see the bootstrap recipe above. Implementing SRP inline would add ~150 LOC of crypto and is best done as a separate utility.
- Access codes / users. The Allegion API exposes per-lock guest codes and user management, but those endpoints are not yet wired through this driver — only lock state.
- Real-time push. Allegion does not publish a public WebSocket / push channel. State changes from the lock face show up after the next polling tick (default 60 s).
- Sense locks without Wi-Fi. Pure Bluetooth Schlage Sense locks have no cloud presence; they cannot be controlled remotely.
Troubleshooting
- 401 on every request. The access token has expired. Set
refresh_token to enable auto-refresh, or paste a freshly-minted access token. Tokens last ~60 min.
get_devices returns an empty list. The account has no locks paired, the lock is offline, or the token belongs to a different Schlage Home account.
lock / unlock returns 409 or 503. The lock has lost Wi-Fi connectivity. Check the Schlage Home app — if the lock shows "offline" there it cannot be controlled remotely either.
- State stays stale after a manual unlock at the lock. Confirm
status_interval is not set above a few minutes. The cloud only reflects local lock actions after the next successful poll. (The driver also re-polls 2.5 s after every issued lock/unlock to confirm the command landed.)
- Cognito refresh fails. Either the
refresh_token is stale (Cognito invalidates RTs after long inactivity or password change) or cognito_client_id has rotated. Re-bootstrap to mint a fresh token pair.
Honest caveats
- The api_key default mirrors a value that has circulated in community projects since the Yonomi era. Allegion has not formally published a partner program. If the cloud starts returning 403, the constant may have rotated.
- Implementations of
lockState semantics vary slightly between Encode firmware revisions; the driver normalises 0/1 (and the strings unlocked/locked) but anything else is treated as unknown rather than coerced.
- Without real hardware on the bench, the lock/unlock command body (
{attributes: {lockState: 0|1}}) is the shape the Allegion v2 endpoint accepts in the documented integrations — confirm against your install before relying on macros.