Roomba 980 Wifi Connectivity Reverse engineering

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?

Thank you for your response Sir . Would you mind (if you have the time) to type out the steps needed to get this integrated in ST? i am a bit confused as to what needs to be done first to get it into ST.
it will be highly appreciated for everyone reading this thread now or in the future.

1 Like

Can someone please, pretty please let us know the steps for integration :slight_smile:

I added my blid and password and set up your device handler, but my roomba does not seem to be receiving any of the commands.

e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug response data: [status:OK, method:multipleFieldSet]
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug response contentType: application/json
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug Connection : keep-alive
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug transfer-encoding : chunked
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug Set-Cookie : prd002.irobot-prd-47873-sg-prd002.irobot-prd-agents=DPCJDCAKJCBP; Path=/; Secure
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug Server : Microsoft-IIS/5.0
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug Date : Sun, 06 Nov 2016 05:14:01 GMT
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug Content-Type : application/json;charset=utf-8
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug Stopping Roomba
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:14:01 AM: debug Roomba Switch is --------------------------on
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug response data: [status:OK, method:multipleFieldSet]
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug response contentType: application/json
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug Connection : keep-alive
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug transfer-encoding : chunked
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug Set-Cookie : prd002.irobot-prd-47873-sg-prd002.irobot-prd-agents=DNCJDCAKJCBP; Path=/; Secure
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug Server : Microsoft-IIS/5.0
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug Date : Sun, 06 Nov 2016 05:13:48 GMT
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug Content-Type : application/json;charset=utf-8
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug Running Roomba
e71ebcbc-582e-4871-9446-d3e4ed3ecdf4 1:13:48 AM: debug Roomba Switch is --------------------------on

Did you do the base64 conversion?

Yeah is it literally “user:[password]” in 64? I might have used [user]:[password]

Changed to the latter and after resetting the Roomba it is working now! Thanks!

yes, literally. Do not add your actual user name in this string.

@M.a.S.e
I posted a device handler code which allows you to controll the roomba directly through ST hub.

Thank you all the guidance in this thread. Got it to work.

I got my blid/pass with dorita980 and can get the roomba to work through the simulator in the IDE. How do I get the device to show up in my phone app? I published the device handler.

EDIT: Nevermind. New to SmartThings here. Had to add the device. Thanks everybody for all the hard work.

Hi all,

here is another simple way to get stuff running without node.js and all the other things (although this is a grrrreeeeaaaat reference!). Use a browser plugin, like e.g. HttpRequester, and the send the commands to your Roomba 980:

1. Get the password:
(Replace 192.168.10.135 with your robots IP address)
(Place the robot on the home base and press the HOME button for about 2 seconds until a series of tones is played and the WIFI light flashes, then hurry to send the POST command)

POST https://192.168.10.135/umi
Content-Type: application/json
Connection: close
User-Agent: aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0
Content-Encoding: identity
Accept: */*
Accept-Language: en-us
Host: 192.168.10.135
content-length: 37

{"do":"get","args":["passwd"],"id":1}

– Response: –

200 OK
Server:  Marvell-WM
Connection:  close
Transfer-Encoding:  chunked
Content-Type:  application/json

{"ok":{"passwd":"a1bcdEF23GhIj4KL"},"id":1}

2. Get the username/blid:
(Replace 192.168.10.135 with your robots IP address)
(For the Authorization header, you must Base64 encode the string “user:”+ThePasswordReceivedFromStep1, e.g. Base64(user:a1bcdEF23GhIj4KL) -> dXNlcjphMWJjZEVGMjNHaElqNEtM)
(The decimal “blid” values in the response must be hex encoded to receive the username: 43,6,75,31,32,127,12,132 -> 2B064B1F207F0C84)

POST https://192.168.10.135/umi
Content-Type: application/json
Connection: close
User-Agent: aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0
Content-Encoding: identity
Accept: */*
Accept-Language: en-us
Host: 192.168.10.135
content-length: 34
Authorization: Basic dXNlcjphMWJjZEVGMjNHaElqNEtM

