apexad / homebridge-mysmartrollershades-bridge

Homebridge MySmartBlinds Bridge plugin
https://www.npmjs.com/package/homebridge-mysmartrollershades-bridge
MIT License
5 stars 2 forks source link

Poll state and control blinds #3

Open apexad opened 3 years ago

apexad commented 3 years ago

As of this writing the plugin is able to boot up and put the roller shades into HomeKit, but they don't have accurate state and cannot be controlled. This issue will document work being done to attempt to do bot of those things.

mbmccormick commented 3 years ago

Apologies for the delay, I haven't had a chance to get on the Discord. I hope to be able to early next week. In the meantime, I managed to install the Tilt iOS app on my Mac and do a packet trace. It looks like after communicating with api.tiltsmarthome.com, the app opens a web socket on port 443 to ws-api.tiltsmarthome.com. But I haven't been able to inspect any of this traffic. I've been using mitmproxy to inspect the HTTP traffic from my iPhone, and tcpdump to do the trace on my Mac ... but haven't figured out how to actually look at what's happening over the web socket connection.

apexad commented 3 years ago

You got an M1 Mac? Nice! I've got a 16" Mac Book Pro so won't be upgrading.

Yes, I saw the web socket as well. I found another user but have been running into issues trying to use the app when not on the same WiFi as the bridge. Could be a location based thing.

apexad commented 3 years ago

EDIT: I have decompiled the app as well into .java files, but is is a surprisingly large application and hard to flow through.

apexad commented 3 years ago
package com.mysmartblinds.tilt.networking;

import kotlin.Metadata;

@Metadata(bv = {1, 0, 3}, d1 = {"\000\024\n\002\030\002\n\002\020\000\n\002\b\002\n\002\020\016\n\002\b\t\bÆ\002\030\0002\0020\001B\007\b\002¢\006\002\020\002R\024\020\003\032\0020\004X†D¢\006\b\n\000\032\004\b\005\020\006R\024\020\007\032\0020\004X†D¢\006\b\n\000\032\004\b\b\020\006R\024\020\t\032\0020\004X†D¢\006\b\n\000\032\004\b\n\020\006R\024\020\013\032\0020\004X†D¢\006\b\n\000\032\004\b\f\020\006¨\006\r"}, d2 = {"Lcom/mysmartblinds/tilt/networking/TiltAPIConstants;", "", "()V", "devURL", "", "getDevURL", "()Ljava/lang/String;", "devWebsocketURL", "getDevWebsocketURL", "prodURL", "getProdURL", "prodWebsocketURL", "getProdWebsocketURL", "app_release"}, k = 1, mv = {1, 1, 16})
public final class TiltAPIConstants {
  public static final TiltAPIConstants INSTANCE = new TiltAPIConstants();

  private static final String devURL = "https://api-dev.tiltsmarthome.com/v2/";

  private static final String devWebsocketURL = "wss://ws-api.tiltsmarthome.com/dev/";

  private static final String prodURL = "https://api.tiltsmarthome.com/v2/";

  private static final String prodWebsocketURL = "wss://ws-api.tiltsmarthome.com/v1/";

  static {
    devWebsocketURL = "wss://ws-api.tiltsmarthome.com/dev/";
    prodWebsocketURL = "wss://ws-api.tiltsmarthome.com/v1/";
  }

  public final String getDevURL() {
    return devURL;
  }

  public final String getDevWebsocketURL() {
    return devWebsocketURL;
  }

  public final String getProdURL() {
    return prodURL;
  }

  public final String getProdWebsocketURL() {
    return prodWebsocketURL;
  }
}
apexad commented 3 years ago

This WebSocket is definitely the key to control and state of the roller shades.

apexad commented 3 years ago

@mbmccormick Try Charles Proxy on your Mac (It's a paid app, but free trial) to see if you can see the WebSocket in there.

apexad commented 3 years ago

I've added the WebSocket connection. Since I don't actually have any roller shades connected to my account, I don't expect to see anything, but the WebSocket connection appears to be successful.

I'm curious to see if once connected those with actual hardware will get any state messages. I'm still holding off on this being published to npmjs.com, so it will require a manual install.

git clone https://github.com/apexad/homebridge-mysmartrollershades-bridge.git
cd homebridge-mysmartrollershades-bridge
npm install
npm run build
sudo npm link

Let me know if you have any questions or get some positive results.

mbmccormick commented 3 years ago

Thanks for the pointer to Charles Proxy! Definitely easier than mitmproxy, but I still couldn't sniff the websocket. Tried to grab it both from my iPhone while proxied to my Mac and from my Mac itself running the iOS app.

I did install the updated plugin, but I'm not getting any state messages in response to changes to the roller shades.

I spent a little time playing around with the wss://ws-api.tiltsmarthome.com/v1/ endpoint on WebSocket King. And here's what I found:

SEND: hello RECEIVE: { "message": "Internal server error", "connectionId": "XjSPIfKxIAMCFDA=", "requestId": "XjS3uG0gIAMFVsw=" }

SEND: {} RECEIVE: { "error": { "message": "Validation errors:\n{ \"keyword\": \"required\", \"dataPath\": \"\", \"schemaPath\": \"#/required\", \"params\": { \"missingProperty\": \".authToken\" }, \"message\": \"should have required property '.authToken'\" }", "statusCode": 422 } }

SEND: { "authToken": <redacted> } RECEIVE: { "error": { "message": "Validation errors:\n{ \"keyword\": \"required\", \"dataPath\": \"\", \"schemaPath\": \"#/required\", \"params\": { \"missingProperty\": \".commands\" }, \"message\": \"should have required property '.commands'\" }", "statusCode": 422 } }

SEND: { "authToken": <redacted>, "commands": "test" } RECEIVE: { "error": { "message": "commands.forEach is not a function", "statusCode": 500 } }

SEND: { "authToken": <redacted>, "commands": [] } RECEIVE: { "error": { "message": "Give me at least one message to forward", "statusCode": 422 } }

SEND: { "authToken": <redacted>, "commands": [ { "id": "E5:87:F1:0A:53:36", "type": "Roller Shade", "payload": "E8036400", "sync": 1605932181, "command": "13" } ] } RECEIVE: { "error": { "message": "Cannot read property 'toUpperCase' of undefined", "statusCode": 500 } }

And then I get stuck... I can't figure out what it wants for the commands parameter.

apexad commented 3 years ago

Wow, that is some great research! Nice find in WebSocket King. Looking through the code, I may have some idea of what needs to be in commands part. I'll post that here as soon as I can.

apexad commented 3 years ago

Does this help at all? https://stackoverflow.com/questions/33090087/how-to-inspect-websocket-traffic-with-charlesproxy-for-ios-simulator-devices

apexad commented 3 years ago

I added this file https://github.com/apexad/homebridge-mysmartrollershades-bridge/blob/master/research/roller_shade_spec.json#L90 (this will go to line 90 for set position/comamnd 13. I don't quite understand how that correlates to payload.

mbmccormick commented 3 years ago

Thanks. I also stumbled across that Stack Overflow post when I was trying to get it to work. For the life of me, I cannot get the web socket to show up from Tilt! I confirmed I can at least see web socket connections in Charles Proxy when I make a connection via Web Socket King from my browser on iOS. I am wondering if the device is discovering the bridge on my local network, and routing that way? I also tried isolating the bridge on my network, but I'm not sure if I was successful. (I've also disabled Bluetooth, to force the connection over the network.)

