Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloud API firmware 2.x.x #25

Closed
koalazak opened this issue Feb 20, 2017 · 57 comments
Closed

Cloud API firmware 2.x.x #25

koalazak opened this issue Feb 20, 2017 · 57 comments

Comments

@koalazak
Copy link
Owner

koalazak commented Feb 20, 2017

Hi guys,
I have almost everything ready to make the Cloud API possible:

  • cloud discovery
  • cloud aws login
  • suscribe to topics in the cloud to receive state updates
  • make changes in the state to set preferences ( schedule, bust, etc)

But i dont found the correct topic and message content to send basic commands like start or stop.
Anybody sniff that data? or has that data?

My sniff data is weired and malformed, i dont know if my sslsplit is showingme the info in the correct encoding. When I send a command with my phone over the cloud I see some bytes in the comunication but no one string like topic o json message.

can anybody help?

here is the working snippet:

const AWSIoTData = require('aws-iot-device-sdk');
const AWS = require('aws-sdk');
const request = require('request-promise');
// install with: npm install aws-iot-device-sdk aws-sdk request-promise request

const ROBOT_BLID = ''; // same as local api
const ROBOT_PASSWORD = ''; // same as local api
const APP_ID = ''; // like IOS-12345678-1234-1234-1234-123456789098

function cloudDiscovery () {
  var requestOptions = {
    'method': 'GET',
    'uri': `https://disc-prod.iot.irobotapi.com/v1/robot/discover/${ROBOT_BLID}`,
    'json': true
  };
  return request(requestOptions);
}

function cloudLogin () {
  return cloudDiscovery().then(function (discoveryData) {
    var postData = {
      'associations': {
        '0': {
          'robot_id': ROBOT_BLID,
          'deleted': false,
          'password': ROBOT_PASSWORD
        }
      },
      'app_id': APP_ID
    };

    var requestOptions = {
      'method': 'POST',
      'headers': {
        'Content-Type': 'application/json',
        'User-Agent': 'aspen/1.9.1.184.1 CFNetwork/808.2.16 Darwin/16.3.0'
      },
      'uri': `${discoveryData.httpBase}/v1/login`,
      'body': postData,
      'json': true
    };

    return request(requestOptions).then((rawLoginResponse) => {
      return {login: rawLoginResponse.associations['0'], credentials: rawLoginResponse.credentials, discovery: discoveryData};
    });
  });
}

function initMQTT (amazonData) {
  var awsConfiguration = {
    poolId: amazonData.credentials.CognitoId,
    region: amazonData.discovery.awsRegion
  };

  var AWSConfiguration = awsConfiguration;

  var clientId = ROBOT_BLID + '-' + (Math.floor((Math.random() * 100000) + 1));

  AWS.config.region = AWSConfiguration.region;

  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: AWSConfiguration.poolId
  });

  const mqttClient = AWSIoTData.device({
    region: AWS.config.region,
    clientId: clientId,
    protocol: 'wss',
    maximumReconnectTimeMs: 8000,
    debug: true,
    accessKeyId: amazonData.credentials.AccessKeyId,
    secretKey: amazonData.credentials.SecretKey,
    sessionToken: amazonData.credentials.SessionToken
  });

  mqttClient.on('connect', function (e) {
    console.log('connect!', e);

    mqttClient.subscribe('$aws/things/' + ROBOT_BLID + '/shadow/#'); // all subtopics

    // const cmd = {'state': {'desired': {'cleanSchedule': {'cycle': ['none', 'none', 'none', 'none', 'none', 'none', 'none'], 'h': [17, 10, 10, 12, 10, 13, 17], 'm': [0, 30, 30, 0, 30, 30, 0]}}}};
    // mqttClient.publish('$aws/things/' + ROBOT_BLID + '/shadow/update', JSON.stringify(cmd));
  });

  mqttClient.on('reconnect', function () {
    console.log('reconnect!');
  });

  mqttClient.on('message', function (t, m) {
    console.log('message:');
    console.log('topic:', t);
    console.log(m.toString());
  });

  mqttClient.on('delta', function (m) {
    console.log('delta:');
    console.log(m);
  });
  mqttClient.on('status', function (m) {
    console.log('status:');
    console.log(m);
  });

  mqttClient.on('data', function (m) {
    console.log('data:');
    console.log(m);
  });

  mqttClient.on('error', function (e) {
    console.log('error:');
    console.log(e);
  });

  mqttClient.on('packetreceive', function (m) {
    console.log('packetreceive:');
    console.log(m);
  });
}

