koalazak / dorita980

Unofficial iRobot Roomba and Braava (i7/i7+, 980, 960, 900, e5, 690, 675, m6, etc) node.js library (SDK) to control your robot
MIT License
942 stars 150 forks source link

Support for 600 Wifi enabled series / other Roombas #71

Closed ahorseman closed 5 years ago

ahorseman commented 6 years ago

Hello,

I wanted to ask whether you guys are also planning to support other Roombas than just the 980?

The Wifi enabled 600 series (currently on a firmware 3.x) would also be interesting to support, as they are much more for the mass market (given the price) and I would be happy to help discovering supported features and requests.

Is there a way to get an API overview of the robot's API (something like wsdl was for web services) that I can post here for enhancing (or forking) dorita980 for 600 series?

koalazak commented 5 years ago

the best way is reversing the mobile app and sniffing the netword trafic between the mobile app and roomba or cloud. I dont own a 600 robot to try. Let me know if you start a 600's fork! thanks!

Zefau commented 5 years ago

I don't own a Roomba 600 or any other with v3 firmware, but according to information in the ioBroker forum, the v3 software uses MQTT via port 8883. See https://forum.iobroker.net/viewtopic.php?f=20&t=8092&p=199686#p199367

Zefau commented 5 years ago

@koalazak How are you sniffing the network traffic? What software are you using and how do you reverse?

koalazak commented 5 years ago

im sniffing with wireshark and charles proxy in mac. And for reversing the app I grab the .apk file from Android store and decompile it with online tools.

crashf commented 5 years ago

Im not sure if this will help you or not, but, I spent some time sniffing packets with wireshark. Here is a dump of just traffic from/to the roomba 675. Opened the app and performed a "return to duck" request.

filtereddump-roomba675-fromrouter.zip

crashf commented 5 years ago

I will also try to dump during the wireless setup etc later today. I am not great with python so if anyone is up for picking around let me know what else I can dump.

koalazak commented 5 years ago

Hi, sorry for the delay. That dumps are ssl encryted. Did you sign the trafic with your own CA? you need to do that to decrypt the dump content

Jeff-Cortese commented 5 years ago

Hi, I was able to get this library working with my Roomba 690 (firmware 3.2). A tip that might be useful is that you can get the Roomba's IP address from the iRobot HOME app by tapping More (...) > Settings > Wi-Fi Settings > Robot Wi-Fi Details. Using that IP, I could use the getpassword npm script to look up the blid and password (via checkV2() function in bin/getpassword.js).

With that...

const dorita980 = require('dorita980');
const myLocalRobot = new dorita980.Local('blid_here', 'password_here', 'ip_here');
myLocalRobot.on('connect', () => { console.log('connected'); });

...works as expected

Zefau commented 5 years ago