I tried sending some of the commands in various forms from the spec you uploaded, but I still get either "Give me at least one message to forward" or "Cannot read property 'toUpperCase' of undefined". I feel like this is just going to be shooting in the dark, and intercepting the web socket connection will be most efficient.

Next step is a full packet capture on my Mac while running the Tilt app locally and see if I can get a look at the messages.

apexad commented 3 years ago

I've also added a zip file of the decompiled tilt app (or as much of it as I could decompile) to research. I'm not an android app developer by any stretch, so this is a bit hard for me to navigate, but perhaps someone else who stumbles upon this post may be able to get some more details of how this all works with he source code. I'll probably take that away once we figure this out.

apexad commented 3 years ago

Tried messing with this in BlueStacks, and saw this format:

commands=[com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@5cbb65c7, com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@62851ab2, com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@f5f29a16, com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@76807813, com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@34546c39, com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@f112e0d1, com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@b87f0d7f, com.mysmartblinds.tilt.datamodel.SceneCommand$RollerShadeSceneCommand@2b67f4d0]
apexad commented 3 years ago

I was messing with the WebSocket while reviewing source code and trying to throw everything at it too, and nothing is working for me either.

apexad commented 3 years ago

So, I have noticed that for Alexa/Echo Devices to control the roller shades, the echo Device needs to be on the same WiFi network as the bridge. Because of this, I was unable to try and look at that traffic. This actually seems to be the case for the application as well. Because of this, it is un-clear to me if this is possible to control the roller shades remotely, but we cannot know for sure until we are able to get some of the WebSocket traffic.

arcticgenes commented 3 years ago

I just placed an order for 3 blinds and 2 shades as well as bridges for each. Once my order arrives and gets installed i'd be happy to be a guinea pig for this plugin. I'll reach out on discord once its all setup and I have the initial plugin installed.

apexad commented 3 years ago

I think I’ll probably adjust this plugin so changes to state persist even though it does not change the real state of the roller shade yet.

This may at least allow for some use with automations and/or things like homebridge-alexa/homebridge-gsh. It’s also a step in the right direction.

Will update here when this is done.

Otherwise I’ve been busy with life and have not had a chance to investigate the WebSocket any further. If anyone is brave enough to talk to tilt smarthome about an API document please do so (but probably best not to mention this plugin).

arcticgenes commented 3 years ago

order finally showed up yesterday. got 2 of the 3 blinds installed and they are working great in the app. this weekend I'll install the roller shades and the shade bridge and see what the plugin does

arcticgenes commented 3 years ago

rollershades and bridge installed. Planning to reach out to tilt on monday about any API.

apexad commented 3 years ago

@arcticgenes Just curious, get any meaningful feedback regarding an API from tilt? I'd love to finish up this plugin, but it's sort of at a stand still right now in less I get some new information of how the web socket works. Most important is if it is even possible to control roller shades through the web socket.

Edit: Roller Shades, not blinds

arcticgenes commented 3 years ago

Totally forgot about this. Just called them and was told they don’t have any documentation and don’t have an open api. Also asked if they had any plans to add homekit and they said no. Looks like at this point we are on our own.

If you have anything you want to test, I’m happy to be a guinea pig and share any logs.

dzeleski commented 3 years ago

Just found this. I just installed two roller shades and found out the other plugin doesnt work for the roller shades. Im a python and ruby dev, going to try and see if I can pick up where everyone left off on this. Ill most likely document it in python.

apexad commented 3 years ago

Just found this. I just installed two roller shades and found out the other plugin doesnt work for the roller shades. Im a python and ruby dev, going to try and see if I can pick up where everyone left off on this. Ill most likely document it in python.

If you can get something working to remotely control these on Python I can easily port that over to NodeJS and get this plugin working.

dzeleski commented 3 years ago

Ok ive spent a good amount of time trying to proxy the websockets and they just do not show up even when using a socks proxy which is super odd.

I decided to start digging into the decompiled java code and I think I found the commands that get sent but I have not done any testing with this data.

package com.mysmartblinds.tilt.generated;

import group.tesseract.bluetoothcommunicationprotocol.codegen.GeneratedCommand;
import kotlin.Metadata;

