Schlage Encode Wifi

That would be SUPER easy with my MQTT version, so it should be easy to do on Mcnutter’s version as well… I’m on vacation for Thanksgiving but I’ll see if I can get some pull requests in to add that to both versions.

1 Like

I believe someone forked my original integration and has made some significant improvements, adding the jammed feature should be easy, but likely as another entity type.

Wondering if I should update to the forked integration. It seems to work pretty well so I was thinking of leaving it alone but I seem to be throwing these errors in the log.

Logger: homeassistant.helpers.integration_platform
Source: loader.py:745
First occurred: 2 December 2022 at 16:17:39 (9 occurrences)
Last logged: 10:30:57

Unexpected error importing schlage/diagnostics.py
Unexpected error importing schlage/recorder.py
Unexpected error importing schlage/cast.py
Unexpected error importing schlage/significant_change.py
Unexpected error importing schlage/hardware.py
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/integration_platform.py", line 40, in _async_process_single_integration_platform_component
    platform = integration.get_platform(platform_name)
  File "/usr/src/homeassistant/homeassistant/loader.py", line 728, in get_platform
    cache[full_name] = self._import_platform(platform_name)
  File "/usr/src/homeassistant/homeassistant/loader.py", line 745, in _import_platform
    return importlib.import_module(f"{self.pkg_path}.{platform_name}")
  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'custom_components.homeasssitant-schlage-main.group'

Are you continuing to work on this? Not really a GitHub pro and wondering if I should update it?

Thanks

For what its worth I’ve been getting those same errors regularly starting from the first integration version and even on the current version but everything seems to be working

Logger: homeassistant.helpers.integration_platform
Source: loader.py:745
First occurred: 1:49:01 PM (9 occurrences)
Last logged: 2:02:39 PM

Unexpected error importing schlage/diagnostics.py
Unexpected error importing schlage/recorder.py
Unexpected error importing schlage/significant_change.py
Unexpected error importing schlage/cast.py
Unexpected error importing schlage/hardware.py
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/integration_platform.py", line 40, in _async_process_single_integration_platform_component
    platform = integration.get_platform(platform_name)
  File "/usr/src/homeassistant/homeassistant/loader.py", line 728, in get_platform
    cache[full_name] = self._import_platform(platform_name)
  File "/usr/src/homeassistant/homeassistant/loader.py", line 745, in _import_platform
    return importlib.import_module(f"{self.pkg_path}.{platform_name}")
  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'custom_components.Schlage.group'

I am seeing this as well. Has anyone figured out what allows the mobile app to work consistently w/ lock and unlock (albeit with a bad delay for a mobile app (seconds) at times) where as the HA integration seems to work for a bit after power cycling the lock and then not at all (or with extreme delay (minutes)) later when trying to lock and unlock?

Something else interesting that I observed is that it looks like the call to lock and unlock triggered a 403 from the API if the HA integration is configured with w/ a user that has a Virtual Key that isn’t an “Admin” in the mobile app but it seems that it can still read the lock state OK. Is there perhaps a different API call that the app is using for non-administrative users or administrative users under certain circumstances?

I am pretty sure the official app uses websockets to subscribe to changes to almost immediate changes to the lock state. The tool I used to discover the API (mitmproxy) doesn’t trivially support sniffing websockets, so I don’t know what those look like. These integrations use cloud polling, which will always have a delay. Comment #59 has my investigation details, if someone can use it to reverse engineer the websocket API.

I’ve been trying to figure that out myself. After a couple weeks my lock can take as much as 10 minutes between when the lock command has been sent and when the lock finally locks, which obviously isn’t ideal. A power cycle of the lock seems to fix that, making me think its the lock itself having the issue and nothing to do with the API or websockets though maybe… I’m tempted to pull out my hardware debugging tools and see if I can dump the firmware off the lock, though I’m expecting its probably encrypted. Its not the end of the world having to power cycle the lock occasionally, but it does take some of the ‘auto’ outa my automation.

This work has been amazing! I’m so happy that my expensive locks can be used in HA finally.

Looks like it lost some steam - what would it take to keep this going to a more robust websocket solution with more resilient connectivity and availability sensing + all of the statuses?

I’m happy to support a bounty / “buy coffee” / donate to push this forward? Not sure how this stuff works, but would love to support!

I looked into the websockets bit and it looks like it only uses websockets to monitor state. So it would receive state updates faster with websockets instead of polling for status like it does now. The API call for changing the state of the lock is the same as the app uses. HOWEVER I’ve noted that the app seems to spam the hell outa the API at times, sending the ‘lock’ command 5-6 times in a row. I wonder if thats to combat the lock’s tendancy to ignore the command for a long time. I’ll do some testing to see if I can sus that out…

That makes sense based on the experience… The command spamming is probably to combat bad connectivity… Sometimes even via app, it takes a few seconds, but status shows fairly quickly and reliably.
With this integration, status updates take 30-60sec and the command takes anywhere from 3-10sec for me (but has never failed yet)…
I use a status light to show door unlocked, so it’s easy to see the delay when locking and it takes 30sec for the light to go off

