Skip to main content

Schlage Encode / Sense+

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:

  1. 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.
  2. 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

  1. Mint tokens. Use one of the bootstrap recipes above to produce an IdToken (and ideally a RefreshToken) for your Schlage Home account.
  2. 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.
  3. 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.
  4. 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.
  5. Verify. Use the Script Console to send lock and unlock and confirm the lock responds within a few seconds.

Attributes

Device — required

NameTypeDescription
access_tokenstring (secure)Cognito IdToken minted for your Schlage Home account.

Device — optional

NameTypeDefaultDescription
refresh_tokenstring (secure)Cognito RefreshToken. When set, the driver auto-refreshes the access token before each session and on 401 responses. Strongly recommended.
cognito_client_idstring26ujbcfppen6t3afts2ad7vfgsAWS Cognito app client id. Community-derived constant.
cognito_regionstringus-west-2AWS region for the Cognito user pool.
api_basestringhttps://api.allegion.yonomi.cloudAllegion partner cloud endpoint.
api_keystring (secure)well-known constantx-api-key header value. Defaults to the value shipped in community projects and the Schlage Home iOS app.
status_intervalint (ms)60000How often to refresh lock state from the cloud. Minimum 15s.
request_timeoutint (ms)15000HTTP request timeout.

Zone — address

FieldDescription
addressThe Schlage cloud deviceId UUID for the lock (e.g. 11111111-2222-3333-4444-555555555555). Discover via get_devices.

Commands

NameArgsDescription
lockaddressRemotely lock the specified lock.
unlockaddressRemotely unlock the specified lock.
get_statusaddressReturn the current lock state with the full cloud detail payload.
get_devicesList all locks visible to the account.
get_device_detailaddressReturn the full cloud detail payload for a lock (battery, firmware, last action).
refresh_tokenForce a Cognito token refresh now (requires refresh_token to be set).

Zone state attributes

The driver writes these attributes onto the GEM zone:

AttributeValues
statelocked, unlocked, or unchanged if the cloud reports unknown.
battery_level0-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.