@Metadata(bv = {1, 0, 3}, d1 = {"\000\026\n\002\030\002\n\002\020\020\n\002\030\002\n\000\n\002\030\002\n\002\b!\b†\001\030\000 $2\b\022\004\022\0020\0000\0012\0020\002:\001$B\022\b\002\022\006\020\003\032\0020\004ø\001\000¢\006\002\020\005R\031\020\003\032\0020\004X–\004ø\001\000¢\006\n\n\002\020\b\032\004\b\006\020\007j\002\b\tj\002\b\nj\002\b\013j\002\b\fj\002\b\rj\002\b\016j\002\b\017j\002\b\020j\002\b\021j\002\b\022j\002\b\023j\002\b\024j\002\b\025j\002\b\026j\002\b\027j\002\b\030j\002\b\031j\002\b\032j\002\b\033j\002\b\034j\002\b\035j\002\b\036j\002\b\037j\002\b j\002\b!j\002\b\"j\002\b#‚\002\004\n\002\b\031¨\006%"}, d2 = {"Lcom/mysmartblinds/tilt/generated/RollerShadeCommand;", "", "Lgroup/tesseract/bluetoothcommunicationprotocol/codegen/GeneratedCommand;", "v", "Lkotlin/UByte;", "(Ljava/lang/String;IB)V", "getV", "()B", "B", "NACK", "ACK", "GET_VERSION", "GET_NAME", "SET_NAME", "GET_ERROR", "CLEAR_ERROR", "IDENTIFY", "LAUNCH_BOOTLOADER", "FACTORY_RESET", "GET_STATUS", "MOVE_MANUAL", "GET_POSITION", "SET_POSITION", "GET_BATTERY", "GET_TIME", "SET_TIME", "GET_SCHEDULE_CHECKSUM", "SET_SCHEDULE", "GET_LOCATION", "SET_LOCATION", "GET_DAYLIGHT_SAVINGS_INFO_CHECKSUM", "SET_DAYLIGHT_SAVINGS_INFO", "GET_CALIBRATION_VALUE", "SET_CALIBRATION_VALUE", "START_CALIBRATION", "GO_TO_CALIBRATION", "Companion", "app_release"}, k = 1, mv = {1, 1, 16})
public enum RollerShadeCommand implements GeneratedCommand {
  ACK, CLEAR_ERROR, FACTORY_RESET, GET_BATTERY, GET_CALIBRATION_VALUE, GET_DAYLIGHT_SAVINGS_INFO_CHECKSUM, GET_ERROR, GET_LOCATION, GET_NAME, GET_POSITION, GET_SCHEDULE_CHECKSUM, GET_STATUS, GET_TIME, GET_VERSION, GO_TO_CALIBRATION, IDENTIFY, LAUNCH_BOOTLOADER, MOVE_MANUAL, NACK, SET_CALIBRATION_VALUE, SET_DAYLIGHT_SAVINGS_INFO, SET_LOCATION, SET_NAME, SET_POSITION, SET_SCHEDULE, SET_TIME, START_CALIBRATION;

  public static final Companion Companion;

  private final byte v;

  static {
    RollerShadeCommand rollerShadeCommand1 = new RollerShadeCommand("NACK", 0, (byte)0);
    NACK = rollerShadeCommand1;
    RollerShadeCommand rollerShadeCommand2 = new RollerShadeCommand("ACK", 1, (byte)1);
    ACK = rollerShadeCommand2;
    RollerShadeCommand rollerShadeCommand3 = new RollerShadeCommand("GET_VERSION", 2, (byte)4);
    GET_VERSION = rollerShadeCommand3;
    RollerShadeCommand rollerShadeCommand4 = new RollerShadeCommand("GET_NAME", 3, (byte)5);
    GET_NAME = rollerShadeCommand4;
    RollerShadeCommand rollerShadeCommand5 = new RollerShadeCommand("SET_NAME", 4, (byte)6);
    SET_NAME = rollerShadeCommand5;
    RollerShadeCommand rollerShadeCommand6 = new RollerShadeCommand("GET_ERROR", 5, (byte)7);
    GET_ERROR = rollerShadeCommand6;
    RollerShadeCommand rollerShadeCommand7 = new RollerShadeCommand("CLEAR_ERROR", 6, (byte)8);
    CLEAR_ERROR = rollerShadeCommand7;
    RollerShadeCommand rollerShadeCommand8 = new RollerShadeCommand("IDENTIFY", 7, (byte)10);
    IDENTIFY = rollerShadeCommand8;
    RollerShadeCommand rollerShadeCommand9 = new RollerShadeCommand("LAUNCH_BOOTLOADER", 8, (byte)11);
    LAUNCH_BOOTLOADER = rollerShadeCommand9;
    RollerShadeCommand rollerShadeCommand10 = new RollerShadeCommand("FACTORY_RESET", 9, (byte)15);
    FACTORY_RESET = rollerShadeCommand10;
    RollerShadeCommand rollerShadeCommand11 = new RollerShadeCommand("GET_STATUS", 10, (byte)16);
    GET_STATUS = rollerShadeCommand11;
    RollerShadeCommand rollerShadeCommand12 = new RollerShadeCommand("MOVE_MANUAL", 11, (byte)17);
    MOVE_MANUAL = rollerShadeCommand12;
    RollerShadeCommand rollerShadeCommand13 = new RollerShadeCommand("GET_POSITION", 12, (byte)18);
    GET_POSITION = rollerShadeCommand13;
    RollerShadeCommand rollerShadeCommand14 = new RollerShadeCommand("SET_POSITION", 13, (byte)19);
    SET_POSITION = rollerShadeCommand14;
    RollerShadeCommand rollerShadeCommand15 = new RollerShadeCommand("GET_BATTERY", 14, (byte)22);
    GET_BATTERY = rollerShadeCommand15;
    RollerShadeCommand rollerShadeCommand16 = new RollerShadeCommand("GET_TIME", 15, (byte)32);
    GET_TIME = rollerShadeCommand16;
    RollerShadeCommand rollerShadeCommand17 = new RollerShadeCommand("SET_TIME", 16, (byte)33);
    SET_TIME = rollerShadeCommand17;
    RollerShadeCommand rollerShadeCommand18 = new RollerShadeCommand("GET_SCHEDULE_CHECKSUM", 17, (byte)34);
    GET_SCHEDULE_CHECKSUM = rollerShadeCommand18;
    RollerShadeCommand rollerShadeCommand19 = new RollerShadeCommand("SET_SCHEDULE", 18, (byte)35);
    SET_SCHEDULE = rollerShadeCommand19;
    RollerShadeCommand rollerShadeCommand20 = new RollerShadeCommand("GET_LOCATION", 19, (byte)36);
    GET_LOCATION = rollerShadeCommand20;
    RollerShadeCommand rollerShadeCommand21 = new RollerShadeCommand("SET_LOCATION", 20, (byte)37);
    SET_LOCATION = rollerShadeCommand21;
    RollerShadeCommand rollerShadeCommand22 = new RollerShadeCommand("GET_DAYLIGHT_SAVINGS_INFO_CHECKSUM", 21, (byte)38);
    GET_DAYLIGHT_SAVINGS_INFO_CHECKSUM = rollerShadeCommand22;
    RollerShadeCommand rollerShadeCommand23 = new RollerShadeCommand("SET_DAYLIGHT_SAVINGS_INFO", 22, (byte)39);
    SET_DAYLIGHT_SAVINGS_INFO = rollerShadeCommand23;
    RollerShadeCommand rollerShadeCommand24 = new RollerShadeCommand("GET_CALIBRATION_VALUE", 23, (byte)48);
    GET_CALIBRATION_VALUE = rollerShadeCommand24;
    RollerShadeCommand rollerShadeCommand25 = new RollerShadeCommand("SET_CALIBRATION_VALUE", 24, (byte)49);
    SET_CALIBRATION_VALUE = rollerShadeCommand25;
    RollerShadeCommand rollerShadeCommand26 = new RollerShadeCommand("START_CALIBRATION", 25, (byte)50);
    START_CALIBRATION = rollerShadeCommand26;
    RollerShadeCommand rollerShadeCommand27 = new RollerShadeCommand("GO_TO_CALIBRATION", 26, (byte)51);
    GO_TO_CALIBRATION = rollerShadeCommand27;
    $VALUES = new RollerShadeCommand[] { 
        rollerShadeCommand1, rollerShadeCommand2, rollerShadeCommand3, rollerShadeCommand4, rollerShadeCommand5, rollerShadeCommand6, rollerShadeCommand7, rollerShadeCommand8, rollerShadeCommand9, rollerShadeCommand10, 
        rollerShadeCommand11, rollerShadeCommand12, rollerShadeCommand13, rollerShadeCommand14, rollerShadeCommand15, rollerShadeCommand16, rollerShadeCommand17, rollerShadeCommand18, rollerShadeCommand19, rollerShadeCommand20, 
        rollerShadeCommand21, rollerShadeCommand22, rollerShadeCommand23, rollerShadeCommand24, rollerShadeCommand25, rollerShadeCommand26, rollerShadeCommand27 };
    Companion = new Companion(null);
  }

