├── CONTRIBUTING.md ├── DDP.cpp ├── DDP.h ├── LICENSE └── README.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Contributions are welcome though as mentioned in the readme, at the moment this is very specific to my thesis. I will clean it up and generalise it once my thesis is finished (June). It will probably be easier to contribute then rather then trying to make sense of it at the moment. It is usable though for others with a bit of modification! 5 | 6 | Fork the repository → Push to your fork → Submit a pull request. 7 | 8 | Please be consistent with the code style. 9 | -------------------------------------------------------------------------------- /DDP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | DDP library for Arduino 3 | */ 4 | 5 | #include "DDP.h" 6 | 7 | // Constructor ///////////////////////////////////////////////////////////////// 8 | DDP::DDP() { 9 | } 10 | 11 | // Public Methods ////////////////////////////////////////////////////////////// 12 | /* 13 | * setup 14 | * 15 | * @host 16 | * @path 17 | * @port 18 | */ 19 | bool DDP::setup(String host, String path /* = "/" */, int port /* = 80 */) { 20 | // Save args 21 | _host = host; 22 | _path = path; 23 | _port = port; 24 | 25 | bool connected = false; 26 | 27 | // Connect to the websocket server 28 | if (_client.connect("192.168.0.3", 3000)) { 29 | //if (_client.connect("winter.ceit.uq.edu.au", 4000)) { 30 | Serial.println("Connected to server"); 31 | connected = true; 32 | 33 | // Handshake with server 34 | connected = handshake(); 35 | } else { 36 | Serial.println("Connection to server failed."); 37 | connected = false; 38 | } 39 | 40 | return connected; 41 | } 42 | 43 | /* 44 | * 45 | */ 46 | bool DDP::handshake() { 47 | // Handshake with server 48 | _webSocketClient.path = "/websocket"; 49 | _webSocketClient.host = "192.168.0.3"; 50 | 51 | if (_webSocketClient.handshake(_client)) { 52 | Serial.println("Handshake successful"); 53 | return true; 54 | } else { 55 | Serial.println("Handshake failed."); 56 | return false; 57 | } 58 | } 59 | 60 | /* 61 | * 62 | */ 63 | void DDP::output() { 64 | String data; 65 | _webSocketClient.getData(data); 66 | 67 | if (data.length() > 0) { 68 | Serial.print("Received data: "); 69 | Serial.println(data); 70 | } 71 | } 72 | 73 | void DDP::waitFor() { 74 | } 75 | 76 | /* 77 | * connect (client -> server) 78 | */ 79 | bool DDP::connect() { 80 | // Prepare message 81 | JsonObject& root = _jsonBuffer.createObject(); 82 | root["msg"] = "connect"; 83 | root["version"] = "1"; 84 | JsonArray& support = _jsonBuffer.createArray(); 85 | support.add("1"); 86 | root["support"] = support; 87 | 88 | char buffer[200]; 89 | root.printTo(buffer, sizeof(buffer)); 90 | 91 | // Send message 92 | _webSocketClient.sendData(buffer); 93 | delay(_pause); 94 | 95 | /* 96 | * Handle response 97 | * 98 | * connected (server->client) 99 | * @session: string (an identifier for the DDP session) 100 | * failed (server->client) 101 | * @version: string (a suggested protocol version to connect with) 102 | */ 103 | String response; 104 | _webSocketClient.getData(response); 105 | 106 | if (response.length() > 0) { 107 | Serial.print("Received response: "); 108 | Serial.println(response); 109 | } 110 | 111 | bool status = false; 112 | 113 | if (response.indexOf("failed") >= 0) { 114 | status = false; 115 | } else if (response.indexOf("connected") >= 0) { 116 | status = true; 117 | // {"msg":"connected","session":"zwKbMXqs7jcKrke4Y"} 118 | _session = response.substring(30, 47); 119 | } 120 | 121 | return status; 122 | 123 | /* TODO Use parseObject 124 | root = _jsonBuffer.parseObject(response); 125 | if (!root.success()) { 126 | Serial.println("parseObject() failed"); 127 | return; 128 | } 129 | 130 | char* msg = root["msg"]; 131 | String msgs(msg); 132 | 133 | if (msgs.equals("failed")) { 134 | Serial.println("failed"); 135 | } else if (msgs.equals("connected")) { 136 | Serial.println("connected"); 137 | } 138 | */ 139 | } 140 | 141 | /* 142 | * listen 143 | */ 144 | void DDP::listen() { 145 | Serial.println("In listen()"); 146 | Serial.println(_timer); 147 | // TODO Check for Meteor connection too 148 | while(_client.connected()) { 149 | // Proactively keep the heartbeat going 150 | if (_timer % 40 == 0) { 151 | ping(); 152 | } 153 | 154 | if (_timer % 10 == 0) { 155 | _timer++; 156 | Serial.println("Returning from listen()"); 157 | return; 158 | } 159 | 160 | String data; 161 | _webSocketClient.getData(data); 162 | 163 | if (data.length() == 0) { 164 | Serial.println("No data..."); 165 | delay(_pause); 166 | _timer++; 167 | continue; 168 | } 169 | 170 | Serial.println("------------------------------"); 171 | Serial.print("data: "); 172 | Serial.println(data); 173 | 174 | /* Heartbeats ************************************************************/ 175 | // Ping 176 | if (data.indexOf("ping") >= 0) { 177 | Serial.println("ping"); 178 | 179 | Serial.println("Pong-ing"); 180 | 181 | // TODO Call pong with optional ID 182 | if (data.length() > 14) { 183 | //String id = data.substring(); 184 | pong(/* id */); 185 | } else { 186 | pong(); 187 | } 188 | 189 | continue; 190 | } 191 | 192 | // Pong 193 | if (data.indexOf("pong") >= 0) { 194 | Serial.println("pong"); 195 | 196 | continue; 197 | } 198 | 199 | /* Managing data *********************************************************/ 200 | /* 201 | * nosub 202 | */ 203 | if (data.indexOf("nosub") >= 0) { 204 | Serial.println("nosub"); 205 | 206 | continue; 207 | } 208 | /* 209 | * added 210 | * 211 | * Assume already 1 document with initial value 212 | * 213 | * Example: 214 | * 215 | * {"msg":"added","collection":"rgb","id":"LM8HndauPqxBHqJ2b","fields":{"r":0,"g":0,"b":0}} 216 | * {"msg":"added","collection":"#r","id":"yq2q2QtmjKn2oPLCv","fields":{"value":0}} 217 | * {"msg":"added","collection":"#r","id":"yq2q2QtmjKn2oPLCv","fields":{"value":"100"}} 218 | */ 219 | if (data.indexOf("added") >= 0) { 220 | Serial.println("added"); 221 | 222 | if (data.indexOf("#r") >= 0) { 223 | String value = data.substring(77, 80); 224 | Serial.println("value: " + value); 225 | _r = value.toInt(); 226 | Serial.print("_r: "); 227 | Serial.println(_r); 228 | } 229 | if (data.indexOf("#g") >= 0) { 230 | String value = data.substring(77, 80); 231 | Serial.println("value: " + value); 232 | _g = value.toInt(); 233 | Serial.print("_g: "); 234 | Serial.println(_g); 235 | } 236 | if (data.indexOf("#b") >= 0) { 237 | String value = data.substring(77, 80); 238 | Serial.println("value: " + value); 239 | _b = value.toInt(); 240 | Serial.print("_b: "); 241 | Serial.println(_b); 242 | } 243 | 244 | continue; 245 | } 246 | /* 247 | * changed 248 | * 249 | * Example: 250 | * 251 | * {"msg":"changed","collection":"#g","id":"BwRE6F3fgkfEJgt7P","fields":{"value":"011"}} 252 | */ 253 | if (data.indexOf("changed") >= 0) { 254 | Serial.println("changed"); 255 | 256 | if (data.indexOf("#r") >= 0) { 257 | String value = data.substring(79, 82); 258 | Serial.println("value: " + value); 259 | _r = value.toInt(); 260 | Serial.print("_r: "); 261 | Serial.println(_r); 262 | } 263 | if (data.indexOf("#g") >= 0) { 264 | String value = data.substring(79, 82); 265 | Serial.println("value: " + value); 266 | _g = value.toInt(); 267 | Serial.print("_g: "); 268 | Serial.println(_g); 269 | } 270 | if (data.indexOf("#b") >= 0) { 271 | String value = data.substring(79, 82); 272 | Serial.println("value: " + value); 273 | _b = value.toInt(); 274 | Serial.print("_b: "); 275 | Serial.println(_b); 276 | } 277 | 278 | continue; 279 | } 280 | /* 281 | * ready 282 | * 283 | * When one or more subscriptions have finished sending their initial batch of data, the server will send a ready message with their IDs. 284 | * 285 | * Example: 286 | * 287 | * {"msg":"ready","subs":["1"]} 288 | */ 289 | if (data.indexOf("ready") >= 0) { 290 | Serial.println("ready"); 291 | if (data.indexOf("1") >= 0) { 292 | Serial.println("R, ready"); 293 | _readyR = true; 294 | } 295 | if (data.indexOf("2") >= 0) { 296 | Serial.println("G, ready"); 297 | _readyG = true; 298 | } 299 | if (data.indexOf("3") >= 0) { 300 | Serial.println("B, ready"); 301 | _readyB = true; 302 | } 303 | continue; 304 | } 305 | 306 | /* Remote procedure calls ************************************************/ 307 | // Don't move on till we've handled the expected result and updated msgs 308 | 309 | // Examples 310 | // data: {"msg":"result","id":"1"} 311 | // {"msg":"result","id":"1","result":"test"} 312 | if (data.indexOf("result") >= 0) { 313 | Serial.println("Handled method/result"); 314 | 315 | // TODO Params 316 | 317 | 318 | /* TODO Errors 319 | * @error string 320 | * @reason optional string 321 | * @details optional string 322 | * 323 | * Example: 324 | * 325 | * data: {"msg":"error","reason":"Malformed method invocation","offendingMessage":{"msg":"method","method":"test2"}} 326 | * data: {"msg":"error","reason":"Bad request","offendingMessage":{}} 327 | * 328 | * Restart over? 329 | * 330 | */ 331 | if (data.indexOf("error") >= 0) { 332 | Serial.println("error"); 333 | } 334 | 335 | //_handledResult = true; 336 | // data: {"msg":"updated","methods":["1"]} 337 | } 338 | if (data.indexOf("updated") >= 0) { 339 | Serial.println("Handled method/updated"); 340 | 341 | //_handledUpdated = true; 342 | } 343 | 344 | 345 | delay(_pause); 346 | } 347 | } 348 | 349 | /* Heartbeats ****************************************************************/ 350 | /* 351 | * ping 352 | * @id optional string (identifier used to correlate with response) 353 | */ 354 | void DDP::ping(String id /* = "" */) { 355 | JsonObject& root = _jsonBuffer.createObject(); 356 | root["msg"] = "ping"; 357 | 358 | if (id.length() > 0) { 359 | root["id"] = id; 360 | } 361 | 362 | char buffer[200]; 363 | root.printTo(buffer, sizeof(buffer)); 364 | 365 | _webSocketClient.sendData(buffer); 366 | } 367 | 368 | /* 369 | * pong 370 | * @id optional string (same as received in the ping message) 371 | */ 372 | void DDP::pong(String id /* = "" */) { 373 | JsonObject& root = _jsonBuffer.createObject(); 374 | root["msg"] = "pong"; 375 | 376 | if (id.length() > 0) { 377 | root["id"] = id; 378 | } 379 | 380 | char buffer[200]; 381 | root.printTo(buffer, sizeof(buffer)); 382 | 383 | _webSocketClient.sendData(buffer); 384 | } 385 | /* Managing data *************************************************************/ 386 | /* 387 | * sub 388 | * @id string (an arbitrary client-determined identifier for this subscription) 389 | * @name string (the name of the subscription) 390 | * @params optional array of EJSON items (parameters to the subscription) 391 | */ 392 | void DDP::sub() { 393 | // Subscribe to r, g ,b 394 | JsonObject& root = _jsonBuffer.createObject(); 395 | root["msg"] = "sub"; 396 | root["id"] = "1"; 397 | root["name"] = "#r"; 398 | 399 | char buffer[200]; 400 | root.printTo(buffer, sizeof(buffer)); 401 | 402 | _webSocketClient.sendData(buffer); 403 | 404 | delay(_pause); 405 | 406 | root["msg"] = "sub"; 407 | root["id"] = "2"; 408 | root["name"] = "#g"; 409 | 410 | root.printTo(buffer, sizeof(buffer)); 411 | 412 | _webSocketClient.sendData(buffer); 413 | 414 | delay(_pause); 415 | 416 | root["msg"] = "sub"; 417 | root["id"] = "3"; 418 | root["name"] = "#b"; 419 | 420 | root.printTo(buffer, sizeof(buffer)); 421 | 422 | _webSocketClient.sendData(buffer); 423 | 424 | delay(_pause * 2); 425 | } 426 | 427 | /* Remote procedure calls ****************************************************/ 428 | /* 429 | * method 430 | * 431 | * @method string (method name) 432 | * @params optional array of EJSON items (parameters to the method) 433 | * @id string (an arbitrary client-determined identifier for this method call) 434 | * @randomSeed optional JSON value (an arbitrary client-determined seed for pseudo-random generators) 435 | */ 436 | void DDP::method(int readR, int readG, int readB) { 437 | // Test call test 438 | JsonObject& root = _jsonBuffer.createObject(); 439 | root["msg"] = "method"; 440 | root["method"] = "readSensor"; 441 | JsonArray& params = _jsonBuffer.createArray(); 442 | params.add(readR); 443 | params.add(readG); 444 | params.add(readB); 445 | root["params"] = params; 446 | root["id"] = "1"; 447 | 448 | char buffer[200]; 449 | root.printTo(buffer, sizeof(buffer)); 450 | 451 | _webSocketClient.sendData(buffer); 452 | } 453 | 454 | /* Sub ***********************************************************************/ 455 | bool DDP::subsReady() { 456 | if (_readyR && _readyG && _readyB && _addedR && _addedG && _addedB) { 457 | return true; 458 | } else { 459 | return false; 460 | } 461 | } 462 | /* RGB ***********************************************************************/ 463 | int DDP::getR() { 464 | return _r; 465 | } 466 | int DDP::getG() { 467 | return _g; 468 | } 469 | int DDP::getB() { 470 | return _b; 471 | } 472 | 473 | // Private Methods ///////////////////////////////////////////////////////////// 474 | -------------------------------------------------------------------------------- /DDP.h: -------------------------------------------------------------------------------- 1 | /* 2 | DDP library for Arduino 3 | 4 | http://www.arduino.cc/en/Hacking/LibraryTutorial 5 | */ 6 | 7 | #ifndef DDP_h 8 | #define DDP_h 9 | 10 | #include 11 | #include 12 | #include "./libs/Arduino-Websocket/WebSocketClient.h" 13 | //#include "./libs/ArduinoWebsocketClient/WebSocketClient.h" 14 | #include "./libs/ArduinoJson/ArduinoJson.h" 15 | 16 | class DDP { 17 | public: 18 | DDP(); 19 | 20 | /************************************************************************** 21 | * WebSocket 22 | *************************************************************************/ 23 | bool setup(String host, String path = "/", int port = 80); 24 | bool handshake(); 25 | 26 | void output(); 27 | void waitFor(); 28 | 29 | /************************************************************************** 30 | * DDP 31 | *************************************************************************/ 32 | bool connect(); 33 | //void connect(String session = "", int version = 1, int support[] = DDP_Versions); 34 | 35 | // Listen 36 | void listen(); 37 | 38 | /* Heartbeats ************************************************************/ 39 | void ping(String id = ""); 40 | void pong(String id = ""); 41 | 42 | /* Managing data *********************************************************/ 43 | // client -> server 44 | void sub(); 45 | void unsub(); 46 | // server -> client 47 | void nosub(); 48 | void added(); 49 | void changed(); 50 | void removed(); 51 | void ready(); 52 | // addedBefore(); 53 | // movedBefore() 54 | 55 | /* Remote procedure calls ************************************************/ 56 | // client -> server 57 | void method(int readR, int readG, int readB); 58 | // server-> client 59 | void result(); 60 | void updated(); 61 | 62 | /* Sub *******************************************************************/ 63 | bool subsReady(); 64 | /* RGB *******************************************************************/ 65 | int getR(); 66 | int getG(); 67 | int getB(); 68 | 69 | private: 70 | int _pause = 1000; 71 | int _timer = 1; 72 | 73 | WebSocketClient _webSocketClient; 74 | EthernetClient _client; 75 | 76 | String _host; 77 | String _path; 78 | int _port; 79 | 80 | String _session; 81 | 82 | StaticJsonBuffer<200> _jsonBuffer; 83 | JsonObject& _root = _jsonBuffer.createObject(); 84 | 85 | /* Subscription */ 86 | bool _readyR = false; 87 | bool _readyG = false; 88 | bool _readyB = false; 89 | bool _addedR = false; 90 | bool _addedG = false; 91 | bool _addedB = false; 92 | // R, G, B 93 | int _r = 0; 94 | int _g = 0; 95 | int _b = 0; 96 | 97 | 98 | //const int DDP_Versions[] = {1}; 99 | 100 | //int _value; 101 | //void _doSomethingSecret(void); 102 | 103 | }; 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jesse Claven 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Arduino DDP library 2 | ================== 3 | 4 | A simple implementation of [DDP (version 1)](https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md) for Arduinos. 5 | 6 | _Primarily created for use in my [thesis project](https://github.com/jesse-c/thesis-sensor) for [CEIT](http://ceit.uq.edu.au) at [The University of Queensland](http://www.uq.edu.au). At the moment it's specific to my project though that will change._ 7 | 8 | 9 | Quick start 10 | ---------- 11 | 12 | ``` 13 | DDP ddp = DDP(); 14 | 15 | void setup() { 16 | if (ddp.setup("192.168.0.1", 3000)) { 17 | if (ddp.connect()) { 18 | } 19 | } 20 | } 21 | 22 | void loop() { 23 | ddp.sub(); 24 | ddp.listen(); 25 | } 26 | ``` 27 | 28 | [Being used in my thesis project](https://github.com/jesse-c/thesis-sensor) 29 | 30 | Documentation 31 | ------------- 32 | 33 | #### Connecting 34 | bool setup(String host, String path ="/", int port = 80) 35 | Partialyl implemented. `host` params not currently used. 36 | 37 | bool connect() 38 | Im 39 | 40 | #### Listening 41 | void listen() 42 | After a connection has successfully been made, `listen()` checks for any data on the line and if any is found, what type of message it was. It then calls the appropriate function. 43 | 44 | #### Heartbeats 45 | void ping(String id = "") 46 | Partially implemented—doesn't yet response with ID if one was included. 47 | 48 | void pong(String id = "") 49 | Partially implemented—doesn't yet response with ID if one was included. 50 | 51 | #### Managing data 52 | #### Client → Server 53 | void sub() 54 | Partiually implemented 55 | 56 | void unsub() 57 | Not yet implemeneted 58 | 59 | #### Server → Client 60 | void nosub() 61 | Not yet implemeneted 62 | 63 | void added() 64 | Partially implemented. 65 | 66 | void changed() 67 | Partially implemented. 68 | 69 | void removed() 70 | Not yet implemeneted 71 | 72 | void ready() 73 | Partially implemented. 74 | 75 | void addedBefore() 76 | Not yet implemeneted 77 | 78 | void movedBefore() 79 | Not yet implemeneted 80 | 81 | #### Remote procedure calls 82 | #### Client → Server 83 | void method() 84 | Partially implemented. 85 | 86 | #### Server → Client 87 | void result() 88 | Partially implemented. 89 | 90 | void updated() 91 | Partially implemented. 92 | 93 | Dependencies 94 | ----------- 95 | 96 | [ArduinoJson](https://github.com/bblanchon/ArduinoJson) 97 | 98 | [Arduino-Websocket](https://github.com/brandenhall/Arduino-Websocket) 99 | 100 | At the moment these work by using absolute paths for them which is not ideal. 101 | 102 | You'll need to download them locally. You can attempt to use them included as 103 | submodules. Run `git submodule update --recursive`. 104 | 105 | Notes 106 | ----- 107 | n/a 108 | 109 | Compatability 110 | ------------ 111 | Tested on: 112 | 113 | - Arduino Mega 2560 114 | 115 | TODO 116 | ---- 117 | - Add tests 118 | 119 | Contributing 120 | ------------- 121 | Contributions are welcome! 122 | --------------------------------------------------------------------------------