Roomba 980 Wifi Connectivity Reverse engineering

Hi @Elfege_Leylavergne,
First: The ASSET-ID is a http header that axeda cloud needs to work now with your request. Try to send the header with the correct value (ElPaso@irobot!xxxxx where xxxx is the robot blid) in your request. (i dont kwno how to add headers with your python request module, i thing is inside params object).
@vwatson FYI.

Second: dorita980 is useful to control your robot from node.js projects. I use it on a HomeKit Accessory Server (like HAP-NodeJS) to control the robot with Siri for example.
Smartthings is also extending his plugin system to support node.js connectos instead of using python.
Also dorita980 is useful to get your robot password without charles proxy and that MITM atack. And with dorita980 you get your robot IP, for example.

best regards!

Hi @LordLiverpool,
I think the ip is bad (see @jgreco comment about).
I just updated the dorita980 code to support dgram in node 4 (you are using node v4, rigth?).
Try again now updating dorita980 to latest version (2.4.1) in your project, where you has the getRobotIp() code:

npm install dorita980@2.4.1
node yourtest.js

But i recomend to you install node 6, btw.

best regards.

Hi @facu and thank you very much for answering my questions! But I know I’m going to be annoying here, and sound like the kind of guy who takes a long time to understand… but… would you have some first steps to use dorita? Where should I implement / install dorita? I really have no idea where to start… sorry about that, I’m pretty sure I’m missing a little tiny detail here. I only need a starting step… no idea where to go, what to do with dorita980 files.

For what regards the header I figured it should be in params but so far I had no success implementing it. In ST documentation it doesn’t look like I can find anything ressembling to inserting a header and I have no idea if I should use httpGet or Post… No idea at all, and this is because I don’t really (yet) know what I’m talking about. So far I only wrote some simple httpGet using basic api commands but, here, I don’t seem to even have the basic knowledge that would lead me to find out what to do…

I’m a very slow learner… haha.

Elfège

Well… I’m going to read a little about headers, ok? :slight_smile: That should help, actually, just having mentioned the fact that this is a header. I don’t know why, but it’s often like this: need to hear (read) it from someone before it comes to my mind to go look into a specific direction… while I knew it was a header… for dorita, though… gotta look into it more carefully.