  RollerShadeCommand(byte paramByte) {
    this.v = (byte)paramByte;
  }

  public byte getV() {
    return this.v;
  }

  public byte toByte() {
    return GeneratedCommand.DefaultImpls.toByte(this);
  }
}

This code seems to layout the different commands that the roller shades accept. These seem to be the bluetooth commands as this file imports the bluetooth library and inherits a class from it. So its probably possible to leverage bluetooth to control these shades like the other plugin does.

That being said I dont think the decompiled code is complete because I cannot find any refs to the websocket URLs anywhere.

dzeleski commented 3 years ago
package com.mysmartblinds.tilt.networking;

import java.util.List;
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;

@Metadata(bv = {1, 0, 3}, d1 = {"\000*\n\002\030\002\n\002\020\000\n\000\n\002\020\016\n\000\n\002\020 \n\002\030\002\n\000\n\002\020\013\n\002\b\016\n\002\020\b\n\002\b\002\b†\b\030\0002\0020\001B%\022\006\020\002\032\0020\003\022\f\020\004\032\b\022\004\022\0020\0060\005\022\b\b\002\020\007\032\0020\b¢\006\002\020\tJ\t\020\020\032\0020\003HÆ\003J\017\020\021\032\b\022\004\022\0020\0060\005HÆ\003J\t\020\022\032\0020\bHÆ\003J-\020\023\032\0020\0002\b\b\002\020\002\032\0020\0032\016\b\002\020\004\032\b\022\004\022\0020\0060\0052\b\b\002\020\007\032\0020\bHÆ\001J\023\020\024\032\0020\b2\b\020\025\032\004\030\0010\001HÖ\003J\t\020\026\032\0020\027HÖ\001J\t\020\030\032\0020\003HÖ\001R\021\020\002\032\0020\003¢\006\b\n\000\032\004\b\n\020\013R\027\020\004\032\b\022\004\022\0020\0060\005¢\006\b\n\000\032\004\b\f\020\rR\021\020\007\032\0020\b¢\006\b\n\000\032\004\b\016\020\017¨\006\031"}, d2 = {"Lcom/mysmartblinds/tilt/networking/BridgePayload;", "", "authToken", "", "commands", "", "Lcom/mysmartblinds/tilt/networking/BridgeAPICommand;", "sendToAll", "", "(Ljava/lang/String;Ljava/util/List;Z)V", "getAuthToken", "()Ljava/lang/String;", "getCommands", "()Ljava/util/List;", "getSendToAll", "()Z", "component1", "component2", "component3", "copy", "equals", "other", "hashCode", "", "toString", "app_release"}, k = 1, mv = {1, 1, 16})
public final class BridgePayload {
  private final String authToken;

  private final List<BridgeAPICommand> commands;

  private final boolean sendToAll;

  public BridgePayload(String paramString, List<BridgeAPICommand> paramList, boolean paramBoolean) {
    this.authToken = paramString;
    this.commands = paramList;
    this.sendToAll = paramBoolean;
  }

  public final String component1() {
    return this.authToken;
  }

  public final List<BridgeAPICommand> component2() {
    return this.commands;
  }

  public final boolean component3() {
    return this.sendToAll;
  }

  public final BridgePayload copy(String paramString, List<BridgeAPICommand> paramList, boolean paramBoolean) {
    Intrinsics.checkParameterIsNotNull(paramString, "authToken");
    Intrinsics.checkParameterIsNotNull(paramList, "commands");
    return new BridgePayload(paramString, paramList, paramBoolean);
  }

  public boolean equals(Object paramObject) {
    if (this != paramObject) {
      if (paramObject instanceof BridgePayload) {
        paramObject = paramObject;
        if (Intrinsics.areEqual(this.authToken, ((BridgePayload)paramObject).authToken) && Intrinsics.areEqual(this.commands, ((BridgePayload)paramObject).commands) && this.sendToAll == ((BridgePayload)paramObject).sendToAll)
          return true; 
      } 
      return false;
    } 
    return true;
  }

  public final String getAuthToken() {
    return this.authToken;
  }