I’ve spent a bunch of time poking at a decompiled Android APK and I have a few answers. :upside_down_face:

The websockets are MQTT connections to Amazon’s IoT service. If you call https://api.allegion.yonomi.cloud/v1/wss with a Cognito identity token in the X-Web-Identity-Token header, you get back a JSON blob with a subscriber id, topics, and a websocket URI that contains authorization tokens as query parameters that you can pass to a MQTT client (note that you have to modify the MQTT client Host header to drop the :433 suffix (e.g. the port) and make sure it’s set up for websockets and has SSL enabled)

There’s a few problems, though:

  1. Only a single client id is generated per account. So if you want to use the Schlage mobile app at the same time, it will punt your other connection. Reconnects are spotty and don’t seem reliable.
  2. Worse, you can only get a subscription for a single device at a time. If you try to subscribe to multiple devices, it seems to just silently not return anything. (The underlying websocket may be returning an error–I haven’t checked that)

So unfortunately this isn’t very helpful for a Home Assistant integration.

In brighter news, I’ve taken some of the breadcrumbs you folks have left in this thread and combined them with my own sleuthing in the decompiled APK and I’ve produced a Python library for talking to the API: GitHub - dknowles2/pyschlage: Python 3 library for interacting with Schlage Encode WiFi locks. (also on PyPi).

I’m hoping to find some time over the holidays to integrate my library into the integration @mcnutter1 started (by the way, you misspelled “assistant” in the repo name :stuck_out_tongue_closed_eyes:), but if someone else wants to take a crack at it, be my guest.

Cheers!

3 Likes

Hey, very nice sluthing!! I’ll have to take a look at your library and see about standardizing the MQTT version on it too. It’d be convenient if they both shared the same library.

1 Like

I’ve finally got mitm-proxy installed to test this out on my Sense/BR400 bridge. The unlock/lock commands seem to be a POST instead of a PUT to https://api.allegion.yonomi.cloud/v1/devices/{deviceID}/commands
and the JSON data has a lot more.

{
   "data": {
      "CAT": "{CAT attribute from GET command}",
      "deviceId": "{deviceID}",
      "state": 1,
      "userId": "{userid}"
      },
   "name": "changelockstate"
}

Not sure how hard this is to add, or maybe it will work with the Encode locks too.
When I hit replay in the mitm-proxy console, the lock instantly responds.
I also have it working as a set of curl commands, so I’ll test it in Home Assistant to see if a delay starts to happen.

1 Like

You can create virtual keys and then register them in separate user accounts with Schlage. I did this because I wanted the history log for my lock to differentiate between me using the mobile app and HA using the API to change the lock state. It may also help keep HA and your mobile device from interfering with each other if there is any sort of per user identity rate limiting on the API.

This suggestion doesn’t solve for multiple locks on one account (I’m guessing the mobile app has some way of dealing with this) but that does seem like it would allow WS push for state changes to HA without fighting against your mobile app.

Yes, bridges use a different endpoint. I’m surprised it’s sending a PUT, though–that’s not in the Retrofit spec from the APK. I guess the server is lenient about what HTTP verb is used.

I think the /commands endpoint works for standalone locks too. I’ll test it out on mine and if it works I’ll make pyschlage use that instead.

For reference, here’s the majority of API that the Android app uses (I translated this by hand, so may have missed some things): Details of the Schlage WiFi API · GitHub

Do virtual keys generate new login credentials? If not, I don’t think this helps.

My hypothesis for how this works:

  1. There is only one MQTT clientId generated per account.
  2. When you call /wss, the server makes a call back to the Amazon IoT service and reconfigures it to only publish the requested device to your clientId. (The clientId never seems to change no matter how many times you call /wss)

This sort of makes sense given how the mobile app works. Since it only shows you one lock at a time, it will actually make a call back to /wss every time you change what lock you’re viewing. I suspect that maybe when you’re not actively watching a lock via MQTT the physical device goes back into a sleep mode to preserve battery, but if you have a MQTT subscriber it will stay active.

Note that “notifications” use a different transport: it seems to use Firebase to send notifications across platforms (e.g. Android/iOS). I haven’t had time to poke at this yet, so I don’t know if the notifications have enough metadata attached to be useful for push-based Home Assistant integration.

1 Like

The changelockstate command doesn’t seem to work with my Encode lock. It returns a 504/Gateway Timeout after 30 seconds.

@rwoelk would you mind sending me a dump of the output from /devices?devicetypeId=br400 and /devices?archetype=lock? (feel free to redact any sensitive info). I’d like to understand how to differentiate between Encode locks and those attached to a bridge.