cloudLogin().then((credentialData) => {
  console.log(credentialData);
  initMQTT(credentialData);
}).catch(console.log);
@koalazak
Copy link
Owner Author

@akpotter @thoro :)

@barbagianni
Copy link

Hey @koalazak!

I'd like to help. Can you provide some instructions?

How do I find the app id to run your snippet? I guess it's from the mobile app?

Cheers

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

I think you can use any string with that format. I get my app ID sniffing the trafic.

@barbagianni
Copy link

OK, it seems to do something. Which is the relevant output? :)

@barbagianni
Copy link

So i did run a start stop cycle through the cloud and got readable output from your script.

Is this what you're looking for?

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

the output is all the messages sent to the topic $aws/things/' + ROBOT_BLID + '/shadow (and subtopics) by the robot or by the official mobile app.
When you say through the cloud and got readable output you mean using the official mobile app? or using this script (publishing to a topic with this script)?

What im looking for is what message and what topic we suppose to use in mqttClient.publish() method in this script to start/stop the robot via this script.

@barbagianni
Copy link

Running this script, then triggering the bot via the official mobile app.

When I start the script, the output ends with "undefined".

@barbagianni
Copy link

One sec, I'm sanitizing the output.

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

ok, you are looking the messages published by the mobile app or robot to that topics. that is what i get too. and using the mqttClient.publish() method I can set new schedule for example. But What im looking for is what message and what topic we suppose to use in mqttClient.publish() method in this script to start/stop the robot via this script.

@barbagianni
Copy link

So do you have some instructions to obtain them?

I have a ton of JSON output. But only after triggering something via the official mobile app.

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

you can start making a MITM attack to sniff the data between the official mobile app and the cloud, getting the mqtt packets and decode them. Google about MITM

@barbagianni
Copy link

No TLS on that?

No chance to reconstruct the command from something like this?
message: topic: $aws/things/ROBOT_BLID_HERE/shadow/update/accepted {"state":{"reported":{"lastCommand":{"command":"start","time":1488399056,"initiator":"rmtApp"}}},"metadata":{"reported":{"lastCommand":{"command":{"timestamp":1488399056},"time":{"timestamp":1488399056},"initiator":{"timestamp":1488399056}}}},"version":1617,"timestamp":1488399057}

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

yes, TLS on that. bypassing with sslsplit.
I try some variants of that message but no way.

@barbagianni
Copy link

Is there a way to trigger the cloud api when you're on wifi?

I have a proxy running to monitor the traffic.

@barbagianni
Copy link

Never mind, i isolated my networks, now it tries to go through the cloud.

I see a message with the app id, i also get the cloud icon in the app. But now my roomba doesn't react when i press start.

@barbagianni
Copy link

Reset everything, now the roomba reacts, but I don't see the commands in the proxy. Only the requests for mission history, login, etc. Will debug a bit more...

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

Its like the start/stop commands are in other format not json, like raw mqtt packets, a few bytes...

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