  public final List<BridgeAPICommand> getCommands() {
    return this.commands;
  }

  public final boolean getSendToAll() {
    return this.sendToAll;
  }

  public int hashCode() {
    byte b;
    String str = this.authToken;
    int i = 0;
    if (str != null) {
      b = str.hashCode();
    } else {
      b = 0;
    } 
    List<BridgeAPICommand> list = this.commands;
    if (list != null)
      i = list.hashCode(); 
    boolean bool1 = this.sendToAll;
    boolean bool2 = bool1;
    if (bool1)
      bool2 = true; 
    return (b * 31 + i) * 31 + bool2;
  }

  public String toString() {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("BridgePayload(authToken=");
    stringBuilder.append(this.authToken);
    stringBuilder.append(", commands=");
    stringBuilder.append(this.commands);
    stringBuilder.append(", sendToAll=");
    stringBuilder.append(this.sendToAll);
    stringBuilder.append(")");
    return stringBuilder.toString();
  }
}

This file seems to show the payload format being created in as a string. It eventually gets converted to json via the JsonConversion class. That being said if we look at what is happening to this.commands it looks gross but im not strong in java. The HashCode function seems to be purposefully ciphering or char shifting the payload. return (b * 31 + i) * 31 + bool2;.

Without being able to capture the websocket data trying to reverse engineer this will be pretty difficult. For now im going to shift over to going the bluetooth route and see if that is a bit easier of a path.

apexad commented 3 years ago

@dzeleski great research. Indeed this is or or less where I am at as well... seems possible that the roller shades are controlled only via bluetooth and cannot be controlled over the network in any way with the exception of possibly using Alexa/Echo maybe? Not sure really, but I have not looked at this in awhile.

mbmccormick commented 2 years ago

I was able to get some additional information by decompiling the Tilt APK, running in an Android emulator and debugging in Android Studio. There's a lot of logging output that looks useful. This is what I get when I just open the application with Bluetooth disabled and navigate to one of my rooms. The app appears to pull in the latest status for each device:

2021-10-12 12:14:30.496 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$ClearBackgroundConnections@8e66d2d, Old State: Idle(pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: []
2021-10-12 12:14:30.496 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$ClearBackgroundConnections@8e66d2d, Old State: Idle(pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: []
        at java.net.Socket.connect(Socket.java:621) 
        at libcore.io.ForwardingOs.connect(ForwardingOs.java:94)
2021-10-12 12:14:32.015 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$ClearBackgroundConnections@8e66d2d, Old State: Idle(pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: []
        at java.net.Socket.connect(Socket.java:570) 
        at libcore.io.IoBridge.connectErrno(IoBridge.java:156)
        at java.net.Socket.<init>(Socket.java:450) 
        at libcore.io.IoBridge.connect(IoBridge.java:134)
        at java.net.Socket.<init>(Socket.java:250) 
2021-10-12 12:14:32.015 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$ClearBackgroundConnections@8e66d2d, Old State: Idle(pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: []
2021-10-12 12:14:32.015 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$ClearBackgroundConnections@8e66d2d, Old State: Idle(pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: []
2021-10-12 12:14:36.289 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$ClearBackgroundConnections@8e66d2d, Old State: Idle(pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: []
2021-10-12 12:14:56.980 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$ClearBackgroundConnections@8e66d2d, Old State: Idle(pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: []
2021-10-12 12:15:03.675 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: ControlPeripherals(targetDevices=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7]), Old State: Idle(pendingConnections=[]), New State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), Command: [com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Command$StartConnecting@1da892d]
2021-10-12 12:15:03.714 3351-3479/com.mysmartblinds.tiltsmarthome D/BRIDGE: state changed: CONNECTING
2021-10-12 12:15:05.257 3351-3479/com.mysmartblinds.tiltsmarthome D/BRIDGE: state changed: OPEN
2021-10-12 12:15:06.894 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Connected
2021-10-12 12:15:07.253 3351-3451/com.mysmartblinds.tiltsmarthome D/BRIDGE: Write to socket: [(MAC: CE:73:CE:50:11:CE - Payload: 01:10), (MAC: CE:73:CE:50:11:CE - Payload: 02:04), (MAC: C0:0C:74:FD:D7:3F - Payload: 01:10), (MAC: C0:0C:74:FD:D7:3F - Payload: 02:04), (MAC: F6:CF:E4:A2:D2:52 - Payload: 01:10), (MAC: F6:CF:E4:A2:D2:52 - Payload: 02:04), (MAC: F4:82:D1:B9:AC:E7 - Payload: 01:10), (MAC: F4:82:D1:B9:AC:E7 - Payload: 02:04)]
2021-10-12 12:15:21.012 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"F4:82:D1:B9:AC:E7","payload":"4110e7033a0201"}],"error":null}
2021-10-12 12:15:21.208 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=F4:82:D1:B9:AC:E7, payload=[65, 16, -25, 3, 58, 2, 1], error=null)])
2021-10-12 12:15:21.236 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] F4:82:D1:B9:AC:E7 - 10:E7:03:3A:02:01, Command: 10, messageId: 1
2021-10-12 12:15:21.237 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"4110e703320201"}],"error":null}
2021-10-12 12:15:21.249 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[65, 16, -25, 3, 50, 2, 1], error=null)])
2021-10-12 12:15:21.272 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] CE:73:CE:50:11:CE - 10:E7:03:32:02:01, Command: 10, messageId: 1
2021-10-12 12:15:21.417 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"F4:82:D1:B9:AC:E7","payload":"7204040100140208020006b7000400"}],"error":null}
2021-10-12 12:15:21.428 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=F4:82:D1:B9:AC:E7, payload=[114, 4, 4, 1, 0, 20, 2, 8, 2, 0, 6, -73, 0, 4, 0], error=null)])
2021-10-12 12:15:21.480 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: BackgroundUpdatesFinished(targetDevices=[F4:82:D1:B9:AC:E7]), Old State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), New State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), Command: []
2021-10-12 12:15:21.488 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] F4:82:D1:B9:AC:E7 - 04:04:01:00:14:02:08:02:00:06:B7:00:04:00, Command: 04, messageId: 2
2021-10-12 12:15:21.489 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"7204040100140208020006b7000400"}],"error":null}
2021-10-12 12:15:21.501 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[114, 4, 4, 1, 0, 20, 2, 8, 2, 0, 6, -73, 0, 4, 0], error=null)])
2021-10-12 12:15:21.564 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: BackgroundUpdatesFinished(targetDevices=[CE:73:CE:50:11:CE]), Old State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), New State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), Command: []
2021-10-12 12:15:21.574 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] CE:73:CE:50:11:CE - 04:04:01:00:14:02:08:02:00:06:B7:00:04:00, Command: 04, messageId: 2
2021-10-12 12:15:23.262 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"C0:0C:74:FD:D7:3F","payload":"4110e703380201"}],"error":null}
2021-10-12 12:15:23.279 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=C0:0C:74:FD:D7:3F, payload=[65, 16, -25, 3, 56, 2, 1], error=null)])
2021-10-12 12:15:23.316 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] C0:0C:74:FD:D7:3F - 10:E7:03:38:02:01, Command: 10, messageId: 1
2021-10-12 12:15:23.785 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"C0:0C:74:FD:D7:3F","payload":"7204040100140208020006b7000400"}],"error":null}
2021-10-12 12:15:23.797 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=C0:0C:74:FD:D7:3F, payload=[114, 4, 4, 1, 0, 20, 2, 8, 2, 0, 6, -73, 0, 4, 0], error=null)])
2021-10-12 12:15:23.930 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: BackgroundUpdatesFinished(targetDevices=[C0:0C:74:FD:D7:3F]), Old State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), New State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), Command: []
2021-10-12 12:15:23.950 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] C0:0C:74:FD:D7:3F - 04:04:01:00:14:02:08:02:00:06:B7:00:04:00, Command: 04, messageId: 2
2021-10-12 12:15:25.311 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"F6:CF:E4:A2:D2:52","payload":"4110e7032f0201"}],"error":null}
2021-10-12 12:15:25.323 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=F6:CF:E4:A2:D2:52, payload=[65, 16, -25, 3, 47, 2, 1], error=null)])
2021-10-12 12:15:25.354 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] F6:CF:E4:A2:D2:52 - 10:E7:03:2F:02:01, Command: 10, messageId: 1
2021-10-12 12:15:27.652 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"F6:CF:E4:A2:D2:52","payload":"7204040100140208020006b7000400"}],"error":null}
2021-10-12 12:15:27.673 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=F6:CF:E4:A2:D2:52, payload=[114, 4, 4, 1, 0, 20, 2, 8, 2, 0, 6, -73, 0, 4, 0], error=null)])
2021-10-12 12:15:27.718 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: BackgroundUpdatesFinished(targetDevices=[F6:CF:E4:A2:D2:52]), Old State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), New State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), Command: []
2021-10-12 12:15:27.725 3351-3484/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] F6:CF:E4:A2:D2:52 - 04:04:01:00:14:02:08:02:00:06:B7:00:04:00, Command: 04, messageId: 2