The connection works with Firmware v3.0, but no data is received as far as I know. Do you have a running instance of ioBroker? I made an adapter for Roomba on this plattform (see https://github.com/Zefau/ioBroker.roomba).

Other users report, that a connection is successful with Firmware v3, but no data is received.

Are you able to execute any commands after connection or able to retrieve preferences?

Kind regards, Zefau

koalazak commented 5 years ago

Hi @Jeff-Cortese, can you run this code and share the output please?

const dorita980 = require('dorita980');
const myLocalRobot = new dorita980.Local('blid_here', 'password_here', 'ip_here');
myLocalRobot.on('connect', () => { 
  console.log('connected'); 
  myLocalRobot.on('packetreceive', function (packet) {
    console.log(packet); 
  });
});

thank you!

Jeff-Cortese commented 5 years ago

@Zefau I'm not sure what ioBroker is, so probably not if it's not a part of dorita and running as a side-effect of the code I posted earlier. And yes, a few commands did work (I have only tried start, pause, resume, and end).

@koalazak I tweaked it a little to format the packet payload buffer:

  const dorita980 = require('dorita980');
  const myLocalRobot = new dorita980.Local('blid_here', 'password_here', 'ip_here');

  myLocalRobot.on('connect', () => { 
    console.log('connected'); 
    myLocalRobot.on('packetreceive', packet =>  {
      packet.payload = JSON.parse(packet.payload);
      console.log(JSON.stringify(packet, null, 2));
    });
  });

The output is as follows (I redacted some info using X's because I'm not sure what is sensitive info). Packets started to repeat, so I terminated it after about 10 seconds. I can run it longer if you'd like.

connected
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 182,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "netinfo": {
          "dhcp": true,
          "addr": xxxxxxxxxx,
          "mask": xxxxxxxxxx,
          "gw": xxxxxxxxxx,
          "dns1": xxxxxxxxxx,
          "dns2": xxxxxxxxxx,
          "bssid": "xxxxxxxxxx",
          "sec": 4
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 78,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "wifistat": {
          "wifi": 1,
          "uap": false,
          "cloud": 1
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 182,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "netinfo": {
          "dhcp": true,
          "addr": xxxxxxxxxx,
          "mask": xxxxxxxxxx,
          "gw": xxxxxxxxxx,
          "dns1": xxxxxxxxxx,
          "dns2": xxxxxxxxxx,
          "bssid": "xxxxxxxxxx",
          "sec": 4
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 84,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "wlcfg": {
          "sec": 7,
          "ssid": "XXXXXXXXXXXXXXXXXXXXXX"
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 60,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "mac": "XX:XX:XX:XX:XX:XX"
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 84,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "country": "US"
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 87,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "cloudEnv": "prod"
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 105,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "svcEndpoints": {
          "svcDeplId": "v007"
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 85,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "name": "Jarvis"
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 515,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "cap": {
          "ota": 1,
          "eco": 1,
          "svcConf": 1
        },
        "bbrun": {
          "nCliffsF": 261,
          "nPanics": 42,
          "hr": 7,
          "min": 58,
          "nScrubs": 74,
          "sqft": 0,
          "nStuck": 0,
          "nPicks": 0,
          "nCliffsR": 0,
          "nMBStll": 0,
          "nWStll": 0,
          "nCBump": 0
        },
        "bbmssn": {
          "aMssnM": 31,
          "nMssnF": 12,
          "nMssnOk": 13,
          "nMssn": 25,
          "nMssnC": 0,
          "aCycleM": 0
        },
        "bbpause": {
          "pauses": [0, 0, 18, 0, 0, 0, 0, 0, 0, 0]
        },
        "cleanSchedule": {
          "cycle": ["none", "start", "start", "start", "start", "start", "none"],
          "h": [0, 14, 14, 14, 14, 14, 9],
          "m": [0, 0, 0, 0, 0, 0, 0]
        },
        "language": 0
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 365,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "cleanMissionStatus": {
          "cycle": "none",
          "phase": "charge",
          "expireM": 0,
          "rechrgM": 0,
          "error": 0,
          "notReady": 0,
          "mssnM": 0,
          "sqft": 0,
          "initiator": "",
          "nMssn": 25
        },
        "dock": {
          "known": false
        },
        "bin": {
          "present": true,
          "full": false
        },
        "batteryType": "lith",
        "batPct": 100,
        "mobilityVer": "6985",
        "bootloaderVer": "4",
        "soundVer": "13"
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 531,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "langs": [
          { "en-US": 0 },
          { "en-GB": 15 },
          { "fr-FR": 1 },
          { "de-DE": 2 },
          { "es-ES": 3 },
          { "es-XL": 11 },
          { "pt-PT": 12 },
          { "pt-BR": 19 },
          { "it-IT": 4 },
          { "nl-NL": 5 },
          { "da-DK": 6 },
          { "sv-SE": 7 },
          { "nb-NO": 8 },
          { "fi-FI": 16 },
          { "pl-PL": 10 },
          { "cs-CZ": 17 },
          { "ru-RU": 18 },
          { "he-IL": 20 },
          { "ja-JP": 13 },
          { "zh-CN": 14 },
          { "zh-TW": 9 }
        ],
        "audio": {
          "active": false
        },
        "binPause": false,
        "carpetBoost": false,
        "noAutoPasses": false,
        "noPP": false,
        "openOnly": false,
        "twoPass": false,
        "vacHigh": false,
        "sku": "R690020",
        "timezone": "America/Chicago"
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 295,
  "topic": "$aws/things/XXXXXXXXXXXXXXXX/shadow/update",
  "payload": {
    "state": {
      "reported": {
        "tz": {
          "ver": 6,
          "events": [
            {
              "dt": 1541073600,
              "off": -300
            },
            {
              "dt": 1541314801,
              "off": -360
            },
            {
              "dt": 1552204801,
              "off": -300
            }
          ]
        },
        "ecoCharge": false,
        "wifiSwVer": "3.2.40+69",
        "softwareVer": "3.2.40+69",
        "hardwareRev": 2,
        "wifiAnt": 0,
        "schedHold": false
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 65,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "signal": {
          "rssi": -40,
          "snr": 46
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 65,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "signal": {
          "rssi": -43,
          "snr": 45
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 65,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "signal": {
          "rssi": -34,
          "snr": 56
        }
      }
    }
  }
}
{
  "cmd": "publish",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 65,
  "topic": "wifistat",
  "payload": {
    "state": {
      "reported": {
        "signal": {
          "rssi": -34,
          "snr": 56
        }
      }
    }
  }
}
{
  "cmd": "pingresp",
  "retain": false,
  "qos": 0,
  "dup": false,
  "length": 0,
  "topic": null,
  "payload": null
}
koalazak commented 5 years ago

Hi, @Jeff-Cortese, thanks a lot! this was very helpful. I think the problem with the other features is that dorita980 is waiting for the pose state and your robot (600 series) is not reporting that postion, then dorita980 wait forever. I will work on fix this, seems easy to do. If you want to test the fix just open node_modules/dorita980/lib/v2/local.js and remove the occurences of pose string or objects (like line 35, 36, 107, 109, etc)

thanks again guys,

gik007 commented 5 years ago

Hi @koalazak,

im sniffing with wireshark and charles proxy in mac. And for reversing the app I grab the .apk file from Android store and decompile it with online tools.

I know that your project is focused on the 980 model (or it was like that at the beginning). I have another model (e5) and have tried to use your library without success. I have the impression that your implementation is close to working with that model, but I fail to get the password ("Error getting password. Follow the instructions and try again." which hints at a short response to the MQTT request. The response I get is "f005efcc3b2903", which is pretty close to the request your code sends to get the password.

I could try to reverse engineer the network traffic, but where should I start? Your implementation shows that you have understood pretty well what happens between client and server (at least for your model), do you have that information somewhere so that we can start creating an unofficial documentation of the communication used by the different roomba models?

Gracias!

Zefau commented 5 years ago

We discussed e5 support in https://github.com/Zefau/ioBroker.roomba/issues/1. Remove "pose" from preferences worked here as well.

gik007 commented 5 years ago

@Zefau thanks, that is a great hint. I just did that, unfortunately that didn't help to get the password. I wonder how other e5 users (like @kgerlich) have managed to get the password out of the device.

gik007 commented 5 years ago

Good news. The error was sitting between keyboard and chair. I did not correctly understand the message about pressing the HOME button (I was pressing the "CLEAN" button). There was a message in another thread giving that hint and it was helpful. So I can confirm that things are working well on the e5. Thanks for making it possible!

koalazak commented 5 years ago

600 and e5 series supported in dorita980@3.1.0 thanks guys

kgerlich commented 5 years ago

Very cool!!!! Thanks!

This side up...

Am 08.01.2019 um 15:03 schrieb Facu ZAK notifications@github.com:

Closed #71.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

Zefau commented 5 years ago

What have you changed with the release? Am I right that pose is no longer part of getPreferences() so it'll work for 600 / e5 series? pose is necessary though in order to be able to draw a map using the positions given, right? So the call of getMission() will still not work for e5 / 600 series?

TonyBrobston commented 5 years ago

I can confirm that I was able to get the blid and password on a Roomba 690 version 3.2.40.

It's super important that the wifi symbol flashes. I had to hold the button above the "Clean" button and the button below the "Clean" button for 2 seconds to get the wifi symbol to flash. Any run before that generated all the information except the password.

It took me forever to figure out the wifi symbol not blinking was the issue, I figured I needed to share this somewhere, hope it helps someone else!

nini57 commented 4 years ago

hello somebody can me confirm this is right please ?

LINE 35 if (robotState.cleanMissionStatus) { //) { && robotState.pose) { LINE 36 client.emit('mission', filterProps(['cleanMissionStatus', 'bin'])); 'bin'])); LINE 107 getPreferences: (decode) => waitPreferences(decode, ['cleanMissionStatus', 'cleanSchedule', 'name', 'vacHigh', 'signal'], false), LINE 109 getMission: (decode) => waitPreferences(decode, ['cleanMissionStatus', 'bin', 'batPct'], true),