├── .gitignore ├── .ruby-gemset ├── .ruby-version ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── demo ├── css │ ├── .sass-cache │ │ └── 3f9888ef66cd5da2b05357a551656ddf59ea6d79 │ │ │ └── style.scssc │ ├── css │ ├── reset.css │ ├── style.css │ ├── style.css.map │ ├── style.scss │ └── unsemantic-grid-responsive.css ├── img │ ├── login.png │ ├── robot_empty.png │ ├── robot_image_botvacconnected.png │ ├── robot_image_botvacd3connected.png │ ├── robot_image_botvacd5connected.png │ └── robot_image_botvacd7connected.png ├── index.html └── neato-demo-app.js ├── jasmine.yml ├── lib ├── .gitkeep ├── neato-0.10.0.min.js └── neato-0.9.0.min.js ├── spec ├── robot-services │ ├── cleaning │ │ ├── cleaningSpec.js │ │ ├── houseCleaning │ │ │ ├── houseCleaningBasic1Spec.js │ │ │ ├── houseCleaningBasic2Spec.js │ │ │ └── houseCleaningMinimal2Spec.js │ │ └── spotCleaning │ │ │ ├── spotCleaningBasic1Spec.js │ │ │ ├── spotCleaningBasic2Spec.js │ │ │ ├── spotCleaningMicro2Spec.js │ │ │ └── spotCleaningMinimal2Spec.js │ ├── findMe │ │ └── findMeBasic1Spec.js │ ├── generalInfo │ │ ├── generalInfoAdvanced1Spec.js │ │ └── generalInfoBasic1Spec.js │ ├── localStats │ │ └── localStatsAdvanced1Spec.js │ ├── maps │ │ └── mapsBasic1Spec.js │ ├── preferences │ │ ├── preferencesAdvanced1Spec.js │ │ └── preferencesBasic1Spec.js │ ├── schedule │ │ ├── scheduleBasic1Spec.js │ │ └── scheduleMinimal1Spec.js │ └── wifis │ │ └── wifiBasic1Spec.js ├── robotSpec.js ├── support │ ├── mock-ajax.js │ ├── specHelper.js │ └── vendor │ │ ├── hmac-sha256-3.1.2.min.js │ │ └── jquery-2.2.0.min.js ├── userSpec.js └── utilsSpec.js └── src ├── application.js ├── neato.js ├── robot-services ├── cleaning │ ├── cleaning.js │ ├── houseCleaning │ │ ├── houseCleaningBasic1.js │ │ ├── houseCleaningBasic2.js │ │ ├── houseCleaningBasic3.js │ │ ├── houseCleaningBasic4.js │ │ └── houseCleaningMinimal2.js │ └── spotCleaning │ │ ├── spotCleaningBasic1.js │ │ ├── spotCleaningBasic2.js │ │ ├── spotCleaningBasic3.js │ │ ├── spotCleaningMicro2.js │ │ └── spotCleaningMinimal2.js ├── findMe │ └── findMeBasic1.js ├── generalInfo │ ├── generalInfoAdvanced1.js │ └── generalInfoBasic1.js ├── localStats │ └── localStatsAdvanced1.js ├── maps │ ├── mapsAdvanced1.js │ ├── mapsBasic1.js │ ├── mapsBasic2.js │ └── mapsMacro1.js ├── preferences │ ├── preferencesAdvanced1.js │ └── preferencesBasic1.js ├── schedule │ ├── scheduleBasic1.js │ ├── scheduleBasic2.js │ └── scheduleMinimal1.js └── wifis │ └── wifiBasic1.js ├── robot.js ├── user.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /.bundle 3 | 4 | # vim 5 | .*.sw[a-z] 6 | *.un~ 7 | Session.vim 8 | 9 | # mine 10 | .idea 11 | 12 | # OSX ignores 13 | .DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | Icon 17 | 18 | ._* 19 | .Spotlight-V100 20 | .Trashes 21 | .AppleDB 22 | .AppleDesktop 23 | Network Trash Folder 24 | Temporary Items 25 | .apdisk 26 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | neato-sdk-js 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.3.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.1 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | install: bundle 10 | 11 | script: "bundle exec rake" 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jasmine' 4 | gem 'uglifier' 5 | gem 'sprockets', '~> 2.12.5' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | execjs (2.7.0) 5 | hike (1.2.3) 6 | jasmine (3.3.0) 7 | jasmine-core (~> 3.3.0) 8 | phantomjs 9 | rack (>= 1.2.1) 10 | rake 11 | jasmine-core (3.3.0) 12 | multi_json (1.13.1) 13 | phantomjs (2.1.1.0) 14 | rack (1.6.11) 15 | rake (12.3.2) 16 | sprockets (2.12.5) 17 | hike (~> 1.2) 18 | multi_json (~> 1.0) 19 | rack (~> 1.0) 20 | tilt (~> 1.1, != 1.3.0) 21 | tilt (1.4.1) 22 | uglifier (4.1.20) 23 | execjs (>= 0.3.0, < 3) 24 | 25 | PLATFORMS 26 | ruby 27 | 28 | DEPENDENCIES 29 | jasmine 30 | sprockets (~> 2.12.5) 31 | uglifier 32 | 33 | BUNDLED WITH 34 | 1.16.2 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017, Neato Robotics Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/NeatoRobotics/neato-sdk-js.svg?branch=master)](https://travis-ci.org/NeatoRobotics/neato-sdk-js) 2 | 3 | # Neato SDK - JS 4 | 5 | This is the [Neato Developer Network's](http://developers.neatorobotics.com) official JavaScript SDK (Beta release). 6 | The Neato JavaScript SDK enables easy interaction with Neato servers and robots via Ajax calls. 7 | 8 | To boost your development, you can also check the *sample application*. 9 | 10 | > This is a beta version. It is subject to change without prior notice. 11 | 12 | ## Preconditions 13 | 14 | - Create the Neato user account via the Neato portal or from the official Neato App 15 | - Link the robot to the user account via the official Neato App 16 | 17 | ## Demo 18 | An example app can be found in the [demo](https://github.com/NeatoRobotics/neato-sdk-js/tree/master/demo) folder. 19 | 20 | For your convenience, an online demo can be seen on the [Neato Developer Network](https://developers.neatorobotics.com) at the address: 21 | [https://developers.neatorobotics.com/demo/sdk-js](https://developers.neatorobotics.com/demo/sdk-js) 22 | 23 | ## Setup 24 | In order to use the Neato SDK JS simply import the *jQuery* and *hmac-sha256* dependencies and the *neato-x.y.z.min.js* file: 25 | 26 | ``` xml 27 | 28 | 29 | 30 | ``` 31 | 32 | ## Usage 33 | The Neato SDK has 3 main roles: 34 | 35 | 1. Handling OAuth authentications 36 | 2. Simplifying users info interactions 37 | 3. Managing communication with Robots 38 | 39 | These tasks are handled by two classes: `Neato.User` and `Neato.Robot`. 40 | 41 | ### Authentication 42 | The Neato SDK leverages on OAuth 2 to perform user authentication. 43 | 44 | #### 1. Retrieve your client_id, scopes and redirect_url 45 | Before start please retrieve your *client_id*, *scopes* and *redirect_uri* you entered during the creation of your app on the Neato Developer Portal. These values must match in order to authenticate the user. 46 | 47 | 48 | #### 2. Start the authentication flow 49 | To start the authentication flow simply invoke the *login()* method on the user object passing the above data: 50 | 51 | ```javascript 52 | var user = new Neato.User(); 53 | user.login({ 54 | clientId: "your_app_client_id", 55 | scopes: "control_robots+email+maps", 56 | redirectUrl: "your_redirect_uri" 57 | }); 58 | ``` 59 | 60 | The SDK will start the authentication flow navigating to the Neato authentication page where the user inserts his Neato account credentials and accept your app to handle your Neato data. If the authentication is successful, you will be redirected to the redirect uri page and an *access_token* parameters will be passed into the url. 61 | 62 | #### 3. Check if the user is now connected 63 | 64 | There's no need to parse the token yourself, the SDK handles it for you. You only have to check if the user is now connected to the Neato server or not: 65 | 66 | ```javascript 67 | var user = new Neato.User(); 68 | user.isConnected() 69 | .done(function () { 70 | // ok you can retrieve your robot list now 71 | }).fail(function () { 72 | // authentication failure or user not yet logged in 73 | }); 74 | ``` 75 | 76 | In order to understand why the *isConnected()* method returns failure, you can use these methods on the user object: 77 | 78 | ```javascript 79 | if(user.authenticationError()) { 80 | // OAuth authentication failed/denied 81 | } else if(!user.connected && user.token != null) { 82 | // invalid / expired token 83 | } else { 84 | // nothing to do here, show the login form to the user 85 | } 86 | ``` 87 | 88 | ### Working with Users 89 | Once the user is authenticated you can retrieve user information using the `NeatoUser` class: 90 | 91 | ```javascript 92 | user.getUserInfo() 93 | .done(function (data) { 94 | var email = data.email || ""; 95 | }) 96 | .fail(function (data) { 97 | // server call error 98 | }); 99 | ``` 100 | #### Get user robots 101 | To get the user robot list you can do this: 102 | 103 | ```javascript 104 | user.getRobots() 105 | .done(function (robots) { 106 | // now user.robots contains the NeatoRobots array 107 | }) 108 | .fail(function (data) { 109 | // server call error 110 | }); 111 | ``` 112 | 113 | *NeatoRobot* is a special class we have developed for you that can be used to directly invoke commands on the robot. 114 | 115 | To count the robots do this: 116 | 117 | ``` javascript 118 | var count = user.robots.length; 119 | ``` 120 | 121 | To retrieve a specific robot by serial number do this: 122 | 123 | ``` javascript 124 | var robot = user.getRobotBySerial(serial); 125 | ``` 126 | 127 | ### Communicating with Robots 128 | Now that you have the robots for an authenticated user it’s time to communicate with them. 129 | In the previous call you've seen how easy it is to retrieve `NeatoRobot` instances for your current user. Those instances are ready to receive messages from your app (if the robots are online obviously). 130 | 131 | #### The robot status 132 | Before, we saw how to retrieve the robot list from the `NeatoUser` class. It is best practice to check the robot state before sending commands, otherwise the robot may be in a state that cannot accept the command and return an error code. To update/get the robot state do this: 133 | 134 | ```javascript 135 | robot.connect(); 136 | ``` 137 | 138 | This method starts polling for the robot state every X seconds and invoke the *onStateChanged()* if a state change occurs. 139 | 140 | So, if you are interested in these events you can do this: 141 | 142 | ``` javascript 143 | robot.onConnected = function () { 144 | console.log(robot.serial + " got connected"); 145 | }; 146 | robot.onDisconnected = function (status, json) { 147 | console.log(robot.serial + " got disconnected"); 148 | }; 149 | robot.onStateChange = function () { 150 | console.log(robot.serial + " got new state:", robot.state); 151 | }; 152 | robot.connect(); 153 | ``` 154 | 155 | #### Sending commands to a Robot 156 | An online robot is ready to receive your commands like `startCleaning`. Some commands require parameters while others don't, see the API doc for details. 157 | 158 | Pause cleaning doesn't require parameters: 159 | 160 | ```javascript 161 | robot.pauseCleaning(); 162 | ``` 163 | 164 | Get general info doesn't require parameters: 165 | 166 | ```javascript 167 | robot.generalInfo(); 168 | ``` 169 | 170 | Start cleaning requires parameters like the cleaning mode (eco or turbo) and, in case of spot cleaning, the spot cleaning parameters (large or small area, 1x or 2x). 171 | 172 | ```javascript 173 | robot.startHouseCleaning({ 174 | mode: Neato.Constants.TURBO_MODE, 175 | modifier: Neato.Constants.HOUSE_FREQUENCY_NORMAL, 176 | navigationMode: Neato.Constants.EXTRA_CARE_OFF 177 | }); 178 | ``` 179 | 180 | #### Working with Robot schedule 181 | To enable or disable all the schedule: 182 | 183 | ```javascript 184 | robot.enableSchedule(); 185 | robot.disableSchedule(); 186 | ``` 187 | 188 | To schedule a robot clean every Monday at 3:00pm: 189 | 190 | ```javascript 191 | robot.setSchedule({ 192 | 1: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" } 193 | }); 194 | ``` 195 | 196 | To schedule a robot clean everyday at 3:00pm: 197 | 198 | ```javascript 199 | robot.setSchedule({ 200 | 0: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" }, 201 | 1: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" }, 202 | 2: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" }, 203 | 3: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" }, 204 | 4: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" }, 205 | 5: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" }, 206 | 6: { mode: Neato.Constants.TURBO_MODE, startTime: "15:00" } 207 | }); 208 | ``` 209 | 210 | *Note: not all robot models support the eco/turbo cleaning mode. You should check the robot available services before sending those parameters.* 211 | 212 | #### Getting robot coverage maps 213 | 214 | To retrieve the list of robot cleaning coverage maps: 215 | 216 | ```javascript 217 | robot.maps().done(function (data) { 218 | if(data["maps"] && data["maps"].length > 0) { 219 | // since map image urls expire, you need to use the map id 220 | // to retrieve a fresh map url 221 | var mapId = data["maps"][0]["id"]; 222 | robot.mapDetails(mapId).done(function (data) { 223 | // show the map image 224 | window.open(data["url"]); 225 | }).fail(function (data) { 226 | // something went wrong getting map details... 227 | }); 228 | }else { 229 | // No maps available yet. Complete at least one house cleaning to view maps. 230 | } 231 | }).fail(function (data) { 232 | // something went wrong getting robots map... 233 | }); 234 | ``` 235 | The code above retrieves the list of all the available maps and, if exists, shows the first one. 236 | 237 | *Note: before trying to use this call please ensure the robot supports the "maps" service.* 238 | 239 | #### Checking robot available services 240 | 241 | Different robot models and versions have different features. So before sending commands to the robot you should check if that command is available on the robot. Otherwise the robot will respond with an error. You can check the available services on the robot: 242 | 243 | ```javascript 244 | var availableServices = robot.state.availableServices; 245 | ``` 246 | 247 | In addition there are some utility methods you can use to check if the robot supports the services. 248 | 249 | ```javascript 250 | if(availableServices["findMe"]) { 251 | //robot has the findMe service 252 | } 253 | ``` 254 | The SDK does a big work to always send to the robot acceptable parameters checking its supported service. So, for simplicity, you can send commands to the robot without any parameters and the SDK fills the call with acceptable ones (where this has sense). 255 | 256 | ```javascript 257 | robot.startHouseCleaning(); 258 | ``` 259 | 260 | The above call send to the robot default parameters and ensure required parameters are filled and not supported parameters are discarded. 261 | The supported services methods are automatically injected into the robot instance after the robot state is retrieved and the good part is that only the correct version of the method is injected. 262 | 263 | #### Use supported services to build your UI 264 | 265 | Supported robot services should be used to build your UI, you should hide unavailable services features and show supported ones. This is particularly true in the robot command page. Here you can benefit of some useful method of the robot class. 266 | 267 | ```javascript 268 | robot.supportEcoTurboMode(); 269 | robot.supportFrequency(); 270 | robot.supportExtraCare(); 271 | robot.supportArea(); 272 | ``` 273 | 274 | Since there are different version of cleaning service you can use this method to build your UI. 275 | 276 | ## Dependencies 277 | 278 | * [jQuery](https://jquery.com/) (> 2.2.0) 279 | * [hmac-sha256.js](https://code.google.com/p/crypto-js/) 280 | 281 | ## Development 282 | Install Ruby and bundle the gems. 283 | 284 | ``` 285 | $ bundle install 286 | ``` 287 | 288 | ### TDD 289 | Start [Jasmine](http://jasmine.github.io/) with: 290 | 291 | ```bash 292 | $ rake jasmine 293 | ``` 294 | 295 | Point your browser to [http://localhost:8888](http://localhost:8888) to see the test results. 296 | 297 | ### CI 298 | To run tests: 299 | 300 | ```bash 301 | $ rake 302 | ``` 303 | 304 | ## Building 305 | To build the minified version of the library: 306 | 307 | ```bash 308 | $ rake build 309 | ``` 310 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'jasmine' 2 | require 'sprockets' 3 | load 'jasmine/tasks/jasmine.rake' 4 | 5 | # default 6 | task :default => ["jasmine:ci"] 7 | 8 | # settings 9 | VERSION = "0.10.0" 10 | ROOT = File.dirname(__FILE__) 11 | SOURCE_DIR = "src" 12 | BUILD_DIR = "lib" 13 | SOURCE_JS = "application.js" 14 | BUILD_JS = "neato-#{VERSION}.min.js" 15 | 16 | # env 17 | ENV['JASMINE_CONFIG_PATH'] = File.join(ROOT, "jasmine.yml") 18 | 19 | # tasks 20 | desc "Build library" 21 | task :build do 22 | # init environment 23 | environment = Sprockets::Environment.new(ROOT) 24 | environment.js_compressor = :uglify 25 | environment.append_path SOURCE_DIR 26 | 27 | # get asset content 28 | asset = environment.find_asset(SOURCE_JS) 29 | javascript_content = asset.to_s 30 | 31 | # add copyright notice 32 | javascript_content = %Q(/** 33 | * Neato SDK Javascript 34 | * https://github.com/NeatoRobotics/neato-sdk-js 35 | * 36 | * Copyright (c)2016 Neato Robotics, Inc. 37 | * Author: Roberto Ostinelli 38 | * 39 | * Version: #{VERSION} 40 | */ 41 | ) + javascript_content + "\n" 42 | 43 | # write to file 44 | File.open(File.join(ROOT, BUILD_DIR, BUILD_JS), 'w') { |file| file.write(javascript_content) } 45 | end 46 | -------------------------------------------------------------------------------- /demo/css/.sass-cache/3f9888ef66cd5da2b05357a551656ddf59ea6d79/style.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/demo/css/.sass-cache/3f9888ef66cd5da2b05357a551656ddf59ea6d79/style.scssc -------------------------------------------------------------------------------- /demo/css/css: -------------------------------------------------------------------------------- 1 | /* 2 | Errno::ENOENT: No such file or directory - scss 3 | 4 | Backtrace: 5 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/plugin/compiler.rb:484:in `read' 6 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/plugin/compiler.rb:484:in `update_stylesheet' 7 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/plugin/compiler.rb:215:in `block in update_stylesheets' 8 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/plugin/compiler.rb:209:in `each' 9 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/plugin/compiler.rb:209:in `update_stylesheets' 10 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/plugin/compiler.rb:294:in `watch' 11 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/plugin.rb:109:in `method_missing' 12 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/exec/sass_scss.rb:360:in `watch_or_update' 13 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/exec/sass_scss.rb:51:in `process_result' 14 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/exec/base.rb:52:in `parse' 15 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/lib/sass/exec/base.rb:19:in `parse!' 16 | /Library/Ruby/Gems/2.0.0/gems/sass-3.4.22/bin/sass:13:in `' 17 | /usr/local/bin/sass:23:in `load' 18 | /usr/local/bin/sass:23:in `
' 19 | */ 20 | body:before { 21 | white-space: pre; 22 | font-family: monospace; 23 | content: "Errno::ENOENT: No such file or directory - scss"; } 24 | -------------------------------------------------------------------------------- /demo/css/reset.css: -------------------------------------------------------------------------------- 1 | a, 2 | abbr, 3 | acronym, 4 | address, 5 | applet, 6 | article, 7 | aside, 8 | audio, 9 | b, 10 | big, 11 | blockquote, 12 | body, 13 | canvas, 14 | caption, 15 | center, 16 | cite, 17 | code, 18 | dd, 19 | del, 20 | details, 21 | dfn, 22 | dialog, 23 | div, 24 | dl, 25 | dt, 26 | em, 27 | embed, 28 | fieldset, 29 | figcaption, 30 | figure, 31 | font, 32 | footer, 33 | form, 34 | h1, 35 | h2, 36 | h3, 37 | h4, 38 | h5, 39 | h6, 40 | header, 41 | hgroup, 42 | hr, 43 | html, 44 | i, 45 | iframe, 46 | img, 47 | ins, 48 | kbd, 49 | label, 50 | legend, 51 | li, 52 | main, 53 | mark, 54 | menu, 55 | meter, 56 | nav, 57 | object, 58 | ol, 59 | output, 60 | p, 61 | pre, 62 | progress, 63 | q, 64 | rp, 65 | rt, 66 | ruby, 67 | s, 68 | samp, 69 | section, 70 | small, 71 | span, 72 | strike, 73 | strong, 74 | sub, 75 | summary, 76 | sup, 77 | table, 78 | tbody, 79 | td, 80 | tfoot, 81 | th, 82 | thead, 83 | time, 84 | tr, 85 | tt, 86 | u, 87 | ul, 88 | var, 89 | video, 90 | xmp { 91 | border: 0; 92 | margin: 0; 93 | padding: 0; 94 | font-size: 100%; 95 | } 96 | 97 | html, 98 | body { 99 | height: 100%; 100 | } 101 | 102 | article, 103 | aside, 104 | details, 105 | figcaption, 106 | figure, 107 | footer, 108 | header, 109 | hgroup, 110 | main, 111 | menu, 112 | nav, 113 | section { 114 | display: block; 115 | } 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | img { 123 | color: transparent; 124 | font-size: 0; 125 | vertical-align: middle; 126 | -ms-interpolation-mode: bicubic; 127 | } 128 | 129 | ul, 130 | ol { 131 | list-style: none; 132 | } 133 | 134 | li { 135 | display: list-item; 136 | } 137 | 138 | table { 139 | border-collapse: collapse; 140 | border-spacing: 0; 141 | } 142 | 143 | th, 144 | td, 145 | caption { 146 | font-weight: normal; 147 | vertical-align: top; 148 | text-align: left; 149 | } 150 | 151 | q { 152 | quotes: none; 153 | } 154 | 155 | q:before, 156 | q:after { 157 | content: ""; 158 | content: none; 159 | } 160 | 161 | sub, 162 | sup, 163 | small { 164 | font-size: 75%; 165 | } 166 | 167 | sub, 168 | sup { 169 | line-height: 0; 170 | position: relative; 171 | vertical-align: baseline; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | sup { 179 | top: -0.5em; 180 | } 181 | 182 | svg { 183 | overflow: hidden; 184 | } 185 | -------------------------------------------------------------------------------- /demo/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Helvetica, sans-serif; 3 | color: #2B3135; } 4 | 5 | a { 6 | cursor: pointer; 7 | cursor: hand; } 8 | 9 | body { 10 | background-color: #fff; } 11 | 12 | h1 { 13 | text-align: center; 14 | text-transform: uppercase; 15 | font-size: 2em; 16 | font-weight: 100; 17 | background-color: #2B3135; 18 | color: #fff; 19 | padding: 30px 0; } 20 | 21 | h4 { 22 | text-align: center; 23 | text-transform: lowercase; 24 | font-size: 1em; 25 | font-weight: lighter; 26 | color: #2B3135; 27 | padding: 10px 0; 28 | height: 20px; } 29 | 30 | h2 { 31 | color: #555; 32 | font-weight: 100; 33 | text-transform: uppercase; 34 | font-size: 1.8em; 35 | text-align: center; 36 | margin: 30px; } 37 | 38 | .ir { 39 | display: block; 40 | background-color: transparent; 41 | border: 0; 42 | overflow: hidden; 43 | text-indent: -9999px; } 44 | 45 | .wrapword { 46 | white-space: -moz-pre-wrap !important; 47 | /* Mozilla, since 1999 */ 48 | white-space: -pre-wrap; 49 | /* Opera 4-6 */ 50 | white-space: -o-pre-wrap; 51 | /* Opera 7 */ 52 | white-space: pre-wrap; 53 | /* css-3 */ 54 | word-wrap: break-word; 55 | /* Internet Explorer 5.5+ */ 56 | white-space: -webkit-pre-wrap; 57 | /* Newer versions of Chrome/Safari*/ 58 | word-break: break-all; 59 | white-space: normal; } 60 | 61 | .hidden { 62 | visibility: hidden; } 63 | 64 | .error { 65 | margin: 30px auto; 66 | width: 250px; 67 | background-color: #ffe6e6; 68 | border: 2px solid #ff4d4d; 69 | border-radius: 10px; 70 | font-size: 0.8em; 71 | color: #ff4d4d; 72 | padding: 5px 0; 73 | text-align: center; } 74 | 75 | a.btn { 76 | display: inline-block; 77 | font-size: 12px; 78 | background-color: rgba(0, 0, 0, 0.5); 79 | color: #fff; 80 | border-radius: 10px; 81 | padding: 10px; 82 | margin: 5px; 83 | text-decoration: none; } 84 | a.btn:hover { 85 | opacity: 0.7; } 86 | 87 | #signin { 88 | margin: 80px 0; } 89 | #signin a { 90 | display: block; 91 | margin: 0 auto; 92 | width: 150px; 93 | height: 60px; 94 | background-image: url("../img/login.png"); 95 | background-position: 0 0; 96 | background-repeat: no-repeat; 97 | background-size: 150px 60px; } 98 | #signin a:hover { 99 | opacity: 0.3; } 100 | 101 | #dashboard .robot { 102 | margin-top: 30px; 103 | margin-bottom: 30px; 104 | padding: 20px 0 0 0; 105 | border: 1px solid #cdcdcd; 106 | border-radius: 20px; 107 | overflow: hidden; 108 | position: relative; } 109 | #dashboard .robot .robot_state { 110 | text-align: center; 111 | font-size: small; 112 | color: #555555; } 113 | #dashboard .robot .cmd_find_me { 114 | position: absolute; 115 | right: 20px; 116 | top: 20px; 117 | background-color: #2B3135; 118 | padding: 20px; 119 | border-radius: 50px; } 120 | #dashboard .robot .cmd_find_me i { 121 | color: #fff; } 122 | #dashboard .robot .cmd_find_me:hover { 123 | opacity: 0.5; } 124 | #dashboard .robot .cmd_maps { 125 | position: absolute; 126 | left: 20px; 127 | top: 20px; 128 | background-color: #2B3135; 129 | padding: 20px; 130 | border-radius: 50px; } 131 | #dashboard .robot .cmd_maps i { 132 | color: #fff; } 133 | #dashboard .robot .cmd_maps:hover { 134 | opacity: 0.5; } 135 | #dashboard .robot .model { 136 | text-align: center; } 137 | #dashboard .robot .name { 138 | text-align: center; 139 | color: #555; 140 | font-size: 20px; 141 | font-weight: 100; } 142 | #dashboard .robot .cleaning-commands { 143 | margin: 10px auto; 144 | text-align: center; } 145 | #dashboard .robot .cleaning-commands a { 146 | display: inline-block; 147 | width: 20px; 148 | height: 20px; 149 | margin-right: 5px; 150 | border-radius: 10px; 151 | wdth: 50px; 152 | text-align: center; 153 | background-color: #2B3135; 154 | padding: 10px 10px; 155 | color: #fff; } 156 | #dashboard .robot .cleaning-commands a:hover { 157 | opacity: 0.5; } 158 | #dashboard .robot .cleaning-commands a.disabled { 159 | cursor: default; 160 | opacity: 0.2; } 161 | #dashboard .robot .cleaning-commands a.disabled:hover { 162 | background-color: rgba(0, 0, 0, 0.8); 163 | background-color: rgba(0, 0, 0, 0.5); } 164 | #dashboard .robot .cleaning-commands a i { 165 | color: #fff; } 166 | #dashboard .robot .other-commands { 167 | height: 60px; 168 | padding: 20px; 169 | margin-top: 30px; 170 | background-color: #2B3135; 171 | text-align: center; } 172 | #dashboard .robot .other-commands p { 173 | font-size: 0.8em; 174 | font-weight: 100; 175 | color: #fff; 176 | text-transform: uppercase; 177 | text-align: center; 178 | margin-bottom: 10px; } 179 | #dashboard table { 180 | margin: 30px 0; } 181 | #dashboard table tr.robot { 182 | border: 2px solid #fff; 183 | background-color: #2B3135; } 184 | #dashboard table tr.robot:hover { 185 | background-color: #004B7C; } 186 | #dashboard table th { 187 | color: #555; 188 | font-weight: 200; } 189 | #dashboard table td { 190 | padding: 20px 0; 191 | color: #fff; 192 | vertical-align: middle; } 193 | #dashboard table td a { 194 | color: #fff; 195 | text-decoration: none; 196 | text-transform: uppercase; } 197 | #dashboard table td.model { 198 | text-align: center; 199 | width: 20%; } 200 | #dashboard table td.model p { 201 | padding-top: 20px; 202 | color: #fff; 203 | font-size: 1em; 204 | font-weight: 100; 205 | text-transform: uppercase; } 206 | #dashboard table td.commands a { 207 | float: left; 208 | display: block; 209 | width: 20px; 210 | height: 20px; 211 | margin-right: 5px; 212 | border-radius: 10px; 213 | wdth: 50px; 214 | text-align: center; 215 | background-color: rgba(0, 0, 0, 0.5); 216 | padding: 10px 10px; } 217 | #dashboard table td.commands a:hover { 218 | background-color: rgba(0, 0, 0, 0.8); } 219 | #dashboard table td.commands a.disabled { 220 | cursor: default; 221 | opacity: 0.2; } 222 | #dashboard table td.commands a.disabled:hover { 223 | background-color: rgba(0, 0, 0, 0.8); 224 | background-color: rgba(0, 0, 0, 0.5); } 225 | 226 | /*# sourceMappingURL=style.css.map */ 227 | -------------------------------------------------------------------------------- /demo/css/style.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "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", 4 | "sources": ["style.scss"], 5 | "names": [], 6 | "file": "style.css" 7 | } -------------------------------------------------------------------------------- /demo/css/style.scss: -------------------------------------------------------------------------------- 1 | // Vars 2 | 3 | $font: Helvetica, sans-serif; 4 | $primary-color: #2B3135; 5 | $background-color: #fff; 6 | $title-color: #555; 7 | $paddings: 20px; 8 | $margins: 30px; 9 | 10 | // Mixins 11 | 12 | @mixin background($imgpath,$width, $height) { 13 | width: $width; 14 | height: $height; 15 | 16 | background: { 17 | image: url($imgpath); 18 | position: 0 0; 19 | repeat: no-repeat; 20 | size: $width $height 21 | } 22 | } 23 | 24 | @mixin centerblock{ 25 | display:block; 26 | margin:0 auto; 27 | } 28 | 29 | // Typography 30 | 31 | * { 32 | font-family: $font; 33 | color: $primary-color; 34 | } 35 | 36 | a { 37 | cursor: pointer; 38 | cursor: hand; 39 | } 40 | 41 | body{ 42 | background-color: $background-color; 43 | } 44 | 45 | h1 { 46 | text-align: center; 47 | text-transform: uppercase; 48 | font-size: 2em; 49 | font-weight: 100; 50 | background-color: $primary-color; 51 | color: $background-color; 52 | padding:30px 0; 53 | } 54 | 55 | h4 { 56 | text-align: center; 57 | text-transform: lowercase; 58 | font-size: 1em; 59 | font-weight: lighter; 60 | color: $primary-color; 61 | padding:10px 0; 62 | height: 20px; 63 | } 64 | 65 | h2 { 66 | color: $title-color; 67 | font-weight: 100; 68 | text-transform: uppercase; 69 | font-size:1.8em; 70 | text-align:center; 71 | margin: $margins; 72 | } 73 | 74 | .ir { 75 | display:block; 76 | background-color: transparent; 77 | border: 0; 78 | overflow: hidden; 79 | text-indent: -9999px; 80 | } 81 | 82 | .wrapword { 83 | white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ 84 | white-space: -pre-wrap; /* Opera 4-6 */ 85 | white-space: -o-pre-wrap; /* Opera 7 */ 86 | white-space: pre-wrap; /* css-3 */ 87 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 88 | white-space: -webkit-pre-wrap; /* Newer versions of Chrome/Safari*/ 89 | word-break: break-all; 90 | white-space: normal; 91 | 92 | } 93 | 94 | .hidden { 95 | visibility: hidden; 96 | } 97 | 98 | .error{ 99 | margin: $margins auto; 100 | width: 250px; 101 | background-color: #ffe6e6; 102 | border: 2px solid #ff4d4d; 103 | border-radius: 10px; 104 | font-size: 0.8em; 105 | color:#ff4d4d; 106 | padding:5px 0; 107 | text-align: center; 108 | } 109 | 110 | a.btn { 111 | display:inline-block; 112 | font-size:12px; 113 | background-color:rgba(0,0,0,0.5); 114 | color:#fff; 115 | border-radius:10px; 116 | padding:10px; 117 | margin: 5px; 118 | text-decoration: none; 119 | 120 | &:hover { 121 | opacity:0.7; 122 | } 123 | } 124 | // Login 125 | 126 | #signin { 127 | margin: ($margins + 50) 0; 128 | 129 | a{ 130 | @include centerblock; 131 | @include background("../img/login.png",150px, 60px); 132 | &:hover{ 133 | opacity:0.3; 134 | } 135 | } 136 | 137 | } 138 | 139 | // Dashboard 140 | #dashboard { 141 | 142 | .robot{ 143 | margin-top: 30px; 144 | margin-bottom: 30px; 145 | padding:$paddings 0 0 0 ; 146 | border:1px solid #cdcdcd; 147 | border-radius: 20px; 148 | overflow: hidden; 149 | position:relative; 150 | 151 | .robot_state{ 152 | text-align: center; 153 | font-size: small; 154 | color: #555555; 155 | } 156 | 157 | .cmd_find_me{ 158 | position:absolute; 159 | right:20px; 160 | top:20px; 161 | background-color: $primary-color; 162 | padding:20px; 163 | border-radius:50px; 164 | 165 | i{ 166 | color:#fff; 167 | } 168 | 169 | &:hover{ 170 | opacity:0.5; 171 | } 172 | } 173 | 174 | .cmd_maps{ 175 | position:absolute; 176 | left:20px; 177 | top:20px; 178 | background-color: $primary-color; 179 | padding:20px; 180 | border-radius:50px; 181 | 182 | i{ 183 | color:#fff; 184 | } 185 | 186 | &:hover{ 187 | opacity:0.5; 188 | } 189 | } 190 | 191 | .model{ 192 | text-align: center; 193 | } 194 | 195 | .name{ 196 | text-align:center; 197 | color: $title-color; 198 | font-size:20px; 199 | font-weight:100; 200 | } 201 | 202 | .cleaning-commands { 203 | margin: 10px auto; 204 | text-align: center; 205 | 206 | a { 207 | display: inline-block; 208 | width: 20px; 209 | height: 20px; 210 | margin-right: 5px; 211 | border-radius: 10px; 212 | wdth:50px; 213 | text-align: center; 214 | background-color: $primary-color; 215 | padding:10px 10px; 216 | color:#fff; 217 | 218 | &:hover{ 219 | opacity: 0.5; 220 | } 221 | 222 | &.disabled{ 223 | cursor: default; 224 | opacity:0.2; 225 | 226 | &:hover{ 227 | background-color: rgba(0,0,0,0.8); 228 | background-color: rgba(0,0,0,0.5); 229 | 230 | } 231 | } 232 | 233 | i { 234 | color:#fff; 235 | } 236 | } 237 | } 238 | 239 | .other-commands { 240 | height:60px; 241 | padding: $paddings; 242 | margin-top:$margins; 243 | background-color: $primary-color; 244 | text-align: center; 245 | 246 | p { 247 | font-size:0.8em; 248 | font-weight:100; 249 | color:#fff; 250 | text-transform:uppercase; 251 | text-align: center; 252 | margin-bottom: 10px; 253 | } 254 | } 255 | } 256 | 257 | table { 258 | margin: $margins 0; 259 | 260 | tr.robot{ 261 | 262 | border:2px solid #fff; 263 | background-color: $primary-color; 264 | 265 | &:hover{ 266 | background-color: #004B7C; 267 | } 268 | } 269 | 270 | th { 271 | color: $title-color; 272 | font-weight:200; 273 | } 274 | 275 | td{ 276 | padding:$paddings 0; 277 | color:$background-color; 278 | vertical-align: middle; 279 | 280 | a { 281 | color:$background-color; 282 | text-decoration: none; 283 | text-transform: uppercase; 284 | } 285 | 286 | &.model{ 287 | text-align: center; 288 | width:20%; 289 | 290 | p{ 291 | padding-top:20px; 292 | color:#fff; 293 | font-size:1em; 294 | font-weight:100; 295 | text-transform:uppercase; 296 | } 297 | } 298 | 299 | &.commands{ 300 | 301 | a { 302 | float: left; 303 | display: block; 304 | width: 20px; 305 | height: 20px; 306 | margin-right: 5px; 307 | border-radius: 10px; 308 | wdth:50px; 309 | text-align: center; 310 | background-color: rgba(0,0,0,0.5); 311 | padding:10px 10px; 312 | 313 | &:hover{ 314 | background-color: rgba(0,0,0,0.8); 315 | } 316 | 317 | &.disabled{ 318 | cursor: default; 319 | opacity:0.2; 320 | 321 | &:hover{ 322 | background-color: rgba(0,0,0,0.8); 323 | background-color: rgba(0,0,0,0.5); 324 | } 325 | } 326 | } 327 | } 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /demo/img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/demo/img/login.png -------------------------------------------------------------------------------- /demo/img/robot_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/demo/img/robot_empty.png -------------------------------------------------------------------------------- /demo/img/robot_image_botvacconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/demo/img/robot_image_botvacconnected.png -------------------------------------------------------------------------------- /demo/img/robot_image_botvacd3connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/demo/img/robot_image_botvacd3connected.png -------------------------------------------------------------------------------- /demo/img/robot_image_botvacd5connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/demo/img/robot_image_botvacd5connected.png -------------------------------------------------------------------------------- /demo/img/robot_image_botvacd7connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/demo/img/robot_image_botvacd7connected.png -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Neato Robotics Example 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |

Neato SDK Example

28 | 29 |
30 | 31 |
32 | Login 33 |
34 |
35 | 36 |
37 |

38 |

My robots

39 |
40 |
41 |

logout

42 |
43 | 44 |
45 | 46 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /demo/neato-demo-app.js: -------------------------------------------------------------------------------- 1 | var NeatoDemoApp = { 2 | clientId: null, 3 | scopes: null, 4 | redirectUrl: null, 5 | user: null, 6 | 7 | initialize: function (options) { 8 | this.clientId = options.clientId; 9 | this.scopes = options.scopes; 10 | this.redirectUrl = options.redirectUrl; 11 | 12 | this.guiShowLoginPage(); 13 | this.guiHideDashboardPage(); 14 | this.guiInitializeEvents(); 15 | this.checkAuthenticationStatus(); 16 | }, 17 | 18 | // robot state 19 | connect: function (robot) { 20 | var self = this; 21 | 22 | robot.onConnected = function () { 23 | console.log(robot.serial + " got connected"); 24 | }; 25 | robot.onDisconnected = function (status, json) { 26 | console.log(robot.serial + " got disconnected"); 27 | self.guiResetAll(robot.serial); 28 | }; 29 | robot.onStateChange = function () { 30 | console.log(robot.serial + " got new state:", robot.state); 31 | self.onStateChange(robot.serial); 32 | }; 33 | robot.connect(); 34 | }, 35 | 36 | disconnect: function (serial) { 37 | this.user.getRobotBySerial(serial).disconnect(); 38 | }, 39 | 40 | onStateChange: function (serial) { 41 | this.guiEnableRobotCommands(serial); 42 | this.guiDisplayState(serial); 43 | }, 44 | 45 | getAvailableCommands: function (serial) { 46 | if (this.user.getRobotBySerial(serial).state) { 47 | return this.user.getRobotBySerial(serial).state.availableCommands; 48 | } else { 49 | return null; 50 | } 51 | }, 52 | 53 | getAvailableServices: function (serial) { 54 | if (this.user.getRobotBySerial(serial).state) { 55 | return this.user.getRobotBySerial(serial).state.availableServices; 56 | } else { 57 | return null; 58 | } 59 | }, 60 | 61 | // robot commands 62 | startOrResume: function (serial) { 63 | var availableCommands = this.getAvailableCommands(serial); 64 | if (!availableCommands) { 65 | return; 66 | } 67 | 68 | if (availableCommands.start) { 69 | this.startHouseCleaning(serial); 70 | } else if (availableCommands.resume) { 71 | this.resumeCleaning(serial); 72 | } 73 | }, 74 | 75 | startHouseCleaning: function (serial) { 76 | this.user.getRobotBySerial(serial).startHouseCleaning({ 77 | mode: Neato.Constants.TURBO_MODE, 78 | modifier: Neato.Constants.HOUSE_FREQUENCY_NORMAL, 79 | navigationMode: Neato.Constants.EXTRA_CARE_OFF 80 | }); 81 | }, 82 | 83 | pauseCleaning: function (serial) { 84 | this.user.getRobotBySerial(serial).pauseCleaning(); 85 | }, 86 | 87 | resumeCleaning: function (serial) { 88 | this.user.getRobotBySerial(serial).resumeCleaning(); 89 | }, 90 | 91 | stopCleaning: function (serial) { 92 | this.user.getRobotBySerial(serial).stopCleaning(); 93 | }, 94 | 95 | sendToBase: function (serial) { 96 | this.user.getRobotBySerial(serial).sendToBase(); 97 | }, 98 | 99 | findMe: function (serial) { 100 | this.user.getRobotBySerial(serial).findMe(); 101 | }, 102 | 103 | maps: function (serial) { 104 | var self = this; 105 | self.user.getRobotBySerial(serial).maps().done(function (data) { 106 | if(data["maps"] && data["maps"].length > 0) { 107 | var mapId = data["maps"][0]["id"]; 108 | self.user.getRobotBySerial(serial).mapDetails(mapId).done(function (data) { 109 | window.open(data["url"]); 110 | }).fail(function (data) { 111 | self.showErrorMessage("something went wrong getting map details...."); 112 | }); 113 | }else { 114 | alert("No maps available yet. Complete at least one house cleaning to view maps.") 115 | } 116 | }).fail(function (data) { 117 | self.showErrorMessage("something went wrong getting robots map...."); 118 | }); 119 | }, 120 | 121 | setScheduleEveryMonday: function(serial) { 122 | this.user.getRobotBySerial(serial).setSchedule({ 123 | 1: { mode: 1, startTime: "15:00" } 124 | }); 125 | }, 126 | 127 | setScheduleEveryDay: function(serial) { 128 | this.user.getRobotBySerial(serial).setSchedule({ 129 | 0: { mode: 1, startTime: "15:00" }, 130 | 1: { mode: 1, startTime: "15:00" }, 131 | 2: { mode: 1, startTime: "15:00" }, 132 | 3: { mode: 1, startTime: "15:00" }, 133 | 4: { mode: 1, startTime: "15:00" }, 134 | 5: { mode: 1, startTime: "15:00" }, 135 | 6: { mode: 1, startTime: "15:00" } 136 | }); 137 | }, 138 | 139 | checkAuthenticationStatus: function () { 140 | var self = this; 141 | this.user = new Neato.User(); 142 | 143 | this.user.isConnected() 144 | .done(function () { 145 | self.guiHideLoginPage(); 146 | self.guiShowDashboardPage(); 147 | self.getDashboard(); 148 | }) 149 | .fail(function () { 150 | //show auth error only if the user attempt to login with a token 151 | if(self.user.authenticationError()) { 152 | self.guiShowAuthenticationErrorUI(self.user.authErrorDescription); 153 | }else if(!self.user.connected && self.user.token != null) { 154 | self.guiShowAuthenticationErrorUI("access denied"); 155 | } 156 | }); 157 | }, 158 | 159 | // GUI 160 | guiInitializeEvents: function () { 161 | var self = this; 162 | 163 | $("#cmd_login").click(function () { 164 | self.user.login({ 165 | clientId: self.clientId, 166 | scopes: self.scopes, 167 | redirectUrl: self.redirectUrl 168 | }); 169 | }); 170 | $("#cmd_logout").click(function () { 171 | self.user.logout() 172 | .done(function (data) { 173 | self.guiHideDashboardPage(); 174 | self.guiShowLoginPage(); 175 | }).fail(function (data) { 176 | self.showErrorMessage("something went wrong during logout..."); 177 | }); 178 | }); 179 | $(document).on("click", ".cmd_start", function () { 180 | self.startOrResume($(this).parents(".robot").attr('data-serial')); 181 | }); 182 | $(document).on("click", ".cmd_pause", function () { 183 | self.pauseCleaning($(this).parents(".robot").attr('data-serial')); 184 | }); 185 | $(document).on("click", ".cmd_stop", function () { 186 | self.stopCleaning($(this).parents(".robot").attr('data-serial')); 187 | }); 188 | $(document).on("click", ".cmd_send_to_base", function () { 189 | self.sendToBase($(this).parents(".robot").attr('data-serial')); 190 | }); 191 | $(document).on("click", ".cmd_find_me", function () { 192 | self.findMe($(this).parents().attr('data-serial')); 193 | }); 194 | $(document).on("click", ".cmd_maps", function () { 195 | self.maps($(this).parents().attr('data-serial')); 196 | }); 197 | $(document).on("click", ".cmd_schedule_monday", function () { 198 | self.setScheduleEveryMonday($(this).parents().parents().attr('data-serial')); 199 | }); 200 | $(document).on("click", ".cmd_schedule_every_day", function () { 201 | self.setScheduleEveryDay($(this).parents().parents().attr('data-serial')); 202 | }); 203 | }, 204 | 205 | guiShowLoginPage: function () { 206 | this.hideErrorMessage(); 207 | $("#signin").show(); 208 | }, 209 | guiHideLoginPage: function () { 210 | $("#signin").hide(); 211 | }, 212 | guiShowDashboardPage: function () { 213 | $("#dashboard").show(); 214 | }, 215 | guiHideDashboardPage: function () { 216 | $("#dashboard").hide(); 217 | }, 218 | 219 | getDashboard: function () { 220 | var self = this; 221 | 222 | //get user email if available 223 | this.user.getUserInfo() 224 | .done(function (data) { 225 | $("#user_first_name").html(data.first_name || ""); 226 | }).fail(function (data) { 227 | self.showErrorMessage("something went wrong accessing user info...."); 228 | }); 229 | 230 | //get user robots 231 | this.user.getRobots() 232 | .done(function (robotsArray) { 233 | var html = ""; 234 | var robot; 235 | //start polling robot state 236 | for (var i = 0; i < robotsArray.length; i++) { 237 | robot = robotsArray[i]; 238 | self.connect(robot); 239 | html += self.guiRobotTemplate(robot); 240 | } 241 | $("#robot_list").html(html); 242 | }).fail(function (data) { 243 | self.showErrorMessage("something went wrong retrieving robot list...."); 244 | }); 245 | }, 246 | 247 | guiRobotTemplate: function(robot) { 248 | return "
" + 249 | "
" + 250 | "

"+robot.name+"

" + 251 | "

NOT AVAILABLE

" + 252 | "" + 253 | "" + 254 | "
" + 255 | "" + 256 | "" + 257 | "" + 258 | "" + 259 | "
" + 260 | "
" + 261 | "

WIPE ALL EXISTING SCHEDULE AND SET IT TO:

" + 262 | "Everyday at 3:00 pm" + 263 | "Monday at 3:00 pm" + 264 | "
" + 265 | "
"; 266 | }, 267 | 268 | getRobotImage: function(model) { 269 | if(model.toLowerCase() == "botvacconnected") return "robot_image_botvacconnected.png"; 270 | else if(model.toLowerCase() == "botvacd3connected") return "robot_image_botvacd3connected.png"; 271 | else if(model.toLowerCase() == "botvacd5connected") return "robot_image_botvacd5connected.png"; 272 | else if(model.toLowerCase() == "botvacd7connected") return "robot_image_botvacd7connected.png"; 273 | else return "robot_empty.png"; 274 | }, 275 | 276 | guiShowAuthenticationErrorUI: function (message) { 277 | this.guiShowLoginPage(); 278 | this.showErrorMessage(message); 279 | }, 280 | 281 | guiEnableRobotCommands: function (serial) { 282 | var availableCommands = this.getAvailableCommands(serial); 283 | if (!availableCommands) { 284 | return; 285 | } 286 | 287 | var $robotUI = $("[data-serial='" + serial + "']"); 288 | 289 | $robotUI.find(".cmd_start").first().removeClass('disabled'); 290 | $robotUI.find(".cmd_pause").first().removeClass('disabled'); 291 | $robotUI.find(".cmd_stop").first().removeClass('disabled'); 292 | $robotUI.find(".cmd_send_to_base").first().removeClass('disabled'); 293 | 294 | if(!(availableCommands.start || availableCommands.resume)) $robotUI.find(".cmd_start").first().addClass('disabled'); 295 | if(!availableCommands.pause) $robotUI.find(".cmd_pause").first().addClass('disabled'); 296 | if(!availableCommands.stop) $robotUI.find(".cmd_stop").first().addClass('disabled'); 297 | if(!availableCommands.goToBase) $robotUI.find(".cmd_send_to_base").first().addClass('disabled'); 298 | 299 | //check available services to enable robot features 300 | var availableServices = this.getAvailableServices(serial); 301 | if (!availableServices) { 302 | return; 303 | } 304 | 305 | if(!availableServices["findMe"]) $robotUI.find(".cmd_find_me").first().hide(); 306 | else $robotUI.find(".cmd_find_me").first().show(); 307 | 308 | if(!availableServices["maps"]) $robotUI.find(".cmd_maps").first().hide(); 309 | else $robotUI.find(".cmd_maps").first().show(); 310 | }, 311 | 312 | guiDisableRobotCommands: function (serial) { 313 | var $robotUI = $("[data-serial='" + serial + "']"); 314 | 315 | $robotUI.find(".cmd_start").first().addClass('disabled'); 316 | $robotUI.find(".cmd_pause").first().addClass('disabled'); 317 | $robotUI.find(".cmd_stop").first().addClass('disabled'); 318 | $robotUI.find(".cmd_send_to_base").first().addClass('disabled'); 319 | $robotUI.find(".cmd_find_me").first().hide(); 320 | $robotUI.find(".cmd_maps").first().hide(); 321 | }, 322 | 323 | guiDisplayState: function (serial) { 324 | var $robotState = $("div[data-serial='" + serial + "']"); 325 | 326 | var prettyState = "NOT AVAILABLE"; 327 | var robot_state = this.user.getRobotBySerial(serial).state.state; 328 | switch (robot_state) { 329 | case 1: 330 | prettyState = "IDLE"; 331 | break; 332 | case 2: 333 | prettyState = "BUSY"; 334 | break; 335 | case 3: 336 | prettyState = "PAUSED"; 337 | break; 338 | case 4: 339 | prettyState = "ERROR"; 340 | break; 341 | default: 342 | prettyState = "NOT AVAILABLE"; 343 | } 344 | 345 | $robotState.find(".robot_state").html(prettyState); 346 | }, 347 | 348 | guiResetState: function (serial) { 349 | var $robotState = $("div[data-serial='" + serial + "']"); 350 | $robotState.find(".robot_state").html("NOT AVAILABLE"); 351 | }, 352 | 353 | guiResetAll: function (serial) { 354 | this.guiResetState(serial); 355 | this.guiDisableRobotCommands(serial); 356 | }, 357 | 358 | showErrorMessage: function(message) { 359 | $("div.error").html(message).show(); 360 | }, 361 | hideErrorMessage: function() { 362 | $("div.error").hide(); 363 | } 364 | }; 365 | -------------------------------------------------------------------------------- /jasmine.yml: -------------------------------------------------------------------------------- 1 | # src_files 2 | # 3 | # Return an array of filepaths relative to src_dir to include before jasmine specs. 4 | # Default: [] 5 | # 6 | # EXAMPLE: 7 | # 8 | # src_files: 9 | # - lib/source1.js 10 | # - lib/source2.js 11 | # - dist/**/*.js 12 | # 13 | src_files: 14 | - 'neato.js' 15 | - 'utils.js' 16 | - 'robot.js' 17 | - 'user.js' 18 | - 'robot-services/*.js' 19 | - 'robot-services/**/*.js' 20 | 21 | # stylesheets 22 | # 23 | # Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs. 24 | # Default: [] 25 | # 26 | # EXAMPLE: 27 | # 28 | # stylesheets: 29 | # - css/style.css 30 | # - stylesheets/*.css 31 | # 32 | # stylesheets: 33 | # - stylesheets/**/*.css 34 | 35 | # helpers 36 | # 37 | # Return an array of filepaths relative to spec_dir to include before jasmine specs. 38 | # Default: ["helpers/**/*.js"] 39 | # 40 | # EXAMPLE: 41 | # 42 | # helpers: 43 | # - helpers/**/*.js 44 | # 45 | helpers: 46 | - 'support/**/**/*.js' 47 | 48 | # spec_files 49 | # 50 | # Return an array of filepaths relative to spec_dir to include. 51 | # Default: ["**/*[sS]pec.js"] 52 | # 53 | # EXAMPLE: 54 | # 55 | # spec_files: 56 | # - **/*[sS]pec.js 57 | # 58 | spec_files: 59 | - '**/*[sS]pec.js' 60 | 61 | # src_dir 62 | # 63 | # Source directory path. Your src_files must be returned relative to this path. Will use root if left blank. 64 | # Default: project root 65 | # 66 | # EXAMPLE: 67 | # 68 | # src_dir: public 69 | # 70 | src_dir: 'src' 71 | 72 | # spec_dir 73 | # 74 | # Spec directory path. Your spec_files must be returned relative to this path. 75 | # Default: spec/javascripts 76 | # 77 | # EXAMPLE: 78 | # 79 | # spec_dir: spec/javascripts 80 | # 81 | spec_dir: 'spec' 82 | 83 | # spec_helper 84 | # 85 | # Ruby file that Jasmine server will require before starting. 86 | # Returned relative to your root path 87 | # Default spec/javascripts/support/jasmine_helper.rb 88 | # 89 | # EXAMPLE: 90 | # 91 | # spec_helper: spec/javascripts/support/jasmine_helper.rb 92 | # 93 | 94 | # boot_dir 95 | # 96 | # Boot directory path. Your boot_files must be returned relative to this path. 97 | # Default: Built in boot file 98 | # 99 | # EXAMPLE: 100 | # 101 | # boot_dir: spec/javascripts/support/boot 102 | # 103 | boot_dir: 104 | 105 | # boot_files 106 | # 107 | # Return an array of filepaths relative to boot_dir to include in order to boot Jasmine 108 | # Default: Built in boot file 109 | # 110 | # EXAMPLE 111 | # 112 | # boot_files: 113 | # - '**/*.js' 114 | # 115 | boot_files: 116 | 117 | # rack_options 118 | # 119 | # Extra options to be passed to the rack server 120 | # by default, Port and AccessLog are passed. 121 | # 122 | # This is an advanced options, and left empty by default 123 | # 124 | # EXAMPLE 125 | # 126 | # rack_options: 127 | # server: 'thin' 128 | 129 | -------------------------------------------------------------------------------- /lib/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeatoRobotics/neato-sdk-js/19f9ae1b5e8f15ba1bdf3c80e72bcd0ec8ea3e69/lib/.gitkeep -------------------------------------------------------------------------------- /lib/neato-0.10.0.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Neato SDK Javascript 3 | * https://github.com/NeatoRobotics/neato-sdk-js 4 | * 5 | * Copyright (c)2016 Neato Robotics, Inc. 6 | * Author: Roberto Ostinelli 7 | * 8 | * Version: 0.10.0 9 | */ 10 | var Neato={user:null,Services:{},Constants:{CLEAN_HOUSE_MODE:2,CLEAN_SPOT_MODE:3,CLEAN_MAP_MODE:4,MANUAL_CLEANING_MODE:1,ECO_MODE:1,TURBO_MODE:2,SPOT_AREA_SMALL:200,SPOT_AREA_LARGE:400,HOUSE_FREQUENCY_NORMAL:1,HOUSE_FREQUENCY_DOUBLE:2,SPOT_FREQUENCY_NORMAL:1,SPOT_FREQUENCY_DOUBLE:2,EXTRA_CARE_OFF:1,EXTRA_CARE_ON:2}};Neato.utils={haveEqualProps:function(e,t){var n;for(n in e)if("undefined"==typeof t[n])return!1;for(n in e)if(e[n])switch(typeof e[n]){case"object":if(!this.haveEqualProps(e[n],t[n]))return!1;break;case"function":if("undefined"==typeof t[n]||"equals"!=n&&e[n].toString()!=t[n].toString())return!1;break;default:if(e[n]!=t[n])return!1}else if(t[n])return!1;for(n in t)if("undefined"==typeof e[n])return!1;return!0},clone:function(e,t){var n={},a=(t=t||{}).except,r=t.only;for(var o in e)!e.hasOwnProperty(o)||-1!==$.inArray(o,a)||void 0!==r&&-1===$.inArray(o,r)||(n[o]=e[o]);return n},isNullOrEmptyJSON:function(e){return null==e||!!jQuery.isEmptyObject(e)}},Neato.User=function(){this.initialize.apply(this,arguments)},Neato.User.prototype={initialize:function(){this.__parseRedirectURIResponse(),this.host="beehive.neatocloud.com",this.oauthHost="apps.neatorobotics.com",this.robots=[],Neato.user=this},getUserInfo:function(){return this.__call("GET","/users/me")},__getRobotMaps:function(e){return this.__call("GET","/users/me/robots/"+e+"/maps")},__getMapDetails:function(e,t){return this.__call("GET","/users/me/robots/"+e+"/maps/"+t)},getRobots:function(){var n=this,a=$.Deferred();return this.__call("GET","/users/me/robots").done(function(e){n.robots=[];for(var t=0;t>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 29 | 32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, 30 | 2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, 31 | u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;gn;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= 35 | c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; 36 | d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); 37 | (function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j