And here's me manipulating one of the blinds (adjusting position):

2021-10-12 12:20:46.714 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: Connected
2021-10-12 12:20:46.816 3351-3451/com.mysmartblinds.tiltsmarthome D/BRIDGE: Write to socket: [(MAC: CE:73:CE:50:11:CE - Payload: 01:10)]
2021-10-12 12:20:49.500 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"4110e703320201"}],"error":null}
2021-10-12 12:20:49.514 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[65, 16, -25, 3, 50, 2, 1], error=null)])
2021-10-12 12:20:49.553 3351-3431/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: BackgroundUpdatesFinished(targetDevices=[CE:73:CE:50:11:CE]), Old State: Controlling(control=[CE:73:CE:50:11:CE], pendingConnections=[]), New State: Controlling(control=[CE:73:CE:50:11:CE], pendingConnections=[]), Command: []
2021-10-12 12:20:49.563 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] CE:73:CE:50:11:CE - 10:E7:03:32:02:01, Command: 10, messageId: 1
2021-10-12 12:20:57.875 3351-3451/com.mysmartblinds.tiltsmarthome D/BRIDGE: Write to socket: [(MAC: CE:73:CE:50:11:CE - Payload: 01:13:49:02:64)]
2021-10-12 12:21:00.869 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"4113"}],"error":null}
2021-10-12 12:21:00.880 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[65, 19], error=null)])
2021-10-12 12:21:00.908 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] CE:73:CE:50:11:CE - 13, Command: 13, messageId: 1
2021-10-12 12:21:19.301 3351-3451/com.mysmartblinds.tiltsmarthome D/BRIDGE: Write to socket: [(MAC: CE:73:CE:50:11:CE - Payload: 02:13:E8:03:64)]
2021-10-12 12:21:21.654 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"4213"}],"error":null}
2021-10-12 12:21:21.665 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[66, 19], error=null)])
2021-10-12 12:21:21.700 3351-3520/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] CE:73:CE:50:11:CE - 13, Command: 13, messageId: 2

I've tried to replicate the messages in a manual WebSocket request, but still not having much luck. Sending this I get no response from the server:

{
    "authToken": <redacted>,
    "commands": [
        {
            "mac": "CE:73:CE:50:11:CE",
            "command": "10"
        }
    ]
}
mbmccormick commented 2 years ago

More investigation notes: It looks like the WebSocket client being used by the app doesn't obey the system-wide proxy. So I installed an app called TunProxy in my emulator to force all connections to Proxyman using a system-wide VPN. Everything in the app works properly (and I see the traffic in Proxyman), except for communication with the WebSocket. And when I observe this with the Android Studio debugger attached, I see the following:

2021-10-12 13:35:27.461 4040-4192/com.mysmartblinds.tiltsmarthome D/BRIDGE: state changed: CONNECTING
2021-10-12 13:35:28.457 4040-4192/com.mysmartblinds.tiltsmarthome D/BRIDGE: state changed: CLOSED
2021-10-12 13:35:28.457 4040-4192/com.mysmartblinds.tiltsmarthome D/BRIDGE: on connect error: The certificate of the peer (ST=SG, CN=*.execute-api.us-east-1.amazonaws.com, O=Proxyman Inc, L=Singapore, C=SG) does not match the expected hostname (ws-api.tiltsmarthome.com)
2021-10-12 13:36:27.876 4040-4119/com.mysmartblinds.tiltsmarthome D/BRIDGE_STATE: Handle Event: com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Event$StopAllConnections@e5dc075, Old State: Controlling(control=[CE:73:CE:50:11:CE, C0:0C:74:FD:D7:3F, D2:E1:9E:D7:0E:7C, F6:CF:E4:A2:D2:52, F4:82:D1:B9:AC:E7], pendingConnections=[]), New State: Idle(pendingConnections=[]), Command: [com.mysmartblinds.tilt.bluetooth.BridgeConnectionStateMachine$Command$StopConnections@e0cc80a]