Make sure you are creating certs with a valid CommonName.
The mobile app is validating the CN field in the roomba cert with the format 'Roomba-{number16}' if it is not valid, then the mobile app disconnect. (the validation is in the mobile app, so you need a selfisgned cert with this CN if you want to perform a sslsplit to sniff the trafic

@koalazak
Copy link
Owner Author

koalazak commented Mar 1, 2017

but if you are seeing the mission commands and commands when you set preferences, there is no problem with the cert.

@barbagianni
Copy link

Hmm, I used mitmproxy instead of sslsplit. I'm a bit too tired to read through how to use sslsplit right now.

There should be a option to see raw tcp traffic in mitmproxy. I'm seeing the http traffic, but I'm guessing mqtt gets lost.

@barbagianni
Copy link

I think i have a working setup now redirecting to sslsplit. A test port 80 request got logged. I tried redirecting 8883 but get no traffic. Any tips to debug the problem?

@barbagianni
Copy link

barbagianni commented Mar 2, 2017

OK, finally got a start message :)

Can I send it to you by email? It's quite long. Seems like it came over an upgraded ssl websocket carrying mqtt. Theres the upgrade, some garbage (probably mqtt), then start message, garbage, pause message.

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

yes sure.
I got the message too. But I cant figure out how to reproduce it in my test snippet. Can you?

@barbagianni
Copy link

I got to a version mismatch. It seems to be a sequence number. The question is where from.

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

well, that is a step forward! There is not in state object?

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

version is in every message received:

{
"state":{"reported":{"svcEndpoints":{"svcDeplId":"v007"}}},
"metadata":{"reported":{"svcEndpoints":{"svcDeplId":{"timestamp":1488553359}}}},
"version":3345,
"timestamp":1488553359
}

you can parse all the messages and store the version to use in the next call.

can you share your code? zaktu.x@gmail.com

@barbagianni
Copy link

Will send it later

@barbagianni
Copy link

Sent the log.

Btw, sending the previous command without the version actually gets a valid reply. But the robot doesn't start.

The message I sent was:
{"state":{"reported":{"lastCommand":{"command":"start","time":1488399056,"initiator":"rmtApp"}}},"metadata":{"reported":{"lastCommand":{"command":{"timestamp":1488399056},"time":{"timestamp":1488399056},"initiator":{"timestamp":1488399056}}}},"timestamp":1488399057}

@barbagianni
Copy link

{ "state":{ "desired":{ "command":{ "command":"start","time":+new Date(),"initiator":"rmtApp" } } }, "timestamp":+new Date() }

Bam! Your working start command! :)

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

nice!
I tried something like that before but nothing. Maybe I dont use the time filed in my tests...
Testing now....

@barbagianni
Copy link

I actually got the robot to react with the last posted one. "start" for start and "stop" for stop.

There seems to be some kind of problems with the timestamps though, as it will start over and over again.

@barbagianni
Copy link

Yes, it seems to be important.

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

oh crap, that works but the robot now is receiving the start command every second :p.
let me kwno if you can stop it :p

@barbagianni
Copy link

Send the stop message. Same format. x)

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

you promise that i can not get a loop of start AND stop commands now? :p

@barbagianni
Copy link

I can't, but my hope is that a hard reset will be able to fix it. :)

Let me try if the app is still able to start it.

@barbagianni
Copy link

Actually it is in a start stop loop. I'm resetting mine.

@barbagianni
Copy link

Yep, a reset fixed it. But that means we have to find out where to get the right timestamps.

@barbagianni
Copy link

Or maybe it's not about the timestamps but some of the other parts of the messages. The messages in the app seem to contain more information.

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

reset as 10 second the start button?

@barbagianni
Copy link

Didn't try that. I used the "reset roomba" option in the app. But you have to pair it again.

@koalazak
Copy link
Owner Author

koalazak commented Mar 3, 2017

reset with 10 second start button doest work. but yes with the App.

@iosdeveloper
Copy link

New firmware is rolling out (again). Does anybody know what's changed? Hopefully nothing broke.
img_4182

@koalazak
Copy link
Owner Author

koalazak commented Mar 5, 2017