{"do":"get","args":["sys"],"id":2}

– Response: –

200 OK
Server:  Marvell-WM
Connection:  close
Transfer-Encoding:  chunked
Content-Type:  application/json

{"ok":{"umi":2,"pid":2,"blid":[43,6,75,31,32,127,12,132],"sw":"v1.2.9","cfg":0,"boot":4042,"main":4313,"wifi":517,"nav":"01.08.04","ui":2996,"audio":32,"bat":"lith"},"id":2}

3. Start the robots clean job:
(Replace 192.168.10.135 with your robots IP address)
(See comment of Step 2 for the Authorization header)

POST https://192.168.10.135/umi
Content-Type: application/json
Connection: close
User-Agent: aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0
Content-Encoding: identity
Accept: */*
Accept-Language: en-us
Host: 192.168.10.135
content-length: 49
Authorization: Basic dXNlcjphMWJjZEVGMjNHaElqNEtM

{"do":"set","args":["cmd" {"op":"start"}],"id":3}

– Response: –

200 OK
Server:  Marvell-WM
Connection:  close
Transfer-Encoding:  chunked
Content-Type:  application/json

{"ok":null,"id":3}

4. Pause the robots clean job:
(Replace 192.168.10.135 with your robots IP address)
(See comment of Step 2 for the Authorization header)

POST https://192.168.10.135/umi
Content-Type: application/json
Connection: close
User-Agent: aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0
Content-Encoding: identity
Accept: */*
Accept-Language: en-us
Host: 192.168.10.135
content-length: 49
Authorization: Basic dXNlcjphMWJjZEVGMjNHaElqNEtM

{"do":"set","args":["cmd" {"op":"pause"}],"id":4}

– Response: –

200 OK
Server:  Marvell-WM
Connection:  close
Transfer-Encoding:  chunked
Content-Type:  application/json

{"ok":null,"id":4}

5. Resume the robots clean job:
(Replace 192.168.10.135 with your robots IP address)
(See comment of Step 2 for the Authorization header)

POST https://192.168.10.135/umi
Content-Type: application/json
Connection: close
User-Agent: aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0
Content-Encoding: identity
Accept: */*
Accept-Language: en-us
Host: 192.168.10.135
content-length: 50
Authorization: Basic dXNlcjphMWJjZEVGMjNHaElqNEtM

{"do":"set","args":["cmd" {"op":"resume"}],"id":5}

– Response: –

200 OK
Server:  Marvell-WM
Connection:  close
Transfer-Encoding:  chunked
Content-Type:  application/json

{"ok":null,"id":5}

6. Stop the robots clean job:
(Replace 192.168.10.135 with your robots IP address)
(See comment of Step 2 for the Authorization header)

POST https://192.168.10.135/umi
Content-Type: application/json
Connection: close
User-Agent: aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0
Content-Encoding: identity
Accept: */*
Accept-Language: en-us
Host: 192.168.10.135
content-length: 48
Authorization: Basic dXNlcjphMWJjZEVGMjNHaElqNEtM

{"do":"set","args":["cmd" {"op":"stop"}],"id":6}

– Response: –

200 OK
Server:  Marvell-WM
Connection:  close
Transfer-Encoding:  chunked
Content-Type:  application/json

{"ok":null,"id":6}

7. Force the robot to return back to its home base:
(Replace 192.168.10.135 with your robots IP address)
(See comment of Step 2 for the Authorization header)

POST https://192.168.10.135/umi
Content-Type: application/json
Connection: close
User-Agent: aspen%20production/2618 CFNetwork/758.3.15 Darwin/15.4.0
Content-Encoding: identity
Accept: */*
Accept-Language: en-us
Host: 192.168.10.135
content-length: 48
Authorization: Basic dXNlcjphMWJjZEVGMjNHaElqNEtM

{"do":"set","args":["cmd" {"op":"dock"}],"id":7}

– Response: –

200 OK
Server:  Marvell-WM
Connection:  close
Transfer-Encoding:  chunked
Content-Type:  application/json

{"ok":null,"id":7}
4 Likes