The app appears to be using SSL pinning to prevent MITM inspection. Looks like there may be some ways to circumvent this on Android with Frida.

mbmccormick commented 2 years ago

OK. Finally got it to work. 😈

I had to circumvent the hostname verification in https://github.com/TakahikoKawasaki/nv-websocket-client/blob/master/src/main/java/com/neovisionaries/ws/client/OkHostnameVerifier.java in the decompiled APK's smali code. That combined with TunProxy to capture all emulator traffic and Freda to bypass SSL pinning got all of the WebSocket traffic into Proxyman.

Here's the poll for status flow:

{
  "authToken": <redacted>,
  "commands": [
    {
      "mac": "C0:0C:74:FD:D7:3F",
      "command": "0110"
    },
    {
      "mac": "C0:0C:74:FD:D7:3F",
      "command": "0204"
    },
    {
      "mac": "CE:73:CE:50:11:CE",
      "command": "0110"
    },
    {
      "mac": "CE:73:CE:50:11:CE",
      "command": "0204"
    },
    {
      "mac": "F4:82:D1:B9:AC:E7",
      "command": "0110"
    },
    {
      "mac": "F4:82:D1:B9:AC:E7",
      "command": "0204"
    },
    {
      "mac": "F6:CF:E4:A2:D2:52",
      "command": "0110"
    },
    {
      "mac": "F6:CF:E4:A2:D2:52",
      "command": "0204"
    }
  ],
  "sendToAll": false
}

And the responses:

{
  "commands": [
    {
      "mac": "F4:82:D1:B9:AC:E7",
      "payload": "4110e703430201"
    }
  ],
  "error": null
}

{
  "commands": [
    {
      "mac": "F4:82:D1:B9:AC:E7",
      "payload": "7204040100140208020006b7000400"
    }
  ],
  "error": null
}

{
  "commands": [
    {
      "mac": "CE:73:CE:50:11:CE",
      "payload": "4110e703330201"
    }
  ],
  "error": null
}

{
  "commands": [
    {
      "mac": "CE:73:CE:50:11:CE",
      "payload": "7204040100140208020006b7000400"
    }
  ],
  "error": null
}

{
  "commands": [
    {
      "mac": "C0:0C:74:FD:D7:3F",
      "payload": "0110",
      "error": "timeout"
    },
    {
      "mac": "C0:0C:74:FD:D7:3F",
      "payload": "0204",
      "error": "timeout"
    },
    {
      "mac": "F6:CF:E4:A2:D2:52",
      "payload": "0110",
      "error": "timeout"
    },
    {
      "mac": "F6:CF:E4:A2:D2:52",
      "payload": "0204",
      "error": "timeout"
    }
  ],
  "error": null
}
2021-10-12 15:27:43.149 8254-8367/com.mysmartblinds.tiltsmarthome D/BRIDGE: Write to socket: [(MAC: CE:73:CE:50:11:CE - Payload: 01:10), (MAC: CE:73:CE:50:11:CE - Payload: 02:04), (MAC: C0:0C:74:FD:D7:3F - Payload: 01:10), (MAC: C0:0C:74:FD:D7:3F - Payload: 02:04), (MAC: F6:CF:E4:A2:D2:52 - Payload: 01:10), (MAC: F6:CF:E4:A2:D2:52 - Payload: 02:04), (MAC: F4:82:D1:B9:AC:E7 - Payload: 01:10), (MAC: F4:82:D1:B9:AC:E7 - Payload: 02:04)]
2021-10-12 15:27:57.870 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"F4:82:D1:B9:AC:E7","payload":"4110e703430201"}],"error":null}
2021-10-12 15:27:57.941 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=F4:82:D1:B9:AC:E7, payload=[65, 16, -25, 3, 67, 2, 1], error=null)])
2021-10-12 15:27:57.966 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] F4:82:D1:B9:AC:E7 - 10:E7:03:43:02:01, Command: 10, messageId: 1
2021-10-12 15:27:58.171 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"F4:82:D1:B9:AC:E7","payload":"7204040100140208020006b7000400"}],"error":null}
2021-10-12 15:27:58.172 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=F4:82:D1:B9:AC:E7, payload=[114, 4, 4, 1, 0, 20, 2, 8, 2, 0, 6, -73, 0, 4, 0], error=null)])
2021-10-12 15:27:58.202 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] F4:82:D1:B9:AC:E7 - 04:04:01:00:14:02:08:02:00:06:B7:00:04:00, Command: 04, messageId: 2
2021-10-12 15:28:00.733 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"4110e703330201"}],"error":null}
2021-10-12 15:28:00.741 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[65, 16, -25, 3, 51, 2, 1], error=null)])
2021-10-12 15:28:00.761 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] CE:73:CE:50:11:CE - 10:E7:03:33:02:01, Command: 10, messageId: 1
2021-10-12 15:28:01.351 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"7204040100140208020006b7000400"}],"error":null}
2021-10-12 15:28:01.353 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[114, 4, 4, 1, 0, 20, 2, 8, 2, 0, 6, -73, 0, 4, 0], error=null)])
2021-10-12 15:28:01.392 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: [APP RX] CE:73:CE:50:11:CE - 04:04:01:00:14:02:08:02:00:06:B7:00:04:00, Command: 04, messageId: 2
2021-10-12 15:28:08.512 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"C0:0C:74:FD:D7:3F","payload":"0110","error":"timeout"},{"mac":"C0:0C:74:FD:D7:3F","payload":"0204","error":"timeout"},{"mac":"F6:CF:E4:A2:D2:52","payload":"0110","error":"timeout"},{"mac":"F6:CF:E4:A2:D2:52","payload":"0204","error":"timeout"}],"error":null}
2021-10-12 15:28:08.517 8254-8399/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=C0:0C:74:FD:D7:3F, payload=[1, 16], error=timeout), BridgeCommandResponse(mac=C0:0C:74:FD:D7:3F, payload=[2, 4], error=timeout), BridgeCommandResponse(mac=F6:CF:E4:A2:D2:52, payload=[1, 16], error=timeout), BridgeCommandResponse(mac=F6:CF:E4:A2:D2:52, payload=[2, 4], error=timeout)])
mbmccormick commented 2 years ago

