Skip to content

WebSockets API

sillyfrog edited this page Jun 22, 2021 · 3 revisions

This portion of the API was discovered using traffic inspection techniques between the app an the hub.

This approach was taken as the Serial Protocol only allows a single TCP connection at once. An incoming connection disconnects an existing connection, where as the WebSockets API allows several connections at once.

Please note that blind, roller and shade are used interchangeably - they mean the same thing in this project.

Connectivity

The connection by the App to the hub is via a secure web sockets connection (wss), on the standard port 443.

Protocol

The protocol is JSON based, and is pure polling (ie: no events appear to be fired by the hub, it's all in response to queries from the app).

Payloads

All examples below have been run through js-beautify. The app implementation uses a single line / minimised JSON.

Some value have been obfuscated, and replaced with xxx as appropriate.

Status update

This is fired every 3 seconds or so by the app. It's a request for the current status of all of the rollers:

{
    "method": "shadow",
    "src": "app",
    "id": 1598162592
}
Field Description
method Value is always shadow
src The source of the query, in this implementation, it always uses app
id Some sort of unique ID. The app appears to use the int of time.time() (UNIX Epoch), so this is replicated in this library in the heartbeat method.

The response is as follows. This contains the data that's used by this library.

{
    "id": 1598162592,
    "src": "esp32_XXXXXX",
    "dst": "app",
    "result": {
        "reported": {
            "hubId": "100XXXX",
            "name": "default",
            "mac": "c4:4f:33:XX:XX:XX",
            "onlineLatest": 1598162603.550303,
            "mfi": {
                "PD": "XXXXXXXXXXXXXXXX",
                "manufacturer": "Rollease Acmeda",
                "model": "MT02-0401-067001"
            },
            "firmware": {
                "version": "1.0.0",
                "RFversion": "B10"
            },
            "shades": {
                "VEB": {
                    "is": true,
                    "ol": true,
                    "mp": 100,
                    "vo": "11.2D24",
                    "ls": 1,
                    "rs": -76
                },
                "S1S": {
                    "is": true,
                    "ol": true,
                    "mp": 0,
                    "ta": 0,
                    "vo": "11.3D24",
                    "ls": 1,
                    "rs": -77
                },
                "D55": {
                    "is": true,
                    "ol": true,
                    "mp": 30,
                    "vo": "11.4D24",
                    "ls": 1,
                    "rs": -70
                },
                "PF8": {
                    "is": true,
                    "ol": true,
                    "mp": 100,
                    "vo": "12.4D24",
                    "ls": 1,
                    "rs": -83
                },
                "0R5": {
                    "is": true,
                    "ol": true,
                    "mp": 0,
                    "ta": 0,
                    "vo": "11.3D24",
                    "ls": 1,
                    "rs": -101
                },
                "YQX": {
                    "is": true,
                    "ol": true,
                    "mp": 100,
                    "vo": "11.3D24",
                    "ls": 1,
                    "rs": -69
                },
                "35W": {
                    "is": true,
                    "ol": true,
                    "mp": 0,
                    "ta": 0,
                    "vo": "11.4D24",
                    "ls": 1,
                    "rs": -73
                },
                "Q46": {
                    "is": true,
                    "ol": true,
                    "mp": 100,
                    "vo": "11.2D24",
                    "ls": 1,
                    "rs": -72
                }
            }
        }
    }
}
Field Description
id The same id as used in the query. This library ignores this value
src Appears to be the type of processor (ESP32) and the last 3 bytes of the MAC address
dst Appears to be the the same as the src in the query
result The response to the query, data analysed so far always gives this a single key value of reported
reported The actual data/payload
hubId The serial number as written on the bottom of the hub
name The name of the hub as configured in the App
mac The MAC address of the hub, I have not tested if the wired Ethernet has the same MAC as the WiFi, nor what is reported if they are different
onlineLatest Assumed to be last time the hub was able to connect to the cloud servers (not verified), in UNIX Epoch
mfi > pd Unsure, have not investigated
mfi > manufacturer The manufacturer of the hub
mfi > model The model of the hub
firmware > version The currently running firmware version
firmware > RFversion Assumed to be the firmware of the current RF subsystem, this may run a different processor for the 433MHz communications
shades An object with all of the actual shades/rollers, the key is the ID of the shade ("Device Address" in the app), and the value is the current status, see the following table for more information

shades

The shades key in the response output has the data that's of most value to the app. The key in the shades object is the ID of the shade, and the fields for the value of the object are as follows:

| Field | Description | | is | boolean - is the blind stationary, when moving this is false | | ol | boolean - is the blind online and communicating with the hub. Note this only appears to update ever 15-30 minutes | | vo | string - the battery voltage, type and version of the devices. The voltage is a float before the first letter, the letter is the type (see the Serial Protocol for details) and the final 2 digits are the version. Note this field is not always present, especially when the hub has just been rebooted. See the Query request below for further details | | ls | int - if the limits have been set for the shade, this is unverified. The only value I have seen here is 1 - and all my shades had their limits set | | rs | int - signal strength of the 433MHZ connection to the hub |

You will note the name of the roller is never sent via this websocket connection. The app appears to get it by making a query to the cloud services. This library works around this by making a short lived connection to the Serial Protocol TCP port.

Query

This is fired when further details are required from the roller. This library only uses this when the vo field is missing on a response. Only a limited number of these requests can be made per second. If too many requests are made too quickly, they will be ignored. To emulate the app, a query with more than one shades value is not used.

{
    "method": "shadow",
    "args": {
        "desired": {
            "shades": {
                "S1S": {
                    "query": true
                }
            }
        },
        "timeStamp": 1598162567.133
    }
}

Most fields are as above, with the exception of the following:

Field Description
args > desired > shades The key is the ID of the desired shade to get further details from ("Device Address" in the app)
args > desired > shades > <ID> > query Set to true to request further details
args > timeStamp The current time as a float in UNIX Epoch

This request does not receive a reply, rather another query request is used to see the current status.

Move Shade

This command actually moves the shade to the desired position.

{
    "method": "shadow",
    "args": {
        "desired": {
            "shades": {
                "S1S": {
                    "movePercent": 0
                }
            }
        },
        "timeStamp": 1598162580.458
    }
}

Fields of note not already outlined above are:

Field Description
args > desired > shades > <ID> > movePercent int - The position to move the blind to, between 0 and 100, where 0 is fully open, and 100 is fully closed

This command does not give any response, the current status needs to be queried using the query command.

Clone this wiki locally