Thank you all for the hard work.
So why is it a small company like wink had sclage all wrapped up in a great bow. I bought wink as my first hub and sclage was great push notifications for every code coustom automations lights for who ever entered depending on time of day loved everything.
Why can’t home assistant get things like this working 100% if a new lock company shows up with some crap no name off the wall can only buy in 1 store in the US in person made from 3d printed plastic. Home assistant has the integration hacs works and every feature under the son works including the read out of the surface temp of the son. Sorry ranting. Why can’t home assistant flex a little just by the growing use of home assistant people want no cloud based systems. Thanks again for your work on this lock hope to get it running on mine.

David,
here is the output from /devices?devicetypeId=br400
I triggered this by going to the WiFi adapters section of my app.

[
    {
        "attributes": {
            "bleFirmwareVersion": "1.4.0",
            "hardwareVersion": "1.0.0",
            "lastActionStatus": "null",
            "lastTalkedTime": [
                {
                    "deviceId": "<redacted>",
                    "timestamp": "2022-12-23T16:22:38Z"
                }
            ],
            "mainFirmwareVersion": "1.1.12",
            "manufacturerName": "Schlage",
            "modelName": "BR400",
            "serialNumber": "1089293",
            "wifiFirmwareVersion": "SD878x-14.76.36.p121-702.1.0-WM"
        },
        "connected": true,
        "connectivityUpdated": "2022-12-23T16:20:13.000Z",
        "created": "2022-08-13T03:21:47.000Z",
        "deviceId": "b6<redacted>",
        "devicetypeId": "br400",
        "lastUpdated": "2022-12-23T16:20:13.000Z",
        "modelName": "BR400",
        "name": "My Home",
        "physicalId": "1089293",
        "relatedDevices": [
            {
                "deviceId": "b6<redacted>"
            }
        ],
        "role": "owner",
        "serialNumber": "<redacted>",
        "users": [
            {
                "email": "r<redacted>",
                "friendlyName": "R<redacted>",
                "identityId": "6e2<redacted>",
                "role": "owner"
            }
        ]
    }
]

and from /devices?archetype=lock


[
    {
        "CAT": "5820834319200146081a27d2abc1420c0050cd939c01db1293d1b6ed2234a3fb5905",                
        "SAT": "582182431930014a0048ba67a2f65c2b742750f54e5626c546dd745091b25e44249f3c",
        "attributes": {
            "CAT": "5820834319200146081a27d2abc1420c0050cd939c01db1293d1b6ed2234a3fb5905",
            "SAT": "582182431930014a0048ba67a2f65c2b742750f54e5626c546dd745091b25e44249f3c",
            "accessCodeLength": 4,
            "adminOnlyEnabled": 0,
            "alarmSelection": 0,
            "alarmSensitivity": 0,
            "alarmState": 0,
            "autoLockTime": 240,
            "batteryLevel": 66,
            "batteryLowState": 0,
            "beeperEnabled": 1,
            "hardwareVersion": "1.3.0",
            "lastTalkedTime": "2022-12-20T22:46:11Z",
            "lockAndLeaveEnabled": 1,
            "lockState": 1,
            "macAddress": "EA:10:CA:87:19:F6",
            "mainFirmwareVersion": "004.031.000",
            "manufacturerName": "Schlage ",
            "modelName": "BE479CEN619",
            "name": "Front Door",
            "profileVersion": "1.1",
            "serialNumber": "<redacted>6",
            "timezone": -60
        },
        "connected": false,
        "connectivityUpdated": "2022-12-20T23:02:35.000Z",
        "created": "2021-03-03T20:19:18.000Z",
        "deviceId": "<redacted>",
        "devicetypeId": "be479",
        "lastUpdated": "2022-12-20T23:02:35.000Z",
        "macAddress": "EA:10:CA:87:19:F6",
        "modelName": "BE479CEN619",
        "name": "Front Door",
        "physicalId": "ea:10:ca:87:19:f6",
        "relatedDevices": [
            {
                "deviceId": "<redacted>"
            }
        ],
        "role": "owner",
        "serialNumber": "0<redacted>6",
        "timezone": -60,
        "users": [
            {
                "email": "<redacted>",
                "friendlyName": "<redacted>",
                "identityId": "<redacted>",
                "role": "admin"
            },
            {
                "email": "<redacted>",
                "friendlyName": "Home Assistant",
                "identityId": "<redacted>",
                "role": "guest"
            },
            {
                "email": "m<redacted>",
                "friendlyName": "M<redacted>",
                "identityId": "<redacted>",
                "role": "guest"
            },
            {
                "email": "r<redacted>",
                "friendlyName": "R<redacted>",
                "identityId": "6e2<redacted>",
                "role": "owner"
            }
        ]
    }
]

I’ve redacted Identity IDs and device IDs just in case, and also names and email addresses.
I would hope there is filtering between accounts that would block someone from adding my device ID with their credentials to open my lock.
I did notice that I could transplant the identityID of another user from my account and it would operate the lock, and show that name in the history. That might be because I was logged in as the owner when I captured the Authorization Bearer. I’ll have to test with the guest account.

1 Like