Close one shade:

{
  "authToken": <redacted>,
  "commands": [
    {
      "mac": "CE:73:CE:50:11:CE",
      "command": "0113000064"
    }
  ],
  "sendToAll": false
}
{
  "commands": [
    {
      "mac": "CE:73:CE:50:11:CE",
      "payload": "4113"
    }
  ],
  "error": null
}
2021-10-12 15:34:57.642 8254-8254/com.mysmartblinds.tiltsmarthome D/REACTOR_COMMAND: Send(macAddress=CE:73:CE:50:11:CE, message=group.tesseract.bluetoothcommunicationprotocol.payloadformatters.EncryptedBluetoothMessage$ApplicationLayer@72207bb)
2021-10-12 15:34:57.676 8254-8367/com.mysmartblinds.tiltsmarthome D/BRIDGE: Write to socket: [(MAC: CE:73:CE:50:11:CE - Payload: 01:13:00:00:64)]
2021-10-12 15:35:00.078 8254-8524/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"4113"}],"error":null}
2021-10-12 15:35:00.083 8254-8524/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[65, 19], error=null)])

Open one shade:

{
  "authToken": <redacted>,
  "commands": [
    {
      "mac": "CE:73:CE:50:11:CE",
      "command": "0213E80364"
    }
  ],
  "sendToAll": false
}
{
  "commands": [
    {
      "mac": "CE:73:CE:50:11:CE",
      "payload": "41100000320201"
    }
  ],
  "error": null
}
2021-10-12 15:35:57.999 8254-8367/com.mysmartblinds.tiltsmarthome D/BRIDGE: Write to socket: [(MAC: CE:73:CE:50:11:CE - Payload: 02:13:E8:03:64)]
2021-10-12 15:35:59.748 8254-8524/com.mysmartblinds.tiltsmarthome D/BRIDGE: text message received: {"commands":[{"mac":"CE:73:CE:50:11:CE","payload":"41100000320201"}],"error":null}
2021-10-12 15:35:59.750 8254-8524/com.mysmartblinds.tiltsmarthome D/BRIDGE: Parsed response: BridgeAPICommandResponse(error=null, commands=[BridgeCommandResponse(mac=CE:73:CE:50:11:CE, payload=[65, 16, 0, 0, 50, 2, 1], error=null)])
apexad commented 2 years ago

Wow! Nice work. My time has been limited lately to work on my home automation stuff sadly and it seems just about all my plugins need updating. However, I will gladly accept PRs and will look into getting this all coded into the plugin as soon as I have time otherwise.

mbmccormick commented 2 years ago

I stubbed out some code to encode/decode the get_status, get_version, and set_position commands using the schema that the WebSocket uses:

function hexToBytes(hex) {
    for (var bytes = [], c = 0; c < hex.length; c += 2) {
        var num = parseInt(hex.substr(c, 2), 16);
        if (num > 127) num -= 256; // special sauce for uint
        bytes.push(num);
    }
    return bytes;
}

 function intToBytes(int) {
    var byteArray = [0, 0];

    for ( var index = 0; index < byteArray.length; index ++ ) {
        var byte = int & 0xff;
        int = (int - byte) / 256 ;
        if (byte > 127) byte -= 256; // special sauce for uint
        byteArray [ index ] = byte;
    }

    return byteArray;
};

function bytesToHex(bytes) {
    for (var hex = [], i = 0; i < bytes.length; i++) {
        var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
        hex.push((current >>> 4).toString(16));
        hex.push((current & 0xF).toString(16));
    }
    return hex.join("");
}

function generateGetStatusCommand() {
    var payload = [ 01, 16 ]; // first byte is message number, second byte is command ID
    var command = bytesToHex(payload);

    return command;
}

function parseGetStatusResponse(payload) {
    var decoded = hexToBytes(payload); 

    return {
        position: decoded[2] + (decoded[3] * 256) + 256, // special sauce for uint-little-16 data type
        percent: decoded[4],
        charge_status: decoded[5],
        calibrated: decoded[6]
    };
}

function generateGetVersionCommand() {
    var payload = [ 01, 04 ]; // first byte is message number, second byte is command ID
    var command = bytesToHex(payload);

    return command;
}

function parseGetVersionResponse(payload) {
    var decoded = hexToBytes(payload);

    return {
        device_major: decoded[2],
        device_minor: decoded[3],
        app_major: decoded[4],
        app_minor: decoded[5],
        dfu_major: decoded[6],
        dfu_minor: decoded[7],
        hardware_major: decoded[8],
        hardware_minor: decoded[9],
        softdevice: decoded[10],
        softdevice_id: decoded[11] + (decoded[12] * 256) + 256, // special sauce for uint-little-16 data type
        app_patch: decoded[13],
        app_build: decoded[14]
    };
}

function generateSetPositionCommand(position) {
    var positionBytes = intToBytes(position);
    var payload = [ 01, 19, positionBytes[0], positionBytes[1], 100 ];
    var command = bytesToHex(payload);

    return command;
}

Any pointers on where to integrate this into the plugin? I see where you started implementing the WebSocket connection in platform.ts, but I'm not sure if that's where I should implement these methods. I'll try and reference your other MySmartBlinds plugin.

adamdottv commented 8 months ago

anybody else still interested in getting this plugin to work? bc i am! can i pickup where whoever left off?

arcticgenes commented 8 months ago

I’d still be interested in getting this working. Let me know if you need a tester.

apexad commented 8 months ago

Feel free to pick this up! Lot’s of good info in this thread. Since I never had the actual hardware I only got so far with this plugin.

You may also need to look at the mysmartblinds plugin as a recently updated that one because of some authentication changes.