@facu I went as far as installing dorita980 and node so npm commands worked so I think I installed properly the packages, I mean that I can do an npm list and see them. After that, the tutorial (found on https://www.npmjs.com/package/dorita980) suddenly shows some code to implement (WHERE???) to send commands. I don’t have the slightest idea of the interface I’m supposed to use with this code? Where should I write this code to so it sends the commands? It doesn’t look like any kind of groovy / smartthings compatible code. Is it for a different platform? And if so, why this page assumes we all know what it should be? If you look at the tuto on how to install dorita on windows it gives a link that leads to a tree of hundreds of folders and subfolders with no explanation whatsoever. This is really weird… So I eventually did some more research and found out that I had to install some stuff and I did it, as described above and now dorita is supposedly installed onto my PC… but I have no idea what to do next… anyway, if you can help, that’s great! :slight_smile: I think I spent too many hours on this now and I should sleep because I’m not being productive at all, as it is obvious here…

Best,
Elfège.

@facu Ok… could send a command through runkit… now I need to find out how to do that from a different platform… I doubt this would work from Arduino, right? Arduino is C and it looks like this is java, right? No idea… lost, lost, lost but slowly getting through! :slight_smile:

Hi,
You were right about the IP, it was 192.168.0.23.
And I’ve now got the password :slight_smile: Thanks for your great help.
Incidentally I can’t use node 6 because I have XP (yeah, I know, but changing is such a pain).

Ok I eventually figured that I just had to create a java file… it was that simple… lol

1 Like

Hi @Elfege_Leylavergne,
dorita980 is a Node.js module. Node.js is across-platform Javascript runtime environment for developing a diverse variety of tools and applications. While JavaScript engines are traditionally run in Web browsers, the Node.js libraries are focused on building server-side applications in JavaScript. And Javascript has nothing to do with Java. Are two different languages (you are creating javascript file to put code inside to use external module (dorita980) to do what you want).
dorita980 itself has nothing to do with SmartThings for now because SmartThings device driver plugin system use python as languaje to code connectors. SmartThings has plans to extend his plugin systen to use node.js modules in near future.
So, today dorita980 is useful with other home automation systems based on node.js like homeBridge or custom home automation projects. Is just a SDK in nodejs to control the robot. You can use it to do what you want.
(and is useful to get your password easly as well)

regards,

I’ve just successfully got my Zipabox home automation to control the Roomba. From that Zipabox you can issue HTTP requests so that’s what I do, having pieced together what’s needed from your posts here and a few things elsewhere. I only used dorita to get the IP and then the user and password.
I imagine many other home automation systems could do something similar.
Many thanks!

Yeah I had to figure that out too. Create a .js file and run it with the node command from a command prompt.

Yes and so far I could not integrate this with smartthings. I know it’s not supposed to be possible but I am looking for a work around by creating a node http server. So far I could access the http server remotely but I can’t get it to execute my js files…

@facu : thanks for the time you took to answer! I really appreciate. I have been working a lot on this now and I can’t get any js file to be run remotely. I tried creating batch files, and they work and run the roomba but now the trick is to have them run remotely.

Maybe a listening script would be the solution: having a script onto the http server listening for incoming data and triggering the js or batch file (containing the node file.js command) accordingly. Any idea ? I tried with http dispatcher but so far I can’t get it to work… because I have no idea of the code I need to write so when a /GET/string command arrives it runs the batch file, which would definitely be a solution allowing to work with smartthings local http requests.

Ok, I’m giving up, for now, on trying to use nodejs files, although it was great to introduce myself to it for I had not wrote some actual javascript in a long, long time… and I’m amazed by how much I forgot, almost everything! Not that I knew a lot in the first place though.

Since it is not yet directly compatible with ST I am still to find a way to send the httpget() command properly. What is MOST frustrating is that it used to work without requesting the asset-id and now I’m lost trying and trying over to send the headers.

I was wondering if someone knew how the local url would look like… I tried to figure this out out of the javascript from dorita980’s local.js but to no avail. I suppose it could look like http://IP:443/. I suppose roomba should have its own http(s) service running since it receives commands from the cloud on port 443, right?

Any bit of indication / help, even apparently useless here will be welcome! :smiley:

Best regards,
Elfège.

Didn’t I see somewhere (maybe here) that the local URL would be:

https://:443/umi

?

it is the case indeed just saw it here and in the local.js of dorita980.

and then the path is the same ? /umi/?blid= etc?

1 Like

I guess so, although I haven’t tried it.
The beer, sadly, will be difficult since I live in Spain :slight_smile:

`

It works!

`

`

 /**
*  Virtual Switch 
*
*  Copyright 2016 Elfege
*
*  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
*  in compliance with the License. You may obtain a copy of the License at:
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
*  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
*  for the specific language governing permissions and limitations under the License.
*
*/
metadata {
    definition (name: "ROOMBA switch", namespace: "Elfege", author: "Elfege") {
        capability "Switch"
        capability "Refresh"     
        command "dock"
        command "resume"
        command "pause"
    }
}
// simulator metadata
simulator {
}
// UI tile definitions
tiles {
    standardTile("CLEAN", "device.switch", width: 1, height: 1, canChangeIcon: false) {
        state "released", label: 'Clean', action: "switch.on", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#0088ff", nextState: "pressed"
        //state "pressed", label: 'Cleaning', action: "refresh.refresh", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#F3F781", nextState: "released"
    }
    standardTile("STOP", "device.switch", width: 1, height: 1, canChangeIcon: false) {
        state "released", label: 'Stop', action: "switch.off", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#0088ff", nextState: "pressed"
        state "pressed", label: 'Stopping', action: "", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#F3F781", nextState: "released"
    }
    standardTile("PAUSE", "device.switch", width: 1, height: 1, canChangeIcon: false) {
        state "released", label: 'pause', action: "pause", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#0088ff" //,  nextState: "pressed"      
        state "pressed", label: 'pausing', action: "", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#F3F781", nextState: "released"
    }
    standardTile("RESUME", "device.switch", width: 1, height: 1, canChangeIcon: false) {
        state "released", label: 'Resume', action: "resume", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#0088ff" //,  nextState: "pressed"      
        state "pressed", label: 'resuming', action: "", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#F3F781", nextState: "released"
    }
    standardTile("DOCK", "device.switch", width: 1, height: 1, canChangeIcon: false) {
        state "released", label: 'Dock', action: "dock", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#0088ff", nextState: "docking" 
        state "pressed", label: 'Docking', action: "", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#F3F781", nextState: "on"
    }
    standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
        state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
    }        
    main "CLEAN"
    details(["CLEAN","STOP","PAUSE", "RESUME", "DOCK", "refresh"])
}
def parse(description) {
    def msg = parseLanMessage(description)
    def headersAsString = msg.header // => headers as a string
    def headerMap = msg.headers      // => headers as a Map
    def body = msg.body              // => request body as a string
    def status = msg.status          // => http status code of the response
    def json = msg.json              // => any JSON included in response body, as a data structure of lists and maps
    def xml = msg.xml                // => any XML included in response body, as a document tree structure
    def data = msg.data              // => either JSON or XML in response body (whichever is specified by content-type header in response)
}
def sendRequest() {
    state.user = "xxxxxxxxxxxxxxxxxxx"
    state.password = "xxxxxxxxxxxxxxxxxxx"
    state.AssetID = "ElPaso@irobot!xxxxxxxxxxxxxxxxxxx" // replace xxx by your robot’s user name 
    state.Authorization = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"  // this is a base64 encoding of the string  "user:" + the rotbot’s password  See http://www.url-encode-decode.com/base64-encode-decode/
// Haven't figured out the local command yet   
// state.deviceNetworkId = "728A8678:1BB"  //  "1921681016:443" hex conversion
    //state.ip = "192.168.10.16:443"
    //state.host =  '${state.ip}:443' /*"https://irobot.axeda.com:443" */
    state.path = "/umi/?blid=${state.user}&robotpwd=${state.password}&method=multipleFieldSet&value=%7B%0A%20%20%22remoteCommand%22%20:%20%22${state.RoombaCmd}%22%0A%7D" 
    def httpRequest = [
        method:"GET",
        uri: "https://irobot.axeda.com/services/v1/rest/Scripto/execute/AspenApiRequest?blid=${state.user}&robotpwd=${state.password}&method=multipleFieldSet&value=%7B%0A%20%20%22remoteCommand%22%20:%20%22${state.RoombaCmd}%22%0A%7D",   
        //strictSSL: false,
        headers:	[
            'User-Agent': 'aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0',
            Accept: '*/*',
            'Accept-Language': 'en-us',
            'ASSET-ID': state.AssetID,            
        ]
    ]
    try {
        httpGet(httpRequest) { resp ->
            resp.headers.each {
                log.debug "${it.name} : ${it.value}"
            }
            log.debug "response contentType: ${resp.contentType}"
            log.debug "response data: ${resp.data}"
        }
    } catch (e) {
        log.error "something went wrong: $e"
    }
}
def dock() {
    sendEvent(name: "switch", value: "on")
    log.debug "Roomba Switch is --------------------------docking"
    state.switch  = "pressed"
    log.debug "Running Roomba. Roomba's ID is $state.ID"
    state.RoombaCmd = "dock" 
    sendRequest() 
}
def on() {
    sendEvent(name: "switch", value: "cleaning")
    log.debug "Roomba Switch is --------------------------on"
    state.switch  = "pressed"
    log.debug "Running Roomba"
    state.RoombaCmd = "start" 
    sendRequest() 
}
def resume() {
    sendEvent(name: "switch", value: "resume")
    log.debug "Roomba Switch is --------------------------resume"
    state.switch  = "pressed"
    log.debug "Running Roomba"

    state.RoombaCmd = "resume" 
    sendRequest() 
}
def off() {
    sendEvent(name: "switch", value: "off")
    log.debug "Roomba Switch is --------------------------on"
    state.switch  = "pressed"
    log.debug "Stopping Roomba"

    state.RoombaCmd = "stop" 
    sendRequest() 
}
def pause() {
    sendEvent(name: "switch", value: "off")
    log.debug "Roomba Switch is --------------------------off"
    state.switch  = "pressed"
    log.debug "Running Roomba"
    state.RoombaCmd = "pause" 
    sendRequest() 
}
indent preformatted text by 4 spaces
2 Likes

Good job. Today my Roomba also successfully cleaned the house under the command of my Zipabox.

Reviving this thread :slight_smile:
Out of curiosity… read through this entire thread, awesome to see you rockstars got it reversed engineered.
I am wondering if anyone got it working with ST hub itself, and what are the options you could do with it after integration?
just got my 980 and was wondering…

It works just fine within Smartthings, it is a very simple protocol.

Can someone explain to me what zipabox is and why we are talking about it on the ST forum?