@iosdeveloper opened a new issue for that: #31

@koalazak
Copy link
Owner Author

koalazak commented Mar 5, 2017

@Letier do you have any progress in the cloud api reverse engeniering?

@barbagianni
Copy link

No luck. :(

It works with the iOS app at the moment, but doesn't do anything when I send the signals. The reset option in the app is greyed out, so I have no clue if I got it stuck in a strange state again.

@barbagianni
Copy link

Managed to reset and catch another conversation with the app. Saw the start message. But I'm suddenly not getting it to start anymore. And there seems to be all kind of state from my past trials saved. I'll need to find a way to clean it up. Posting via the snippet doesn't seem to work anymore.

@barbagianni
Copy link

barbagianni commented Mar 5, 2017

So the situation seems to be that you somehow have to switch between certain states.

The following cycles work for me:
{ "state":{"desired":{"command":{"command":"start","time":Math.floor(new Date()/1000),"initiator":"rmtApp"}}}}
{ "state":{"desired":{"command":{"command":"clean","time":Math.floor(new Date()/1000),"initiator":"rmtApp"}}}}
{ "state":{"desired":{"command":null}}}

{ "state":{"desired":{"command":{"command":"stop","time":Math.floor(new Date()/1000),"initiator":"rmtApp"}}}}
{ "state":{"desired":{"command":null}}}

I'm not sure if clean is a real command. I just tried it and the robot started moving.

@koalazak
Copy link
Owner Author

I dont like that aprouch :p may there is another fancy way haha. If i dont foudn the way y just implement that in next version.

@andrey3diq
Copy link

Hi Guys, did you try to contact iRobot support to get some additional info?

@pschmitt
Copy link

I dont believe they'd be of much help:
https://homesupport.irobot.com/app/answers/detail/a_id/9840

@andrey3diq
Copy link

andrey3diq commented Oct 25, 2017

Snippet doesn't work for me.
body: { errorMessage: 'Authentication failed', errorType: 'AspenError.AuthenticationFailed' }

I think it can be because I use fake APP_ID. Could you please share the correct one?

UPD1 Please ignore. It does login with APP_ID IOS-88888888-4444-4444-4444-121212121212

UPD2 We should pass host parameter when create a DeviceClient. I changed this part:

const mqttClient = AWSIoTData.device({
    host: amazonData.discovery.mqtt,
    region: AWS.config.region,
    clientId: clientId,
    protocol: 'wss',
    maximumReconnectTimeMs: 8000,
    debug: true,
    accessKeyId: amazonData.credentials.AccessKeyId,
    secretKey: amazonData.credentials.SecretKey,
    sessionToken: amazonData.credentials.SessionToken
  });

@koalazak
Copy link
Owner Author

Nice, are you trying to send commands with mqttClient.publish() ?

@andrey3diq
Copy link

I use AWSIoTData.thingShadow instead of AWSIoTData.device to create the object.
This is my code:

    const mqttClient = AWSIoTData.thingShadow(connectionOptions);
    var clientTokenUpdate;

    mqttClient.on('connect', function (e) {
        console.log('connect!', e);

        mqttClient.register(ROBOT_BLID, {debug: true}, function () {
            var startCmd = {command: {command: 'start'}};
            // this is to reset command
            //var startCmd = null;
            clientTokenUpdate = mqttClient.update(ROBOT_BLID, {'state': {'desired': startCmd}});

            // this is to get the full thing shadow state
            //clientTokenUpdate = mqttClient.get(ROBOT_BLID);
            if (clientTokenUpdate === null) {
                console.log('update shadow failed, operation still in progress');
            }
        });
    });

    mqttClient.on('status',
        function (thingName, stat, clientToken, stateObject) {
            console.log('received ' + stat + ' on ' + thingName + ': ' +
                JSON.stringify(stateObject));

    mqttClient.on('delta',
        function (thingName, stateObject) {
            console.log('received delta on ' + thingName + ': ' +
                JSON.stringify(stateObject));
        });

    mqttClient.on('timeout',
        function (thingName, clientToken) {
            console.log('received timeout on ' + thingName +
                ' with token: ' + clientToken);

When I set the 'desired' state to 'command' it doesn't appear in the shadow state but instead it acts on my device.
It doesn't clear the 'desired' state and I think this is why it's looping constantly trying to set this property.
The same behavior was when I tried to set the boolean property to string value. It keeps this property in 'desired' section and constantly sends the 'delta' events. You can check if you get full state.
I think we should clean the 'desired' command manually. Right after it acts.

@aimproxy
Copy link

aimproxy commented Jan 9, 2020

Hi, I'm implementing ur library in C for an IoT School Project, https://github.com/roombavacuum/libroomba
and when I sent a command to my roomba e5 v3 in local, I got this:

MQTT Message: Topic wifistat, Qos 0, Len 163
Payload (0 - 80): {"state":{"reported":{"netinfo":{"dhcp":true,"addr":3232235870,"mask":4294967040
MQTT Message: Done
MQTT Message: Topic wifistat, Qos 0, Len 68
Payload (0 - 68): {"state":{"reported":{"wifistat":{"wifi":1,"uap":false,"cloud":1}}}}
MQTT Message: Done
MQTT Message: Topic wifistat, Qos 0, Len 163
Payload (0 - 80): {"state":{"reported":{"netinfo":{"dhcp":true,"addr":3232235870,"mask":4294967040
MQTT Message: Done
MQTT Message: Topic wifistat, Qos 0, Len 82
Payload (0 - 80): {"state":{"reported":{"wlcfg":{"sec":7,"ssid":"566F6461666F6E652D304134413632"}}
MQTT Message: Done
MQTT Message: Topic wifistat, Qos 0, Len 50
Payload (0 - 50): {"state":{"reported":{"mac":"d0:c5:d3:bd:21:31"}}}
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 40
Payload (0 - 40): {"state":{"reported":{"country": "PT"}}}
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 43
Payload (0 - 43): {"state":{"reported":{"cloudEnv": "prod"}}}
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 61
Payload (0 - 61): {"state":{"reported":{"svcEndpoints":{"svcDeplId": "v011"}}}}
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 37
Payload (0 - 37): {"state":{"reported":{"name": "e5"}}}
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 470
Payload (0 - 80): {"state":{"reported":{"lastDisconnect":4,"cap":{"ota":1,"eco":1,"svcConf":1},"ba
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 378
Payload (0 - 80): {"state":{"reported":{"bbmssn":{"nMssn":254,"nMssnOK":219,"nMssnF":35,"aMssnM":1
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 475
Payload (0 - 80): {"state":{"reported":{"cleanSchedule":{"cycle":["none","none","none","none","non
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 460
Payload (0 - 80): {"state":{"reported":{"langs":[{"en-US":0},{"en-GB":15},{"fr-FR":1},{"de-DE":2},
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 244
Payload (0 - 80): {"state":{"reported":{"tz":{"ver":7,"events":[{"dt":1564675200,"off":60},{"dt":1
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 294
Payload (0 - 80): {"state":{"reported":{"lastCommand":{
   "command": "start",
   "time": 15785928
MQTT Message: Done
MQTT Message: Topic $aws/things/3178480822035600/shadow/update, Qos 0, Len 201
Payload (0 - 80): {"state":{"reported":{"cleanMissionStatus":{"cycle":"clean","phase":"run","expir
MQTT Message: Done
MQTT Message: Topic wifistat, Qos 0, Len 55
Payload (0 - 55): {"state":{"reported":{"signal":{"rssi":-67,"snr":23}}}}

Is that helpful to ur could implementation?

@koalazak
Copy link
Owner Author

will close this issue and reopen if start with the research again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants