├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── docs ├── breadboard │ ├── led-rgb.fzz │ ├── led-rgb.png │ ├── servo.fzz │ └── servo.png ├── led-rgb.md ├── servo-continuous.md ├── servo-keypress.md ├── servo-prompt.md └── servo.md ├── eg ├── .gitignore ├── analog-multi.js ├── blink.js ├── board-multi.js ├── board.js ├── crash-test.js ├── i2c-blinkm.js ├── i2c-tmp102.js ├── internal-rgb-rainbow.js ├── led-rgb.js ├── ping-read.js ├── read.js ├── servo-continuous.js ├── servo-keypress.js ├── servo-prompt.js └── servo.js ├── lib └── particle.js ├── package.json ├── readme.md └── test └── particle.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.csv 3 | eg/read.local.js 4 | eg/write.local.js 5 | Gruntfile.js 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | before_script: 6 | - npm install -g grunt-cli 7 | compiler: clang 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var inspect = require("util").inspect, 2 | fs = require("fs"); 3 | 4 | 5 | module.exports = function(grunt) { 6 | 7 | var task = grunt.task; 8 | var file = grunt.file; 9 | var log = grunt.log; 10 | var verbose = grunt.verbose; 11 | var fail = grunt.fail; 12 | var option = grunt.option; 13 | var config = grunt.config; 14 | var template = grunt.template; 15 | var _ = grunt.util._; 16 | 17 | 18 | 19 | // Project configuration. 20 | grunt.initConfig({ 21 | pkg: grunt.file.readJSON("package.json"), 22 | nodeunit: { 23 | tests: [ 24 | "test/particle.js" 25 | ] 26 | }, 27 | jshint: { 28 | options: { 29 | curly: true, 30 | eqeqeq: true, 31 | esnext: true, 32 | immed: true, 33 | latedef: false, 34 | noarg: true, 35 | sub: true, 36 | undef: true, 37 | boss: true, 38 | eqnull: true, 39 | node: true, 40 | strict: false, 41 | globals: { 42 | exports: true, 43 | document: true, 44 | WeakMap: true, 45 | window: true, 46 | Promise: true 47 | } 48 | }, 49 | files: { 50 | src: [ 51 | "Gruntfile.js", 52 | "lib/**/*.js", 53 | "test/**/*.js", 54 | "eg/**/*.js" 55 | ] 56 | } 57 | }, 58 | 59 | jsbeautifier: { 60 | files: ["lib/**/*.js", "eg/**/*.js", "test/**/*.js"], 61 | options: { 62 | js: { 63 | braceStyle: "collapse", 64 | breakChainedMethods: false, 65 | e4x: false, 66 | evalCode: false, 67 | indentChar: " ", 68 | indentLevel: 0, 69 | indentSize: 2, 70 | indentWithTabs: false, 71 | jslintHappy: false, 72 | keepArrayIndentation: false, 73 | keepFunctionIndentation: false, 74 | maxPreserveNewlines: 10, 75 | preserveNewlines: true, 76 | spaceBeforeConditional: true, 77 | spaceInParen: false, 78 | unescapeStrings: false, 79 | wrapLineLength: 0 80 | } 81 | } 82 | }, 83 | watch: { 84 | src: { 85 | files: [ 86 | "Gruntfile.js", 87 | "lib/**/!(johnny-five)*.js", 88 | "test/**/*.js", 89 | "eg/**/*.js" 90 | ], 91 | tasks: ["default"], 92 | options: { 93 | interrupt: true, 94 | }, 95 | } 96 | } 97 | }); 98 | 99 | grunt.loadNpmTasks("grunt-contrib-watch"); 100 | grunt.loadNpmTasks("grunt-contrib-nodeunit"); 101 | grunt.loadNpmTasks("grunt-contrib-jshint"); 102 | grunt.loadNpmTasks("grunt-jsbeautifier"); 103 | 104 | grunt.registerTask("default", ["jshint", "nodeunit"]); 105 | 106 | // Explicit test task runs complete set of tests 107 | grunt.registerTask("test", ["jshint", "nodeunit"]); 108 | }; 109 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Rick Waldron 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /docs/breadboard/led-rgb.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/particle-io/08945e5bbaa2029b441833ea6d30ef420e9d54f4/docs/breadboard/led-rgb.fzz -------------------------------------------------------------------------------- /docs/breadboard/led-rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/particle-io/08945e5bbaa2029b441833ea6d30ef420e9d54f4/docs/breadboard/led-rgb.png -------------------------------------------------------------------------------- /docs/breadboard/servo.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/particle-io/08945e5bbaa2029b441833ea6d30ef420e9d54f4/docs/breadboard/servo.fzz -------------------------------------------------------------------------------- /docs/breadboard/servo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/particle-io/08945e5bbaa2029b441833ea6d30ef420e9d54f4/docs/breadboard/servo.png -------------------------------------------------------------------------------- /docs/led-rgb.md: -------------------------------------------------------------------------------- 1 | # Led Rgb 2 | 3 | Run with: 4 | ``` bash 5 | node eg/led-rgb.js 6 | ``` 7 | 8 | 9 | ``` javascript 10 | var five = require("johnny-five"), 11 | Particle = require("../lib/particle"), 12 | keypress = require('keypress'), 13 | board; 14 | 15 | 16 | // Create Johnny-Five board connected via Particle 17 | board = new five.Board({ 18 | io: new Particle({ 19 | token: process.env.PARTICLE_TOKEN, 20 | deviceId: process.env.PARTICLE_DEVICE_ID 21 | }) 22 | }); 23 | 24 | // The board's pins will not be accessible until 25 | // the board has reported that it is ready 26 | board.on("ready", function() { 27 | console.log("CONNECTED"); 28 | 29 | 30 | // Initialize the RGB LED 31 | var a = new five.Led.RGB({ 32 | pins: { 33 | red: "A5", 34 | green: "A6", 35 | blue: "A7" 36 | } 37 | }); 38 | 39 | // RGB LED alternate constructor 40 | // This will normalize an array of pins in [r, g, b] 41 | // order to an object (like above) that's shaped like: 42 | // { 43 | // red: r, 44 | // green: g, 45 | // blue: b 46 | // } 47 | //var a = new five.Led.RGB(["A5","A6","A7"]); 48 | 49 | // Turn it on and set the initial color 50 | a.on(); 51 | a.color("#FF0000"); 52 | 53 | // Listen for user input to change the RGB color 54 | process.stdin.resume(); 55 | process.stdin.setEncoding('utf8'); 56 | process.stdin.setRawMode(true); 57 | 58 | var keymap = { 59 | r: "#FF0000", // red 60 | g: "#00FF00", // green 61 | b: "#0000FF", // blue 62 | w: "#FFFFFF" // white 63 | }; 64 | 65 | process.stdin.on('keypress', function (ch, key) { 66 | 67 | if ( !key ) { 68 | return; 69 | } 70 | 71 | if (keymap[key.name]) { 72 | a.color(keymap[key.name]); 73 | a.on(); 74 | } else { 75 | a.off(); 76 | } 77 | 78 | }); 79 | 80 | }); 81 | 82 | board.on("error", function(error) { 83 | console.log(error); 84 | }); 85 | 86 | ``` 87 | 88 | 89 | ## Breadboard/Illustration 90 | 91 | 92 | ![docs/breadboard/led-rgb.png](breadboard/led-rgb.png) 93 | [docs/breadboard/led-rgb.fzz](breadboard/led-rgb.fzz) 94 | 95 | 96 | 97 | 98 | 99 | ## Contributing 100 | All contributions must adhere to the [Idiomatic.js Style Guide](https://github.com/rwldrn/idiomatic.js), 101 | by maintaining the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). 102 | 103 | ## License 104 | Copyright (c) 2012 Rick Waldron 105 | Licensed under the MIT license. -------------------------------------------------------------------------------- /docs/servo-continuous.md: -------------------------------------------------------------------------------- 1 | # Servo Continuous 2 | 3 | Run with: 4 | ``` bash 5 | node eg/servo-continuous.js 6 | ``` 7 | 8 | 9 | ``` javascript 10 | var five = require("johnny-five"), 11 | Particle = require("../lib/particle"), 12 | board; 13 | 14 | // Create Johnny-Five board connected via Particle 15 | board = new five.Board({ 16 | io: new Particle({ 17 | token: process.env.PARTICLE_TOKEN, 18 | deviceId: process.env.PARTICLE_DEVICE_ID 19 | }) 20 | }); 21 | 22 | // The board's pins will not be accessible until 23 | // the board has reported that it is ready 24 | board.on("ready", function() { 25 | 26 | // Create a new `servo` hardware instance. 27 | var servo = new five.Servo({ 28 | pin: "D0", 29 | // `type` defaults to standard servo. 30 | // For continuous rotation servos, override the default 31 | // by setting the `type` here 32 | type: "continuous" 33 | }); 34 | 35 | // Inject the `servo` hardware into 36 | // the Repl instance's context; 37 | // allows direct command line access 38 | board.repl.inject({ 39 | servo: servo 40 | }); 41 | 42 | // Continuous Rotation Servo API 43 | 44 | // cw( speed ) 45 | // clockWise( speed) 46 | // ccw( speed ) 47 | // counterClockwise( speed ) 48 | // 49 | // Set the speed at which the continuous rotation 50 | // servo will rotate at, either clockwise or counter 51 | // clockwise, respectively 52 | servo.cw(0.5); // half speed clockwise 53 | 54 | }); 55 | ``` 56 | 57 | 58 | ## Breadboard/Illustration 59 | 60 | 61 | ![docs/breadboard/servo.png](breadboard/servo.png) 62 | [docs/breadboard/servo.fzz](breadboard/servo.fzz) 63 | 64 | 65 | 66 | 67 | 68 | ## Contributing 69 | All contributions must adhere to the [Idiomatic.js Style Guide](https://github.com/rwldrn/idiomatic.js), 70 | by maintaining the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). 71 | 72 | ## License 73 | Copyright (c) 2012 Rick Waldron 74 | Licensed under the MIT license. -------------------------------------------------------------------------------- /docs/servo-keypress.md: -------------------------------------------------------------------------------- 1 | # Servo Keypress 2 | 3 | Run with: 4 | ``` bash 5 | node eg/servo-keypress.js 6 | ``` 7 | 8 | 9 | ``` javascript 10 | var five = require("johnny-five"); 11 | var Particle = require("../lib/particle"); 12 | var keypress = require('keypress'); 13 | 14 | keypress(process.stdin); 15 | 16 | var board = new five.Board({ 17 | io: new Particle({ 18 | token: process.env.PARTICLE_TOKEN, 19 | deviceId: process.env.PARTICLE_DEVICE_ID 20 | }) 21 | }); 22 | 23 | 24 | board.on("ready", function() { 25 | 26 | console.log("Let's test a simple servo. Use Up and Down arrows for CW and CCW respectively. Space to stop."); 27 | 28 | var servo = new five.Servo({ 29 | pin : "D0", 30 | type : 'continuous' 31 | }).stop(); 32 | 33 | process.stdin.resume(); 34 | process.stdin.setEncoding('utf8'); 35 | process.stdin.setRawMode(true); 36 | 37 | process.stdin.on('keypress', function (ch, key) { 38 | 39 | if ( !key ) { 40 | return; 41 | } 42 | 43 | if ( key.name === 'q' ) { 44 | 45 | console.log('Quitting'); 46 | process.exit(); 47 | 48 | } else if ( key.name === 'up' ) { 49 | 50 | console.log('CW'); 51 | servo.cw(); 52 | 53 | } else if ( key.name === 'down' ) { 54 | 55 | console.log('CCW'); 56 | servo.ccw(); 57 | 58 | } else if ( key.name === 'space' ) { 59 | 60 | console.log('Stopping'); 61 | servo.stop(); 62 | 63 | } 64 | 65 | }); 66 | 67 | }); 68 | ``` 69 | 70 | 71 | ## Breadboard/Illustration 72 | 73 | 74 | ![docs/breadboard/servo.png](breadboard/servo.png) 75 | [docs/breadboard/servo.fzz](breadboard/servo.fzz) 76 | 77 | 78 | 79 | 80 | 81 | ## Contributing 82 | All contributions must adhere to the [Idiomatic.js Style Guide](https://github.com/rwldrn/idiomatic.js), 83 | by maintaining the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). 84 | 85 | ## License 86 | Copyright (c) 2012 Rick Waldron 87 | Licensed under the MIT license. -------------------------------------------------------------------------------- /docs/servo-prompt.md: -------------------------------------------------------------------------------- 1 | # Servo Prompt 2 | 3 | Run with: 4 | ``` bash 5 | node eg/servo-prompt.js 6 | ``` 7 | 8 | 9 | ``` javascript 10 | var Particle = require('../lib/particle'); 11 | var readline = require('readline'); 12 | 13 | var rl = readline.createInterface({ 14 | input: process.stdin, 15 | output: process.stdout 16 | }); 17 | 18 | 19 | var board = new Particle({ 20 | token: process.env.PARTICLE_TOKEN, 21 | deviceId: process.env.PARTICLE_DEVICE_ID 22 | }); 23 | 24 | 25 | board.on('ready', function() { 26 | 27 | this.pinMode('D0', this.MODES.SERVO); 28 | 29 | rl.setPrompt('SERVO TEST (0-180)> '); 30 | rl.prompt(); 31 | 32 | rl.on('line', function(line) { 33 | var pos = line.trim(); 34 | board.servoWrite("D0", pos); 35 | rl.prompt(); 36 | }).on('close', function() { 37 | process.exit(0); 38 | }); 39 | 40 | }); 41 | 42 | ``` 43 | 44 | 45 | ## Breadboard/Illustration 46 | 47 | 48 | ![docs/breadboard/servo.png](breadboard/servo.png) 49 | [docs/breadboard/servo.fzz](breadboard/servo.fzz) 50 | 51 | 52 | 53 | 54 | 55 | ## Contributing 56 | All contributions must adhere to the [Idiomatic.js Style Guide](https://github.com/rwldrn/idiomatic.js), 57 | by maintaining the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). 58 | 59 | ## License 60 | Copyright (c) 2012 Rick Waldron 61 | Licensed under the MIT license. -------------------------------------------------------------------------------- /docs/servo.md: -------------------------------------------------------------------------------- 1 | # Servo 2 | 3 | Run with: 4 | ``` bash 5 | node eg/servo.js 6 | ``` 7 | 8 | 9 | ``` javascript 10 | var five = require("johnny-five"), 11 | Particle = require("../lib/particle"), 12 | temporal = require('temporal'), 13 | board; 14 | 15 | // Create Johnny-Five board connected via Particle 16 | board = new five.Board({ 17 | io: new Particle({ 18 | token: process.env.PARTICLE_TOKEN, 19 | deviceId: process.env.PARTICLE_DEVICE_ID 20 | }) 21 | }); 22 | 23 | // The board's pins will not be accessible until 24 | // the board has reported that it is ready 25 | board.on("ready", function() { 26 | console.log("CONNECTED"); 27 | 28 | // Create a new `servo` hardware instance. 29 | var servo = new five.Servo("D0"); 30 | 31 | temporal.queue([ 32 | { 33 | delay: 0, 34 | task: function(){ 35 | // min() 36 | // 37 | // set the servo to the minimum degrees 38 | // defaults to 0 39 | // 40 | servo.min(); 41 | } 42 | },{ 43 | delay: 1000, 44 | task: function(){ 45 | // max() 46 | // 47 | // set the servo to the maximum degrees 48 | // defaults to 180 49 | // 50 | servo.max(); 51 | } 52 | },{ 53 | delay: 1000, 54 | task: function(){ 55 | // center() 56 | // 57 | // centers the servo to 90° 58 | // 59 | servo.center(); 60 | } 61 | },{ 62 | delay: 1000, 63 | task: function(){ 64 | // sweep( obj ) 65 | // 66 | // Perform a min-max cycling servo sweep (defaults to 0-180) 67 | // optionally accepts an object of sweep settings: 68 | // { 69 | // lapse: time in milliseconds to wait between moves 70 | // defaults to 500ms 71 | // degrees: distance in degrees to move 72 | // defaults to 10° 73 | // } 74 | // 75 | servo.sweep(); 76 | } 77 | },{ 78 | delay: 5000, 79 | task: function(){ 80 | // stop( ) 81 | // 82 | // Stop a moving servo 83 | servo.stop(); 84 | } 85 | },{ 86 | delay: 1000, 87 | task: function(){ 88 | // to( deg ) 89 | // 90 | // Moves the servo to position by degrees 91 | // 92 | servo.to( 15 ); 93 | } 94 | },{ 95 | delay: 1000, 96 | task: function(){ 97 | // step ( deg ) 98 | // 99 | // Move servo relative to current position by degrees 100 | 101 | temporal.loop(500,function(){ 102 | if(this.called > 10){ 103 | process.exit(0); 104 | this.stop(); 105 | } 106 | 107 | servo.step( 15 ); 108 | }); 109 | 110 | } 111 | } 112 | ]); 113 | 114 | }); 115 | ``` 116 | 117 | 118 | ## Breadboard/Illustration 119 | 120 | 121 | ![docs/breadboard/servo.png](breadboard/servo.png) 122 | [docs/breadboard/servo.fzz](breadboard/servo.fzz) 123 | 124 | 125 | 126 | 127 | ## Contributing 128 | All contributions must adhere to the [Idiomatic.js Style Guide](https://github.com/rwldrn/idiomatic.js), 129 | by maintaining the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). 130 | 131 | ## License 132 | Copyright (c) 2012 Rick Waldron 133 | Licensed under the MIT license. -------------------------------------------------------------------------------- /eg/.gitignore: -------------------------------------------------------------------------------- 1 | blink.local.js 2 | write.local.js 3 | read.local.js 4 | -------------------------------------------------------------------------------- /eg/analog-multi.js: -------------------------------------------------------------------------------- 1 | var Particle = require("../lib/particle"); 2 | var board = new Particle({ 3 | token: process.env.PARTICLE_TOKEN, 4 | deviceId: process.env.PARTICLE_DEVICE_BLACK 5 | }); 6 | 7 | board.on("ready", function() { 8 | console.log("CONNECTED"); 9 | 10 | var pins = [ 11 | "A0", 12 | "A1" 13 | ]; 14 | 15 | pins.forEach(function(pin) { 16 | this.pinMode(pin, this.MODES.INPUT); 17 | this.analogRead(pin, function(data) { 18 | console.log(pin, data); 19 | }); 20 | }, this); 21 | }); 22 | -------------------------------------------------------------------------------- /eg/blink.js: -------------------------------------------------------------------------------- 1 | var Particle = require("../lib/particle"); 2 | var board = new Particle({ 3 | token: process.env.PARTICLE_TOKEN, 4 | deviceId: process.env.PARTICLE_PHOTON_1 5 | }); 6 | 7 | board.on("ready", function() { 8 | console.log("CONNECTED"); 9 | 10 | var byte = 0; 11 | 12 | this.pinMode("D7", this.MODES.OUTPUT); 13 | 14 | setInterval(function() { 15 | this.digitalWrite("D7", (byte ^= 1)); 16 | }.bind(this), 500); 17 | }); 18 | 19 | board.on("error", function(error) { 20 | console.log(error); 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /eg/board-multi.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"), 2 | Particle = require("../lib/Particle"), 3 | board; 4 | 5 | // Define array of available cores with appropriate DEVICE_IDs for each. 6 | // Note that the five.Boards(["ID1","ID2"]) shorthand doesn't work for 7 | // particle-io because we have to pass the io configuration object. 8 | // The five.Boards port option is also not applicable here. 9 | var cores = [ 10 | { 11 | id: "A", 12 | io: new Particle({ 13 | token: process.env.PARTICLE_TOKEN, 14 | deviceId: process.env.PARTICLE_DEVICE_ID_A 15 | }) 16 | },{ 17 | id: "B", 18 | io: new Particle({ 19 | token: process.env.PARTICLE_TOKEN, 20 | deviceId: process.env.PARTICLE_DEVICE_ID_B, 21 | }) 22 | } 23 | ]; 24 | 25 | // Create 2 particle board instances 26 | new five.Boards(cores).on("ready", function() { 27 | 28 | // Both "A" and "B" are initialized 29 | // (connected and available for communication) 30 | 31 | // |this| is an array-like object containing references 32 | // to each initialized board. 33 | this.each(function(board) { 34 | 35 | // Initialize an Led instance on default D7 LED of 36 | // each initialized particle and strobe it. 37 | new five.Led({ 38 | pin: "D7", 39 | board: board 40 | }).strobe(); 41 | 42 | }); 43 | 44 | }); -------------------------------------------------------------------------------- /eg/board.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"), 2 | Particle = require("../lib/particle"), 3 | board; 4 | 5 | // Create Johnny-Five board connected via Particle 6 | board = new five.Board({ 7 | io: new Particle({ 8 | token: process.env.PARTICLE_TOKEN, 9 | deviceId: process.env.PARTICLE_DEVICE_ID 10 | }) 11 | }); 12 | 13 | // The board's pins will not be accessible until 14 | // the board has reported that it is ready 15 | board.on("ready", function() { 16 | console.log("CONNECTED"); 17 | 18 | // Once connected, we can do normal Johnny-Five stuff 19 | var led = new five.Led("D7"); 20 | 21 | led.blink(); 22 | 23 | }); 24 | 25 | board.on("error", function(error) { 26 | console.log(error); 27 | }); 28 | -------------------------------------------------------------------------------- /eg/crash-test.js: -------------------------------------------------------------------------------- 1 | var moment = require("moment"); 2 | var Particle = require("../lib/particle"); 3 | var board = new Particle({ 4 | token: process.env.PARTICLE_TOKEN, 5 | deviceId: process.env.PARTICLE_DEVICE_ROBOTSCONF 6 | }); 7 | 8 | // node eg/crash-test.js 9 | // node eg/crash-test.js 8 100 digital 10 | 11 | var count = process.argv[2] || 8; 12 | var interval = process.argv[3] || 100; 13 | var type = process.argv[4] || "digital"; 14 | 15 | console.log(count, interval, type); 16 | board.on("ready", function() { 17 | console.log("CONNECTED"); 18 | 19 | this.setSamplingInterval(interval); 20 | 21 | var prefix = type[0].toUpperCase(); 22 | var mode = type === "analog" ? "ANALOG" : "INPUT"; 23 | var values = Array.from({length: count }); 24 | var last = Date.now(); 25 | 26 | 27 | Array.from({ length: count }, function(_, i) { return prefix + i; }).forEach(function(pin) { 28 | this.pinMode(pin, this.MODES[mode]); 29 | this[type + "Read"](pin, function(data) { 30 | // console.log(pin, data); 31 | 32 | var now = Date.now(); 33 | 34 | values[Number(pin.replace(prefix, ""))] = data; 35 | 36 | if (pin === (prefix + "7") && now > last + 1000) { 37 | last = now; 38 | console.log(values); 39 | console.log(moment(now).format("hh:mm:ss"), now); 40 | } 41 | 42 | }); 43 | }, this); 44 | }); 45 | -------------------------------------------------------------------------------- /eg/i2c-blinkm.js: -------------------------------------------------------------------------------- 1 | var Photon = require("../lib/particle"); 2 | var board = new Photon({ 3 | token: process.env.PARTICLE_TOKEN, 4 | deviceId: process.env.PARTICLE_PHOTON_1 5 | }); 6 | 7 | // http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf 8 | var BlinkM = { 9 | 0x09: "ADDRESS", 10 | 0x63: "FADE_TO_RGB", 11 | 0x43: "FADE_TO_RANDOM_RGB", 12 | 0x70: "SCRIPT_PLAY", 13 | 0x6f: "SCRIPT_STOP", 14 | 0x6e: "SET_RGB", 15 | 0x67: "GET_RGB", 16 | }; 17 | 18 | Object.keys(BlinkM).forEach(function(key) { 19 | // Turn the value into a key and 20 | // the key into an int value 21 | BlinkM[BlinkM[key]] = key | 0; 22 | }); 23 | 24 | var rgb = { 25 | red: [0xff, 0x00, 0x00], 26 | orange: [0xff, 0x7f, 0x00], 27 | yellow: [0xff, 0xff, 0x00], 28 | green: [0x00, 0xff, 0x00], 29 | blue: [0x00, 0x00, 0xff], 30 | indigo: [0x31, 0x00, 0x62], 31 | violet: [0x4b, 0x00, 0x82], 32 | white: [0xff, 0xff, 0xff], 33 | }; 34 | 35 | var rainbow = Object.keys(rgb).reduce(function(colors, color) { 36 | // While testing, I found that the BlinkM produced 37 | // more vibrant colors when provided a 7 bit value. 38 | return (colors[color] = rgb[color].map(to7bit), colors); 39 | }, {}); 40 | 41 | var colors = Object.keys(rainbow); 42 | var index = 0; 43 | 44 | board.on("ready", function() { 45 | console.log("READY"); 46 | 47 | this.i2cConfig(); 48 | this.i2cWrite(BlinkM.ADDRESS, BlinkM.SCRIPT_STOP); 49 | this.i2cWrite(BlinkM.ADDRESS, BlinkM.SET_RGB, [0, 0, 0]); 50 | 51 | var time = Date.now(); 52 | var cycle = function() { 53 | var color = colors[index++]; 54 | 55 | this.i2cWrite(BlinkM.ADDRESS, BlinkM.FADE_TO_RGB, rainbow[color]); 56 | this.i2cReadOnce(BlinkM.ADDRESS, BlinkM.GET_RGB, 3, function(data) { 57 | console.log("RGB: [%s]", data); 58 | 59 | if (index === colors.length) { 60 | index = 0; 61 | } 62 | var now = Date.now(); 63 | var diff = 1000 - (now - time); 64 | 65 | time = now; 66 | 67 | if (diff < 0) { 68 | setImmediate(cycle); 69 | } else { 70 | setTimeout(cycle, diff); 71 | } 72 | }); 73 | }.bind(this); 74 | 75 | cycle(); 76 | }); 77 | 78 | function scale(value, inMin, inMax, outMin, outMax) { 79 | return (value - inMin) * (outMax - outMin) / 80 | (inMax - inMin) + outMin; 81 | } 82 | 83 | function to7bit(value) { 84 | return scale(value, 0, 255, 0, 127) | 0; 85 | } 86 | -------------------------------------------------------------------------------- /eg/i2c-tmp102.js: -------------------------------------------------------------------------------- 1 | var Photon = require("../lib/particle"); 2 | var board = new Photon({ 3 | token: process.env.PARTICLE_TOKEN, 4 | deviceId: process.env.PARTICLE_PHOTON_1 5 | }); 6 | 7 | board.on("ready", function() { 8 | console.log("READY"); 9 | 10 | this.i2cConfig(); 11 | 12 | this.i2cRead(0x48, 2, function(data) { 13 | var raw = ((data[0] << 8) | data[1]) >> 4; 14 | 15 | if (raw & (1 << 11)) { 16 | raw |= 0xF800; // Set bits 11 to 15 to 1s to get this reading into real twos compliment 17 | } 18 | 19 | raw = raw >> 15 ? ((raw ^ 0xFFFF) + 1) * -1 : raw; 20 | 21 | console.log(raw / 16); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /eg/internal-rgb-rainbow.js: -------------------------------------------------------------------------------- 1 | var Particle = require("../lib/particle"); 2 | var board = new Particle({ 3 | token: process.env.PARTICLE_TOKEN, 4 | deviceId: process.env.PARTICLE_DEVICE_ID 5 | }); 6 | 7 | board.on("ready", function() { 8 | var rainbow = ["FF0000", "FF7F00", "00FF00", "FFFF00", "0000FF", "4B0082", "8F00FF"]; 9 | var index = 0; 10 | 11 | setInterval(function() { 12 | if (index + 1 === rainbow.length) { 13 | index = 0; 14 | } 15 | this.internalRGB(rainbow[index++]); 16 | }.bind(this), 500); 17 | }); 18 | -------------------------------------------------------------------------------- /eg/led-rgb.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"), 2 | Particle = require("../lib/particle"), 3 | keypress = require("keypress"), 4 | board; 5 | 6 | 7 | // Create Johnny-Five board connected via Particle 8 | board = new five.Board({ 9 | io: new Particle({ 10 | token: process.env.PARTICLE_TOKEN, 11 | deviceId: process.env.PARTICLE_DEVICE_ID 12 | }) 13 | }); 14 | 15 | // The board's pins will not be accessible until 16 | // the board has reported that it is ready 17 | board.on("ready", function() { 18 | console.log("CONNECTED"); 19 | 20 | 21 | // Initialize the RGB LED 22 | var a = new five.Led.RGB({ 23 | pins: { 24 | red: "A5", 25 | green: "A6", 26 | blue: "A7" 27 | } 28 | }); 29 | 30 | // RGB LED alternate constructor 31 | // This will normalize an array of pins in [r, g, b] 32 | // order to an object (like above) that's shaped like: 33 | // { 34 | // red: r, 35 | // green: g, 36 | // blue: b 37 | // } 38 | //var a = new five.Led.RGB(["A5","A6","A7"]); 39 | 40 | // Turn it on and set the initial color 41 | a.on(); 42 | a.color("#FF0000"); 43 | 44 | // Listen for user input to change the RGB color 45 | process.stdin.resume(); 46 | process.stdin.setEncoding("utf8"); 47 | process.stdin.setRawMode(true); 48 | 49 | var keymap = { 50 | r: "#FF0000", // red 51 | g: "#00FF00", // green 52 | b: "#0000FF", // blue 53 | w: "#FFFFFF" // white 54 | }; 55 | 56 | process.stdin.on("keypress", function (ch, key) { 57 | 58 | if ( !key ) { 59 | return; 60 | } 61 | 62 | if (keymap[key.name]) { 63 | a.color(keymap[key.name]); 64 | a.on(); 65 | } else { 66 | a.off(); 67 | } 68 | 69 | }); 70 | 71 | }); 72 | 73 | board.on("error", function(error) { 74 | console.log(error); 75 | }); 76 | -------------------------------------------------------------------------------- /eg/ping-read.js: -------------------------------------------------------------------------------- 1 | var Photon = require("../lib/particle"); 2 | var board = new Photon({ 3 | token: process.env.PARTICLE_TOKEN, 4 | deviceId: process.env.PARTICLE_PHOTON_1, 5 | }); 6 | 7 | board.on("ready", function() { 8 | console.log("READY"); 9 | 10 | var continuousRead = function() { 11 | this.pingRead({ pin: "D3" }, function(duration) { 12 | console.log(duration); 13 | 14 | setTimeout(continuousRead, 65); 15 | }); 16 | }.bind(this); 17 | 18 | continuousRead(); 19 | }); 20 | -------------------------------------------------------------------------------- /eg/read.js: -------------------------------------------------------------------------------- 1 | var Particle = require("../lib/particle"); 2 | var board = new Particle({ 3 | token: process.env.PARTICLE_TOKEN, 4 | deviceId: process.env.PARTICLE_DEVICE_ID 5 | }); 6 | 7 | board.on("ready", function() { 8 | console.log("CONNECTED"); 9 | 10 | this.analogRead("A0", function(data) { 11 | console.log( "A0", data ); 12 | }); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /eg/servo-continuous.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"), 2 | Particle = require("../lib/particle"), 3 | board; 4 | 5 | // Create Johnny-Five board connected via Particle 6 | board = new five.Board({ 7 | io: new Particle({ 8 | token: process.env.PARTICLE_TOKEN, 9 | deviceId: process.env.PARTICLE_DEVICE_ID 10 | }) 11 | }); 12 | 13 | // The board's pins will not be accessible until 14 | // the board has reported that it is ready 15 | board.on("ready", function() { 16 | 17 | // Create a new `servo` hardware instance. 18 | var servo = new five.Servo({ 19 | pin: "D0", 20 | // `type` defaults to standard servo. 21 | // For continuous rotation servos, override the default 22 | // by setting the `type` here 23 | type: "continuous" 24 | }); 25 | 26 | // Inject the `servo` hardware into 27 | // the Repl instance's context; 28 | // allows direct command line access 29 | board.repl.inject({ 30 | servo: servo 31 | }); 32 | 33 | // Continuous Rotation Servo API 34 | 35 | // cw( speed ) 36 | // clockWise( speed) 37 | // ccw( speed ) 38 | // counterClockwise( speed ) 39 | // 40 | // Set the speed at which the continuous rotation 41 | // servo will rotate at, either clockwise or counter 42 | // clockwise, respectively 43 | servo.cw(0.5); // half speed clockwise 44 | 45 | }); -------------------------------------------------------------------------------- /eg/servo-keypress.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"); 2 | var Particle = require("../lib/particle"); 3 | var keypress = require('keypress'); 4 | 5 | keypress(process.stdin); 6 | 7 | var board = new five.Board({ 8 | io: new Particle({ 9 | token: process.env.PARTICLE_TOKEN, 10 | deviceId: process.env.Particle_DEVICE_ID 11 | }) 12 | }); 13 | 14 | 15 | board.on("ready", function() { 16 | 17 | console.log("Let's test a simple servo. Use Up and Down arrows for CW and CCW respectively. Space to stop."); 18 | 19 | var servo = new five.Servo({ 20 | pin : "D0", 21 | type : 'continuous' 22 | }).stop(); 23 | 24 | process.stdin.resume(); 25 | process.stdin.setEncoding('utf8'); 26 | process.stdin.setRawMode(true); 27 | 28 | process.stdin.on('keypress', function (ch, key) { 29 | 30 | if ( !key ) { 31 | return; 32 | } 33 | 34 | if ( key.name === 'q' ) { 35 | 36 | console.log('Quitting'); 37 | process.exit(); 38 | 39 | } else if ( key.name === 'up' ) { 40 | 41 | console.log('CW'); 42 | servo.cw(); 43 | 44 | } else if ( key.name === 'down' ) { 45 | 46 | console.log('CCW'); 47 | servo.ccw(); 48 | 49 | } else if ( key.name === 'space' ) { 50 | 51 | console.log('Stopping'); 52 | servo.stop(); 53 | 54 | } 55 | 56 | }); 57 | 58 | }); -------------------------------------------------------------------------------- /eg/servo-prompt.js: -------------------------------------------------------------------------------- 1 | var Particle = require('../lib/particle'); 2 | var readline = require('readline'); 3 | 4 | var rl = readline.createInterface({ 5 | input: process.stdin, 6 | output: process.stdout 7 | }); 8 | 9 | 10 | var board = new Particle({ 11 | token: process.env.PARTICLE_TOKEN, 12 | deviceId: process.env.PARTICLE_DEVICE_ID 13 | }); 14 | 15 | board.on('ready', function() { 16 | 17 | this.pinMode('D0', this.MODES.SERVO); 18 | 19 | rl.setPrompt('SERVO TEST (0-180)> '); 20 | rl.prompt(); 21 | 22 | rl.on('line', function(line) { 23 | var pos = line.trim(); 24 | board.servoWrite("D0", pos); 25 | rl.prompt(); 26 | }).on('close', function() { 27 | process.exit(0); 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /eg/servo.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"), 2 | Particle = require("../lib/particle"), 3 | temporal = require('temporal'), 4 | board; 5 | 6 | // Create Johnny-Five board connected via Particle 7 | board = new five.Board({ 8 | io: new Particle({ 9 | token: process.env.PARTICLE_TOKEN, 10 | deviceId: process.env.PARTICLE_DEVICE_ID 11 | }) 12 | }); 13 | 14 | 15 | // The board's pins will not be accessible until 16 | // the board has reported that it is ready 17 | board.on("ready", function() { 18 | console.log("CONNECTED"); 19 | 20 | // Create a new `servo` hardware instance. 21 | var servo = new five.Servo("D0"); 22 | 23 | temporal.queue([ 24 | { 25 | delay: 0, 26 | task: function(){ 27 | // min() 28 | // 29 | // set the servo to the minimum degrees 30 | // defaults to 0 31 | // 32 | servo.min(); 33 | } 34 | },{ 35 | delay: 1000, 36 | task: function(){ 37 | // max() 38 | // 39 | // set the servo to the maximum degrees 40 | // defaults to 180 41 | // 42 | servo.max(); 43 | } 44 | },{ 45 | delay: 1000, 46 | task: function(){ 47 | // center() 48 | // 49 | // centers the servo to 90° 50 | // 51 | servo.center(); 52 | } 53 | },{ 54 | delay: 1000, 55 | task: function(){ 56 | // sweep( obj ) 57 | // 58 | // Perform a min-max cycling servo sweep (defaults to 0-180) 59 | // optionally accepts an object of sweep settings: 60 | // { 61 | // lapse: time in milliseconds to wait between moves 62 | // defaults to 500ms 63 | // degrees: distance in degrees to move 64 | // defaults to 10° 65 | // } 66 | // 67 | servo.sweep(); 68 | } 69 | },{ 70 | delay: 5000, 71 | task: function(){ 72 | // stop( ) 73 | // 74 | // Stop a moving servo 75 | servo.stop(); 76 | } 77 | },{ 78 | delay: 1000, 79 | task: function(){ 80 | // to( deg ) 81 | // 82 | // Moves the servo to position by degrees 83 | // 84 | servo.to( 15 ); 85 | } 86 | },{ 87 | delay: 1000, 88 | task: function(){ 89 | // step ( deg ) 90 | // 91 | // Move servo relative to current position by degrees 92 | 93 | temporal.loop(500,function(){ 94 | if(this.called > 10){ 95 | process.exit(0); 96 | this.stop(); 97 | } 98 | 99 | servo.step( 15 ); 100 | }); 101 | 102 | } 103 | } 104 | ]); 105 | 106 | }); -------------------------------------------------------------------------------- /lib/particle.js: -------------------------------------------------------------------------------- 1 | require("es6-shim"); 2 | require("array-includes").shim(); 3 | 4 | var net = require("net"); 5 | var Emitter = require("events").EventEmitter; 6 | var https = require("https"); 7 | var priv = new Map(); 8 | 9 | var errors = { 10 | cloud: "Unable to connect to particle cloud.", 11 | firmware: "Unable to connect to the voodoospark firmware, has it been loaded?", 12 | instance: "Expected instance of Particle.", 13 | pwm: "PWM is only available on D0, D1, A0, A1, A4, A5, A6, A7" 14 | }; 15 | 16 | var pins = [ 17 | { id: "D0", modes: [0, 1, 3, 4] }, 18 | { id: "D1", modes: [0, 1, 3, 4] }, 19 | { id: "D2", modes: [0, 1, 3, 4] }, 20 | { id: "D3", modes: [0, 1, 3, 4] }, 21 | { id: "D4", modes: [0, 1] }, 22 | { id: "D5", modes: [0, 1] }, 23 | { id: "D6", modes: [0, 1] }, 24 | { id: "D7", modes: [0, 1] }, 25 | 26 | { id: "", modes: [] }, 27 | { id: "", modes: [] }, 28 | 29 | { id: "A0", modes: [0, 1, 2, 3, 4] }, 30 | { id: "A1", modes: [0, 1, 2, 3, 4] }, 31 | { id: "A2", modes: [0, 1, 2] }, 32 | { id: "A3", modes: [0, 1, 2] }, 33 | { id: "A4", modes: [0, 1, 2, 3, 4] }, 34 | { id: "A5", modes: [0, 1, 2, 3, 4] }, 35 | { id: "A6", modes: [0, 1, 2, 3, 4] }, 36 | { id: "A7", modes: [0, 1, 2, 3, 4] } 37 | ]; 38 | 39 | var modes = Object.freeze({ 40 | INPUT: 0, 41 | OUTPUT: 1, 42 | ANALOG: 2, 43 | PWM: 3, 44 | SERVO: 4, 45 | I2C: 6 46 | }); 47 | 48 | var modesMap = [ 49 | "INPUT", 50 | "OUTPUT", 51 | "ANALOG", 52 | "PWM", 53 | "SERVO", 54 | "I2C" 55 | ]; 56 | 57 | var DIGITAL_READ = 0x03; 58 | var ANALOG_READ = 0x04; 59 | var REPORTING = 0x05; 60 | var SAMPLE_INTERVAL = 0x06; 61 | var INTERNAL_RGB = 0x07; 62 | var I2C_CONFIG = 0x30; 63 | var I2C_WRITE = 0x31; 64 | var I2C_READ = 0x32; 65 | var I2C_READ_CONTINUOUS = 0x33; 66 | var I2C_REGISTER_NOT_SPECIFIED = 0xFF; 67 | var I2C_REPLY = 0x77; 68 | var PING_READ = 0x08; 69 | 70 | function service(token, deviceId, action) { 71 | var url = "https://api.particle.io/v1/devices"; 72 | 73 | if (deviceId) { 74 | url += "/" + deviceId; 75 | 76 | if (action) { 77 | url += "/" + action; 78 | } 79 | } 80 | 81 | url += "?access_token=" + token; 82 | 83 | return url; 84 | } 85 | 86 | function from7BitBytes(lsb, msb) { 87 | if (Array.isArray(lsb)) { 88 | msb = lsb[1]; 89 | lsb = lsb[0]; 90 | } 91 | var result = lsb | (msb << 0x07); 92 | return result >= 16383 ? -1 : result; 93 | } 94 | 95 | function to7BitBytes(value) { 96 | return [value & 0x7f, value >> 0x07 & 0x7f]; 97 | } 98 | 99 | 100 | function processReceived(board, data) { 101 | var dlength = data.length; 102 | var length, action, pin, pinName, pinIndex, port, 103 | lsb, msb, value, portValue, type, event, 104 | dataLength, i2cData, address, register; 105 | 106 | for (var i = 0; i < dlength; i++) { 107 | board.buffer.push(data.readUInt8(i)); 108 | } 109 | 110 | length = board.buffer.length; 111 | 112 | if (length >= 4) { 113 | 114 | while (length) { 115 | action = board.buffer.shift(); 116 | 117 | // Digital reads are allowed to be 118 | // reported on Analog pins 119 | // 120 | if (action === REPORTING) { 121 | pin = board.buffer.shift(); 122 | lsb = board.buffer.shift(); 123 | msb = board.buffer.shift(); 124 | 125 | value = from7BitBytes(lsb, msb); 126 | 127 | port = +pin; 128 | portValue = +value; 129 | 130 | for (var k = 0; k < 8; k++) { 131 | pinIndex = k + (10 * port); 132 | event = "digital-read-" + (port ? "A" : "D") + k; 133 | value = portValue & (1 << k) ? 1 : 0; 134 | 135 | if (typeof board._events[event] !== "undefined" && 136 | board.pins[pinIndex].value !== value) { 137 | board.pins[pinIndex].value = value; 138 | board.emit(event, value); 139 | } 140 | } 141 | } 142 | 143 | if (action === DIGITAL_READ || 144 | action === ANALOG_READ) { 145 | pin = board.buffer.shift(); 146 | lsb = board.buffer.shift(); 147 | msb = board.buffer.shift(); 148 | 149 | value = from7BitBytes(lsb, msb); 150 | 151 | if (action === ANALOG_READ) { 152 | pinName = "A" + (pin - 10); 153 | type = "analog"; 154 | 155 | // This shifts the value 2 places to the left 156 | // for compatibility with firmata's 10-bit ADC 157 | // analog values. In the future it might be nice 158 | // to allow some 159 | value >>= 2; 160 | } 161 | 162 | if (action === DIGITAL_READ) { 163 | pinName = "D" + pin; 164 | type = "digital"; 165 | } 166 | 167 | event = type + "-read-" + pinName; 168 | 169 | board.pins[pin].value = value; 170 | board.emit(event, value); 171 | } 172 | 173 | if (action === PING_READ) { 174 | pin = board.buffer.shift(); 175 | value = (board.buffer.shift() << 24) + 176 | (board.buffer.shift() << 16) + 177 | (board.buffer.shift() << 8) + 178 | board.buffer.shift(); 179 | 180 | pinName = "D" + pin; 181 | 182 | board.pins[pin].value = value; 183 | board.emit("ping-read-" + pin, value); 184 | } 185 | 186 | if (action === I2C_REPLY) { 187 | dataLength = board.buffer.shift(); 188 | address = board.buffer.shift(); 189 | lsb = board.buffer.shift(); 190 | msb = board.buffer.shift(); 191 | 192 | register = from7BitBytes(lsb, msb); 193 | 194 | event = "I2C-reply-" + address; 195 | 196 | if (register !== I2C_REGISTER_NOT_SPECIFIED) { 197 | event += "-" + register; 198 | } 199 | 200 | i2cData = []; 201 | for (i = 0; i < dataLength; i++) { 202 | i2cData.push(board.buffer.shift()); 203 | } 204 | 205 | board.emit(event, i2cData); 206 | } 207 | 208 | length = board.buffer.length; 209 | } 210 | } 211 | } 212 | 213 | function startReading(state) { 214 | if (!state.isReading) { 215 | state.isReading = true; 216 | state.socket.on("data", function(data) { 217 | processReceived(this, data); 218 | }.bind(this)); 219 | } 220 | } 221 | 222 | function Particle(opts) { 223 | Emitter.call(this); 224 | 225 | if (!(this instanceof Particle)) { 226 | return new Particle(opts); 227 | } 228 | 229 | var state = { 230 | isConnected: false, 231 | isReading: false, 232 | deviceId: opts.deviceId, 233 | deviceName: opts.deviceName, 234 | token: opts.token, 235 | host: opts.host || null, 236 | port: opts.port || 8001, 237 | client: null, 238 | socket: null, 239 | rgb: { 240 | red: null, 241 | green: null, 242 | blue: null 243 | } 244 | }; 245 | 246 | this.name = "particle-io"; 247 | this.buffer = []; 248 | this.isReady = false; 249 | 250 | this.pins = pins.map(function(pin) { 251 | return { 252 | name: pin.id, 253 | supportedModes: pin.modes, 254 | mode: pin.modes[0], 255 | value: 0 256 | }; 257 | }); 258 | 259 | this.analogPins = this.pins.slice(10).map(function(pin, i) { 260 | return i; 261 | }); 262 | 263 | // Store private state 264 | priv.set(this, state); 265 | 266 | var afterCreate = function(error) { 267 | if (error) { 268 | this.emit("error", error); 269 | } else { 270 | state.isConnected = true; 271 | this.emit("connect"); 272 | } 273 | }.bind(this); 274 | 275 | if (state.host && state.port) { 276 | setTimeout(function() { 277 | Particle.Client.create(this, afterCreate); 278 | }.bind(this), 0); 279 | } else { 280 | this.connect(function(error, data) { 281 | // console.log( "connect -> connect -> handler" ); 282 | 283 | if (error !== undefined && error !== null) { 284 | this.emit("error", error); 285 | } else if (data.cmd !== "VarReturn") { 286 | this.emit("error", errors.firmware); 287 | } else { 288 | var address = data.result.split(":"); 289 | state.host = address[0]; 290 | state.port = parseInt(address[1], 10); 291 | // Moving into after connect so we can obtain the ip address 292 | Particle.Client.create(this, afterCreate); 293 | } 294 | }.bind(this)); 295 | } 296 | } 297 | 298 | 299 | Particle.Client = { 300 | create: function(particle, afterCreate) { 301 | if (!(particle instanceof Particle)) { 302 | throw new Error(errors.instance); 303 | } 304 | var state = priv.get(particle); 305 | var connection = { 306 | host: state.host, 307 | port: state.port 308 | }; 309 | 310 | state.socket = net.connect(connection, function() { 311 | // Set ready state bit 312 | particle.isReady = true; 313 | particle.emit("ready"); 314 | 315 | startReading.call(particle, state); 316 | }); 317 | 318 | afterCreate(); 319 | } 320 | }; 321 | 322 | Particle.prototype = Object.create(Emitter.prototype, { 323 | constructor: { 324 | value: Particle 325 | }, 326 | MODES: { 327 | value: modes 328 | }, 329 | HIGH: { 330 | value: 1 331 | }, 332 | LOW: { 333 | value: 0 334 | }, 335 | deviceId: { 336 | get: function() { 337 | return priv.get(this).deviceId || "UNKNOWN"; 338 | } 339 | }, 340 | deviceName: { 341 | get: function() { 342 | return priv.get(this).deviceName || "UNKNOWN"; 343 | } 344 | }, 345 | deviceIp: { 346 | get: function() { 347 | return priv.get(this).host || "UNKNOWN"; 348 | } 349 | }, 350 | devicePort: { 351 | get: function() { 352 | return priv.get(this).port || "UNKNOWN"; 353 | } 354 | } 355 | }); 356 | 357 | function fetchJson(url) { 358 | return new Promise(function(resolve, reject) { 359 | https.get(url, function(res) { 360 | var body = "", err; 361 | 362 | res.on("data", function(d) { 363 | body += d; 364 | }); 365 | 366 | res.on("end", function () { 367 | if (res.statusCode === 200) { 368 | var data = JSON.parse(body); 369 | 370 | if (data.error) { 371 | reject("ERROR: " + data.code + " " + data.error_description); 372 | } else { 373 | resolve(data); 374 | } 375 | } else { 376 | reject(errors.cloud + ": code: " + res.statusCode + " " + res.statusMessage); 377 | } 378 | }); 379 | }); 380 | }); 381 | } 382 | 383 | function fetchDeviceId(token, deviceName) { 384 | var url = service(token); 385 | 386 | return fetchJson(url) 387 | .then(function(devices) { 388 | var device = devices.find(function(d) { 389 | return d.name === deviceName; 390 | }); 391 | 392 | if (device) { 393 | return device.id; 394 | } else { 395 | return Promise.reject("Unable to find device " + deviceName); 396 | } 397 | }); 398 | } 399 | 400 | Particle.prototype.fetchEndpoint = function() { 401 | var state = priv.get(this); 402 | var promise = state.deviceId ? 403 | Promise.resolve(state.deviceId) : 404 | fetchDeviceId(state.token, state.deviceName).then(function(deviceId) { 405 | return state.deviceId = deviceId; 406 | }); 407 | 408 | return promise.then(function(deviceId) { 409 | var url = service(state.token, state.deviceId, "endpoint"); 410 | return fetchJson(url); 411 | }); 412 | }; 413 | 414 | Particle.prototype.connect = function(handler) { 415 | var state = priv.get(this); 416 | var err; 417 | 418 | if (state.isConnected) { 419 | return this; 420 | } 421 | 422 | this.fetchEndpoint() 423 | .then(function(data) { 424 | if(handler) { 425 | handler(null, data); 426 | } 427 | }) 428 | .catch(function(error) { 429 | if(handler) { 430 | handler(error, null); 431 | } 432 | }); 433 | }; 434 | 435 | Particle.prototype.pinMode = function(pin, mode) { 436 | var state = priv.get(this); 437 | var buffer; 438 | var offset; 439 | var pinInt; 440 | var sMode; 441 | 442 | sMode = mode = +mode; 443 | 444 | // Normalize when the mode is ANALOG (2) 445 | if (mode === 2) { 446 | // Normalize to pin string name if numeric pin 447 | if (typeof pin === "number") { 448 | pin = "A" + pin; 449 | } 450 | } 451 | 452 | // For PWM (3), writes will be executed via analogWrite 453 | if (mode === 3) { 454 | sMode = 1; 455 | } 456 | 457 | offset = pin[0] === "A" ? 10 : 0; 458 | pinInt = (String(pin).replace(/A|D/, "") | 0) + offset; 459 | 460 | // Throw if attempting to create a PWM or SERVO on an incapable pin 461 | // True PWM (3) is CONFIRMED available on: 462 | // 463 | // D0, D1, A0, A1, A5 464 | // 465 | // 466 | if (this.pins[pinInt].supportedModes.indexOf(mode) === -1) { 467 | throw new Error("Unsupported pin mode: " + modesMap[mode] + " for " + pin); 468 | } 469 | 470 | // Track the mode that user expects to see. 471 | this.pins[pinInt].mode = mode; 472 | 473 | // Send the coerced mode 474 | buffer = new Buffer([ 0x00, pinInt, sMode ]); 475 | 476 | // console.log(buffer); 477 | state.socket.write(buffer); 478 | 479 | return this; 480 | }; 481 | 482 | ["analogWrite", "digitalWrite", "servoWrite"].forEach(function(fn) { 483 | var isAnalog = fn === "analogWrite"; 484 | var isServo = fn === "servoWrite"; 485 | var action = isAnalog ? 0x02 : (isServo ? 0x41 : 0x01); 486 | 487 | Particle.prototype[fn] = function(pin, value) { 488 | var state = priv.get(this); 489 | var buffer = new Buffer(3); 490 | var offset = pin[0] === "A" ? 10 : 0; 491 | var pinInt = (String(pin).replace(/A|D/i, "") | 0) + offset; 492 | 493 | buffer[0] = action; 494 | buffer[1] = pinInt; 495 | buffer[2] = value; 496 | 497 | // console.log(buffer); 498 | state.socket.write(buffer); 499 | this.pins[pinInt].value = value; 500 | 501 | return this; 502 | }; 503 | }); 504 | 505 | // TODO: Define protocol for gather this information. 506 | ["analogRead", "digitalRead"].forEach(function(fn) { 507 | var isAnalog = fn === "analogRead"; 508 | // Use 0x05 to get a continuous read. 509 | var action = 0x05; 510 | // var action = isAnalog ? 0x04 : 0x03; 511 | // var offset = isAnalog ? 10 : 0; 512 | var value = isAnalog ? 2 : 1; 513 | var type = isAnalog ? "analog" : "digital"; 514 | 515 | Particle.prototype[fn] = function(pin, handler) { 516 | var state = priv.get(this); 517 | var buffer = new Buffer(3); 518 | var pinInt; 519 | var event; 520 | 521 | if (isAnalog && typeof pin === "number") { 522 | pin = "A" + pin; 523 | } 524 | var offset = pin[0] === "A" ? 10 : 0; 525 | pinInt = (String(pin).replace(/A|D/i, "") | 0) + offset; 526 | event = type + "-read-" + pin; 527 | 528 | buffer[0] = action; 529 | buffer[1] = pinInt; 530 | buffer[2] = value; 531 | 532 | // register a handler for 533 | this.on(event, handler); 534 | 535 | startReading.call(this, state); 536 | 537 | // Tell the board we have a new pin to read 538 | state.socket.write(buffer); 539 | 540 | return this; 541 | }; 542 | }); 543 | 544 | Particle.prototype.pingRead = function(opts, handler) { 545 | var state = priv.get(this); 546 | var buffer = new Buffer(2); 547 | var pin = opts.pin; 548 | var offset = pin[0] === "A" ? 10 : 0; 549 | var pinInt; 550 | var event; 551 | 552 | pinInt = (String(pin).replace(/A|D/i, "") | 0) + offset; 553 | event = "ping-read-" + pinInt; 554 | 555 | buffer[0] = PING_READ; 556 | buffer[1] = pinInt; 557 | 558 | this.once(event, handler); 559 | 560 | startReading.call(this, state); 561 | 562 | state.socket.write(buffer); 563 | 564 | return this; 565 | }; 566 | 567 | /** 568 | * I2C Support 569 | */ 570 | Particle.prototype.i2cConfig = function(options) { 571 | var state = priv.get(this); 572 | var delay; 573 | 574 | if (options === undefined) { 575 | options = 0; 576 | } 577 | 578 | // If this encounters an legacy calls to i2cConfig 579 | if (typeof options === "number") { 580 | delay = options; 581 | } else { 582 | delay = options.delay; 583 | } 584 | 585 | state.socket.write(new Buffer([I2C_CONFIG].concat(to7BitBytes(delay)))); 586 | }; 587 | 588 | Particle.prototype.i2cWrite = function(address, cmdRegOrData, dataBytes) { 589 | /** 590 | * cmdRegOrData: 591 | * [... arbitrary bytes] 592 | * 593 | * or 594 | * 595 | * cmdRegOrData, dataBytes: 596 | * command [, ...] 597 | * 598 | */ 599 | var state = priv.get(this); 600 | var register = cmdRegOrData; 601 | var dataToSend, payloadLength; 602 | 603 | // If i2cWrite was used for an i2cWriteReg call... 604 | if (arguments.length === 3 && 605 | typeof cmdRegOrData === "number" && 606 | typeof dataBytes === "number") { 607 | 608 | dataBytes = [dataBytes]; 609 | } 610 | 611 | // Fix arguments if called with Firmata.js API 612 | if (arguments.length === 2) { 613 | if (Array.isArray(cmdRegOrData)) { 614 | dataBytes = cmdRegOrData.slice(); 615 | register = dataBytes.shift(); 616 | } else { 617 | dataBytes = []; 618 | } 619 | } 620 | 621 | payloadLength = dataBytes.length * 2 + 2; 622 | 623 | dataToSend = [I2C_WRITE, address] 624 | .concat(to7BitBytes(payloadLength)) 625 | .concat(to7BitBytes(register)); 626 | 627 | dataBytes.forEach(function(byte) { 628 | dataToSend = dataToSend.concat(to7BitBytes(byte)); 629 | }); 630 | 631 | state.socket.write(new Buffer(dataToSend)); 632 | 633 | return this; 634 | }; 635 | 636 | Particle.prototype.i2cWriteReg = function(address, register, value) { 637 | return this.i2cWrite(address, [register, value]); 638 | }; 639 | 640 | Particle.prototype.i2cRead = function(address, register, bytesToRead, callback) { 641 | var state = priv.get(this); 642 | var event = "I2C-reply-" + address; 643 | var dataToSend; 644 | 645 | // Fix arguments if called with Firmata.js API 646 | if (arguments.length === 3 && 647 | typeof register === "number" && 648 | typeof bytesToRead === "function") { 649 | callback = bytesToRead; 650 | bytesToRead = register; 651 | register = null; 652 | } 653 | 654 | callback = typeof callback === "function" ? callback : function() {}; 655 | 656 | if (typeof register === "number") { 657 | event += "-" + register; 658 | } else { 659 | register = I2C_REGISTER_NOT_SPECIFIED; 660 | } 661 | 662 | this.on(event, callback); 663 | 664 | startReading.call(this, state); 665 | 666 | dataToSend = [I2C_READ_CONTINUOUS, address] 667 | .concat(to7BitBytes(register)) 668 | .concat(to7BitBytes(bytesToRead)); 669 | 670 | state.socket.write(new Buffer(dataToSend)); 671 | 672 | return this; 673 | }; 674 | 675 | Particle.prototype.i2cReadOnce = function(address, register, bytesToRead, callback) { 676 | var state = priv.get(this); 677 | var event = "I2C-reply-" + address; 678 | var dataToSend; 679 | 680 | // Fix arguments if called with Firmata.js API 681 | if (arguments.length === 3 && 682 | typeof register === "number" && 683 | typeof bytesToRead === "function") { 684 | callback = bytesToRead; 685 | bytesToRead = register; 686 | register = null; 687 | } 688 | 689 | callback = typeof callback === "function" ? callback : function() {}; 690 | 691 | if (typeof register === "number") { 692 | event += "-" + register; 693 | } else { 694 | register = I2C_REGISTER_NOT_SPECIFIED; 695 | } 696 | 697 | this.once(event, callback); 698 | 699 | startReading.call(this, state); 700 | 701 | dataToSend = [I2C_READ, address] 702 | .concat(to7BitBytes(register)) 703 | .concat(to7BitBytes(bytesToRead)); 704 | 705 | state.socket.write(new Buffer(dataToSend)); 706 | 707 | return this; 708 | }; 709 | 710 | // Necessary for Firmata.js compatibility. 711 | Particle.prototype.sendI2CWriteRequest = Particle.prototype.i2cWrite; 712 | Particle.prototype.sendI2CReadRequest = Particle.prototype.i2cReadOnce; 713 | Particle.prototype.sendI2CConfig = Particle.prototype.i2cConfig; 714 | 715 | /** 716 | * Compatibility Shimming 717 | */ 718 | Particle.prototype.setSamplingInterval = function(interval) { 719 | var state = priv.get(this); 720 | var safeInterval = Math.max(Math.min(Math.pow(2, 14) - 1, interval), 10); 721 | 722 | priv.get(this).interval = safeInterval; 723 | 724 | state.socket.write(new Buffer([SAMPLE_INTERVAL].concat(to7BitBytes(safeInterval)))); 725 | 726 | return this; 727 | }; 728 | 729 | 730 | Particle.prototype.internalRGB = function(red, green, blue) { 731 | var state = priv.get(this); 732 | var data = [INTERNAL_RGB]; 733 | var input, values; 734 | 735 | 736 | if (arguments.length === 0) { 737 | return Object.assign({}, state.rgb); 738 | } 739 | 740 | if (arguments.length === 1) { 741 | input = red; 742 | 743 | if (input === null || input === undefined) { 744 | throw new Error("Particle.internalRGB: invalid color (" + input + ")"); 745 | } 746 | 747 | if (typeof input === "object") { 748 | 749 | if (Array.isArray(input)) { 750 | // internalRGB([Byte, Byte, Byte]) 751 | values = input.slice(); 752 | } else { 753 | // internalRGB({ 754 | // red: Byte, 755 | // green: Byte, 756 | // blue: Byte 757 | // }); 758 | values = [ input.red, input.green, input.blue ]; 759 | } 760 | } else { 761 | 762 | if (typeof input === "string") { 763 | // internalRGB("#ffffff") 764 | if (input.length === 7 && input[0] === "#") { 765 | input = input.slice(1); 766 | } 767 | 768 | if (!input.match(/^[0-9A-Fa-f]{6}$/)) { 769 | throw new Error("Particle.internalRGB: invalid color (" + input + ")"); 770 | } 771 | 772 | // internalRGB("ffffff") 773 | values = [ 774 | parseInt(input.slice(0, 2), 16), 775 | parseInt(input.slice(2, 4), 16), 776 | parseInt(input.slice(4, 6), 16), 777 | ]; 778 | } 779 | } 780 | } else { 781 | // internalRGB(Byte, Byte, Byte) 782 | values = [red, green, blue]; 783 | } 784 | 785 | // Be sure we got all three values from one of the above signatures 786 | if (values.length !== 3 || values.includes(null) || values.includes(undefined)) { 787 | throw new Error("Particle.internalRGB: invalid color ([" + values.join(",") + "])"); 788 | } 789 | 790 | values = values.map(function(value) { 791 | return constrain(value, 0, 255); 792 | }); 793 | 794 | // Update internal state 795 | state.rgb.red = values[0]; 796 | state.rgb.green = values[1]; 797 | state.rgb.blue = values[2]; 798 | 799 | 800 | // Send buffer over wire 801 | state.socket.write(new Buffer(data.concat(values))); 802 | return this; 803 | }; 804 | 805 | Particle.prototype.reset = function() { 806 | return this; 807 | }; 808 | 809 | Particle.prototype.close = function() { 810 | var state = priv.get(this); 811 | state.socket.close(); 812 | state.server.close(); 813 | }; 814 | 815 | function constrain(value, lower, upper) { 816 | return Math.min(upper, Math.max(lower, value)); 817 | } 818 | 819 | 820 | module.exports = Particle; 821 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "particle-io", 3 | "description": "Particle Core & Photon IO Plugin for Johnny-Five", 4 | "version": "0.14.0", 5 | "homepage": "https://github.com/rwaldron/particle-io", 6 | "author": { 7 | "name": "Rick Waldron ", 8 | "email": "waldron.rick@gmail.com" 9 | }, 10 | "contributors": [ 11 | { 12 | "name": "Chris Williams", 13 | "email": "" 14 | }, 15 | { 16 | "name": "David Resseguie", 17 | "email": "" 18 | }, 19 | { 20 | "name": "Pawel Szymczykowski", 21 | "email": "" 22 | }, 23 | { 24 | "name": "Remy Sharp", 25 | "email": "" 26 | }, 27 | { 28 | "name": "Ron Evans", 29 | "email": "" 30 | }, 31 | { 32 | "name": "Brian Genisio", 33 | "email": "" 34 | } 35 | ], 36 | "keywords": [ 37 | "particle", 38 | "spark", 39 | "io", 40 | "arduino", 41 | "firmata", 42 | "johnny-five" 43 | ], 44 | "repository": { 45 | "type": "git", 46 | "url": "git://github.com/rwaldron/particle-io.git" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/rwaldron/particle-io/issues" 50 | }, 51 | "licenses": [ 52 | { 53 | "type": "MIT", 54 | "url": "https://github.com/rwaldron/particle-io/blob/master/LICENSE-MIT" 55 | } 56 | ], 57 | "main": "lib/particle", 58 | "engines": { 59 | "node": ">=0.10.0" 60 | }, 61 | "dependencies": { 62 | "array-includes": "latest", 63 | "es6-shim": "latest" 64 | }, 65 | "devDependencies": { 66 | "grunt": "~0.4.1", 67 | "grunt-contrib-jshint": "^1.0.0", 68 | "grunt-contrib-nodeunit": "^1.0.0", 69 | "grunt-contrib-watch": "^1.0.0", 70 | "grunt-jsbeautifier": "^0.2.13", 71 | "sinon": "^1.17.5" 72 | }, 73 | "scripts": { 74 | "test": "grunt" 75 | }, 76 | "js-flags": "--harmony" 77 | } 78 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Particle-io 2 | 3 | [![Build Status](https://travis-ci.org/rwaldron/particle-io.png?branch=master)](https://travis-ci.org/rwaldron/particle-io) 4 | 5 | Particle-io is a Firmata-compatibility IO class for writing node programs that interact with [Particle devices](http://docs.particle.io/) (formerly Spark). Particle-io was built at [Bocoup](http://bocoup.com/) 6 | 7 | ### Getting Started 8 | 9 | In order to use the particle-io library, you will need to load the special 10 | [voodoospark](https://github.com/voodootikigod/voodoospark) firmware onto your 11 | device. We recommend you review [VoodooSpark's Getting Started](https://github.com/voodootikigod/voodoospark#getting-started) before continuing. There is also a screencast of how to get started: [Get Your Motor Running: Particle Photon and Johnny Five](https://www.youtube.com/watch?v=jhism2iqT7o). 12 | 13 | We also recommend storing your Particle token and device ID in a dot file so they can be accessed as properties of `process.env`. Create a file in your home directory called `.particlerc` that contains: 14 | 15 | ```sh 16 | export PARTICLE_TOKEN="your particle token" 17 | export PARTICLE_DEVICE_ID="your device id" 18 | ``` 19 | 20 | Then add the following to your dot-rc file of choice: 21 | 22 | ```sh 23 | source ~/.particlerc 24 | ``` 25 | 26 | Ensure your host computer (where you're running your Node.js application) and the Particle are on the same local network. 27 | 28 | ### Blink an Led 29 | 30 | 31 | The "Hello World" of microcontroller programming: 32 | 33 | ```js 34 | var Particle = require("particle-io"); 35 | var board = new Particle({ 36 | token: process.env.PARTICLE_TOKEN, 37 | deviceId: process.env.PARTICLE_DEVICE_ID 38 | }); 39 | 40 | board.on("ready", function() { 41 | console.log("Device Ready.."); 42 | this.pinMode("D7", this.MODES.OUTPUT); 43 | 44 | var byte = 0; 45 | 46 | // This will "blink" the on board led 47 | setInterval(function() { 48 | this.digitalWrite("D7", (byte ^= 1)); 49 | }.bind(this), 500); 50 | }); 51 | ``` 52 | 53 | #### Troubleshooting 54 | 55 | If your board is connecting, but the `board.on("ready", ...)` event is not occuring, ensure the wifi network you are connected to allows for direct TCP client/server sockets (this form of communication is often blocked by public wifi networks). 56 | 57 | ### Johnny-Five IO Plugin 58 | 59 | Particle-io can be used as an [IO Plugin](https://github.com/rwaldron/johnny-five/wiki/IO-Plugins) for [Johnny-Five](https://github.com/rwaldron/johnny-five): 60 | 61 | ```js 62 | var five = require("johnny-five"); 63 | var Particle = require("particle-io"); 64 | var board = new five.Board({ 65 | io: new Particle({ 66 | token: process.env.PARTICLE_TOKEN, 67 | deviceId: process.env.PARTICLE_DEVICE_ID 68 | }) 69 | }); 70 | 71 | board.on("ready", function() { 72 | console.log("Device Ready.."); 73 | var led = new five.Led("D7"); 74 | led.blink(); 75 | }); 76 | ``` 77 | 78 | See the above [Troubleshooting](#troubleshooting) section if your board is connecting, but the `board.on("ready", ...)` event is not occuring. 79 | 80 | ### API 81 | **Constructor** 82 | The Particle component can be connected using one of three mechanisms: `deviceId`, `deviceName`, or `host`/`port`. Both `deviceId` and `deviceName` require an additional `token` so that the host and port of the device can be retrieved from the Particle cloud. 83 | 84 | Example: 85 | ```js 86 | var Particle = require("particle-io"); 87 | 88 | var byId = new Particle({ 89 | token: process.env.PARTICLE_TOKEN, 90 | deviceId: process.env.PARTICLE_DEVICE_ID 91 | }); 92 | 93 | var byName = new Particle({ 94 | token: process.env.PARTICLE_TOKEN, 95 | deviceName: process.env.PARTICLE_DEVICE_NAME || "crazy_pickle" 96 | }); 97 | 98 | var byIp = new Particle({ 99 | host: '192.168.0.111', 100 | port: 48879 101 | }); 102 | ``` 103 | 104 | **device properties** 105 | You can get the information that the component knows about the chip with some read-only device properties. This is useful for retrieving the IP address of the Particle so that you can swith over to `host`/`port` mode if necessary. 106 | 107 | The available properties are: 108 | - `deviceId`: The ID of the device 109 | - `deviceName`: The name of the device 110 | - `deviceIp`: The IP address of the device on the local network 111 | - `devicePort`: The port the device is listening on for connections 112 | 113 | Example: 114 | ```js 115 | var Particle = require("particle-io"); 116 | 117 | var board = new Particle({ 118 | token: process.env.PARTICLE_TOKEN, 119 | deviceName: process.env.PARTICLE_DEVICE_Name || "crazy_pickle" 120 | }); 121 | 122 | board.on("ready", function() { 123 | console.log( 124 | "Connected to " + board.deviceName + 125 | " (" + board.deviceId + ") " + 126 | "at " + board.deviceIp + ":" + board.devicePort 127 | ); 128 | }); 129 | ``` 130 | 131 | **MODES** 132 | 133 | > The `MODES` property is available as a Particle instance property: 134 | 135 | ```js 136 | var board = new Particle(...); 137 | board.MODES; 138 | ``` 139 | - INPUT: 0 140 | - OUTPUT: 1 141 | - ANALOG: 2 142 | - PWM: 3 143 | - SERVO: 4 144 | 145 | 146 | 147 | **pinMode(pin, MODE)** 148 | 149 | > Set a pin's mode to any one of the MODES. 150 | 151 | Example: 152 | ```js 153 | var board = new Particle(...); 154 | 155 | board.on("ready", function() { 156 | 157 | // Set digital pin 7 to OUTPUT: 158 | this.pinMode("D7", this.MODES.OUTPUT); 159 | 160 | // or just use the integer: 161 | this.pinMode("D7", 1); 162 | 163 | }); 164 | ``` 165 | 166 | PWM Support (Servo support is also limited to these pins): 167 | - Core pins: A0, A1, A4, A5, A6, A7, D0, D1. 168 | - Photon pins: A4, A5, D0, D1, D2, D3 169 | - P1 pins: A4, A5, D0, D1, D2, D3 170 | 171 | 172 | **digitalWrite(pin, value)** 173 | 174 | > Sets the pin to `1` or `0`, which either connects it to 3.3V (the maximum voltage of the system) or to GND (ground). 175 | 176 | Example: 177 | ```js 178 | var board = new Particle(...); 179 | 180 | board.on("ready", function() { 181 | 182 | // This will turn ON the on-board LED 183 | this.digitalWrite("D7", 1); 184 | 185 | // OR... 186 | 187 | // This will turn OFF the on-board LED 188 | this.digitalWrite("D7", 0); 189 | 190 | }); 191 | ``` 192 | 193 | **analogWrite(pin, value)** 194 | 195 | > Sets the pin to an 8-bit value between 0 and 255, where 0 is the same as LOW and 255 is the same as HIGH. This is sort of like sending a voltage between 0 and 3.3V, but since this is a digital system, it uses a mechanism called Pulse Width Modulation, or PWM. You could use analogWrite to dim an LED, as an example. PWM is available on D0, D1, A0, A1, A4, A5, A6 and A7. 196 | 197 | 198 | Example: 199 | ```js 200 | var board = new Particle(...); 201 | 202 | board.on("ready", function() { 203 | 204 | // Set an LED to full brightness 205 | this.analogWrite("A7", 255); 206 | 207 | // OR... 208 | 209 | // Set an LED to half brightness 210 | this.analogWrite("A7", 128); 211 | 212 | }); 213 | ``` 214 | 215 | **servoWrite(pin, value)** 216 | 217 | > Sets the pin to a value between 0 and 180, where the value represents degrees of the servo horn. The value is converted to a PWM signal. PWM is available on D0, D1, A0, A1, A4, A5, A6 and A7. 218 | 219 | Example: 220 | ```js 221 | var board = new Particle(...); 222 | 223 | board.on("ready", function() { 224 | 225 | // Move a servo to 90 degrees 226 | this.servoWrite("D0", 90); 227 | 228 | }); 229 | ``` 230 | 231 | 232 | **digitalRead(pin, handler)** Setup a continuous read handler for specific digital pin (D0-D7). 233 | 234 | > This will read the digital value of a pin, which can be read as either HIGH or LOW. If you were to connect the pin to a 3.3V source, it would read HIGH (1); if you connect it to GND, it would read LOW (0). 235 | 236 | Example: 237 | ```js 238 | var board = new Particle(...); 239 | 240 | board.on("ready", function() { 241 | 242 | // Log all the readings for D1 243 | this.digitalRead("D1", function(data) { 244 | console.log(data); 245 | }); 246 | 247 | }); 248 | ``` 249 | 250 | 251 | **analogRead(pin, handler)** Setup a continuous read handler for specific analog pin (A0-A7). Use with all analog sensors 252 | 253 | 254 | Example: 255 | ```js 256 | var board = new Particle(...); 257 | 258 | board.on("ready", function() { 259 | 260 | // Log all the readings for A1 261 | this.analogRead("A1", function(data) { 262 | console.log(data); 263 | }); 264 | 265 | }); 266 | ``` 267 | 268 | 269 | ## License 270 | See LICENSE file. 271 | -------------------------------------------------------------------------------- /test/particle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Particle = require("../lib/particle"); 4 | var Emitter = require("events").EventEmitter; 5 | var sinon = require("sinon"); 6 | var ParticleAPIVariable = {cmd: "VarReturn", result: "127.0.0.1:48879"}; 7 | 8 | 9 | function setupParticle(test) { 10 | test.clock = sinon.useFakeTimers(); 11 | 12 | test.state = new State(); 13 | test.map = sinon.stub(Map.prototype, "get").returns(test.state); 14 | test.socketwrite = sinon.spy(test.state.socket, "write"); 15 | test.connect = sinon.stub(Particle.prototype, "connect", function(handler) { 16 | handler(null, {cmd: "VarReturn", result: "127.0.0.1:48879"}); 17 | }); 18 | 19 | return new Particle({ 20 | token: "token", 21 | deviceId: "deviceId" 22 | }); 23 | } 24 | 25 | function restore(target) { 26 | for (var prop in target) { 27 | if (target[prop] != null && 28 | typeof target[prop].restore === "function") { 29 | target[prop].restore(); 30 | } 31 | if (typeof target[prop] === "object") { 32 | restore(target[prop]); 33 | } 34 | } 35 | } 36 | 37 | 38 | function State() { 39 | this.isConnected = false; 40 | this.isReading = false; 41 | this.deviceId = "deviceId"; 42 | this.token = "token"; 43 | this.service = "service"; 44 | this.port = 9000; 45 | this.server = {}; 46 | this.socket = new Emitter(); 47 | this.socket.write = function() {}; 48 | this.rgb = { 49 | red: null, 50 | green: null, 51 | blue: null 52 | }; 53 | } 54 | 55 | sinon.stub(Particle.Client, "create", function(particle, onCreated) { 56 | process.nextTick(function() { 57 | particle.emit("ready"); 58 | }); 59 | process.nextTick(onCreated); 60 | }); 61 | 62 | exports["Particle"] = { 63 | setUp: function(done) { 64 | this.particle = setupParticle(this); 65 | 66 | this.proto = {}; 67 | 68 | this.proto.functions = [{ 69 | name: "analogRead" 70 | }, { 71 | name: "analogWrite" 72 | }, { 73 | name: "connect" 74 | }, { 75 | name: "digitalRead" 76 | }, { 77 | name: "digitalWrite" 78 | }, { 79 | name: "pinMode" 80 | }, { 81 | name: "servoWrite" 82 | }]; 83 | 84 | this.proto.objects = [{ 85 | name: "MODES" 86 | }]; 87 | 88 | this.proto.numbers = [{ 89 | name: "HIGH" 90 | }, { 91 | name: "LOW" 92 | }]; 93 | 94 | this.instance = [{ 95 | name: "pins" 96 | }, { 97 | name: "analogPins" 98 | }, { 99 | name: "deviceId" 100 | }, { 101 | name: "deviceName" 102 | }, { 103 | name: "deviceIp" 104 | }, { 105 | name: "devicePort" 106 | }]; 107 | 108 | done(); 109 | }, 110 | tearDown: function(done) { 111 | restore(this); 112 | done(); 113 | }, 114 | shape: function(test) { 115 | test.expect( 116 | this.proto.functions.length + 117 | this.proto.objects.length + 118 | this.proto.numbers.length + 119 | this.instance.length 120 | ); 121 | 122 | this.proto.functions.forEach(function(method) { 123 | test.equal(typeof this.particle[method.name], "function"); 124 | }, this); 125 | 126 | this.proto.objects.forEach(function(method) { 127 | test.equal(typeof this.particle[method.name], "object"); 128 | }, this); 129 | 130 | this.proto.numbers.forEach(function(method) { 131 | test.equal(typeof this.particle[method.name], "number"); 132 | }, this); 133 | 134 | this.instance.forEach(function(property) { 135 | test.notEqual(typeof this.particle[property.name], "undefined"); 136 | }, this); 137 | 138 | test.done(); 139 | }, 140 | readonly: function(test) { 141 | test.expect(7); 142 | 143 | test.equal(this.particle.HIGH, 1); 144 | 145 | test.throws(function() { 146 | this.particle.HIGH = 42; 147 | }); 148 | 149 | test.equal(this.particle.LOW, 0); 150 | 151 | test.throws(function() { 152 | this.particle.LOW = 42; 153 | }); 154 | 155 | test.deepEqual(this.particle.MODES, { 156 | INPUT: 0, 157 | OUTPUT: 1, 158 | ANALOG: 2, 159 | PWM: 3, 160 | SERVO: 4, 161 | I2C: 6 162 | }); 163 | 164 | test.throws(function() { 165 | this.particle.MODES.INPUT = 42; 166 | }); 167 | 168 | test.throws(function() { 169 | this.particle.MODES = 42; 170 | }); 171 | 172 | test.done(); 173 | }, 174 | emitter: function(test) { 175 | test.expect(1); 176 | test.ok(this.particle instanceof Emitter); 177 | test.done(); 178 | }, 179 | connected: function(test) { 180 | test.expect(1); 181 | 182 | this.particle.on("connect", function() { 183 | test.ok(true); 184 | test.done(); 185 | }); 186 | }, 187 | ready: function(test) { 188 | test.expect(1); 189 | 190 | this.particle.on("ready", function() { 191 | test.ok(true); 192 | test.done(); 193 | }); 194 | } 195 | }; 196 | 197 | [ 198 | "analogWrite", 199 | "digitalWrite", 200 | "analogRead", 201 | "digitalRead" 202 | ].forEach(function(fn) { 203 | var entry = "Particle.prototype." + fn; 204 | var action = fn.toLowerCase(); 205 | var isAnalog = action === "analogwrite" || action === "analogread"; 206 | 207 | var index = isAnalog ? 10 : 0; 208 | var pin = isAnalog ? "A0" : "D0"; 209 | // All reporting messages are received as: 210 | // 211 | // [action, pin, lsb, msb] 212 | // 213 | // Where lsb and msb are 7-bit bytes that represent a single value 214 | // 215 | // 216 | var receiving = new Buffer(isAnalog ? [4, 10, 127, 31] : [3, 0, 1, 0]); 217 | var sent, value, type; 218 | 219 | exports[entry] = { 220 | setUp: function(done) { 221 | this.particle = setupParticle(this); 222 | done(); 223 | }, 224 | tearDown: function(done) { 225 | restore(this); 226 | done(); 227 | } 228 | }; 229 | 230 | // *Read Tests 231 | if (/read/.test(action)) { 232 | type = isAnalog ? "analog" : "digital"; 233 | value = isAnalog ? 1023 : 1; 234 | // This triggers the "reporting" action to start 235 | sent = isAnalog ? 236 | [5, 10, 2] : // continuous, analog 0, analog 237 | [5, 0, 1]; // continuous, digital 0, digital 238 | 239 | exports[entry].data = function(test) { 240 | test.expect(4); 241 | 242 | var handler = function(data) { 243 | test.equal(data, value); 244 | test.done(); 245 | }; 246 | 247 | this.particle[fn](pin, handler); 248 | 249 | var buffer = this.socketwrite.args[0][0]; 250 | 251 | for (var i = 0; i < sent.length; i++) { 252 | test.equal(sent[i], buffer.readUInt8(i)); 253 | } 254 | 255 | this.state.socket.emit("data", receiving); 256 | }; 257 | 258 | exports[entry].handler = function(test) { 259 | test.expect(1); 260 | 261 | var handler = function(data) { 262 | test.equal(data, value); 263 | test.done(); 264 | }; 265 | 266 | this.particle[fn](pin, handler); 267 | this.state.socket.emit("data", receiving); 268 | }; 269 | 270 | exports[entry].event = function(test) { 271 | test.expect(1); 272 | 273 | var event = type + "-read-" + pin; 274 | 275 | this.particle.once(event, function(data) { 276 | test.equal(data, value); 277 | test.done(); 278 | }); 279 | 280 | var handler = function(data) {}; 281 | 282 | this.particle[fn](pin, handler); 283 | this.state.socket.emit("data", receiving); 284 | }; 285 | 286 | if (isAnalog) { 287 | exports[entry].analogPin = function(test) { 288 | 289 | test.expect(1); 290 | 291 | var handler = function(data) { 292 | test.equal(data, value); 293 | test.done(); 294 | }; 295 | 296 | // Analog read on pin 0 (zero), which is A0 or 10 297 | this.particle.analogRead(0, handler); 298 | this.state.socket.emit("data", receiving); 299 | }; 300 | } 301 | 302 | } else { 303 | // *Write Tests 304 | value = isAnalog ? 255 : 1; 305 | sent = isAnalog ? [2, 10, 255] : [1, 0, 1]; 306 | exports[entry].write = function(test) { 307 | test.expect(4); 308 | 309 | this.particle[fn](pin, value); 310 | 311 | test.ok(this.socketwrite.calledOnce); 312 | 313 | var buffer = this.socketwrite.args[0][0]; 314 | 315 | for (var i = 0; i < sent.length; i++) { 316 | test.equal(sent[i], buffer.readUInt8(i)); 317 | } 318 | 319 | test.done(); 320 | }; 321 | 322 | exports[entry].stored = function(test) { 323 | test.expect(1); 324 | 325 | this.particle[fn](pin, value); 326 | 327 | test.equal(this.particle.pins[index].value, value); 328 | 329 | test.done(); 330 | }; 331 | } 332 | }); 333 | 334 | 335 | exports["Particle.protototype.digitalRead (port value)"] = { 336 | setUp: function(done) { 337 | this.particle = setupParticle(this); 338 | done(); 339 | }, 340 | tearDown: function(done) { 341 | restore(this); 342 | done(); 343 | }, 344 | 345 | portValue: function(test) { 346 | test.expect(3); 347 | 348 | var spy = sinon.spy(); 349 | var high = new Buffer( 350 | // CMD, PORT, LSB, MSB 351 | [0x05, 0x00, 0x20, 0x00] 352 | ); 353 | 354 | var low = new Buffer( 355 | // CMD, PORT, LSB, MSB 356 | [0x05, 0x00, 0x10, 0x00] 357 | ); 358 | 359 | this.particle.pinMode("D5", this.particle.MODES.INPUT); 360 | this.particle.digitalRead("D5", spy); 361 | this.state.socket.emit("data", high); 362 | this.state.socket.emit("data", low); 363 | 364 | test.equal(spy.callCount, 2); 365 | test.ok(spy.firstCall.calledWith(1)); 366 | test.ok(spy.lastCall.calledWith(0)); 367 | 368 | test.done(); 369 | } 370 | }; 371 | 372 | function validateSent(test, buffer, sent) { 373 | test.equal(sent.length, buffer.length); 374 | 375 | for (var i = 0; i < sent.length; i++) { 376 | test.equal(sent[i], buffer.readUInt8(i)); 377 | } 378 | } 379 | 380 | exports["Particle.protototype.i2cConfig"] = { 381 | setUp: function(done) { 382 | this.particle = setupParticle(this); 383 | done(); 384 | }, 385 | tearDown: function(done) { 386 | restore(this); 387 | done(); 388 | }, 389 | noOptionsDefaultsToZero: function(test) { 390 | var particle = this.particle; 391 | test.expect(4); 392 | 393 | particle.i2cConfig(); 394 | 395 | validateSent(test, this.socketwrite.args[0][0], [ 396 | 0x30, // command 397 | 0x00, 0x00 // 7bit delay 398 | ]); 399 | 400 | test.done(); 401 | }, 402 | numericOptionSendsConfigWithDelay: function(test) { 403 | test.expect(4); 404 | 405 | this.particle.i2cConfig(55); 406 | 407 | validateSent(test, this.socketwrite.args[0][0], [ 408 | 0x30, // command 409 | 0x37, 0x00 // 7bit delay 410 | ]); 411 | 412 | test.done(); 413 | }, 414 | objectOptionSendsConfigWithDelay: function(test) { 415 | test.expect(4); 416 | 417 | this.particle.i2cConfig({ 418 | delay: 550 419 | }); 420 | 421 | validateSent(test, this.socketwrite.args[0][0], [ 422 | 0x30, // command 423 | 0x26, 0x04 // 7bit delay 424 | ]); 425 | 426 | test.done(); 427 | } 428 | }; 429 | 430 | exports["Particle.protototype.i2cWrite"] = { 431 | setUp: function(done) { 432 | this.particle = setupParticle(this); 433 | done(); 434 | }, 435 | tearDown: function(done) { 436 | restore(this); 437 | done(); 438 | }, 439 | singleValueGetsSent: function(test) { 440 | var address = 0x01; 441 | var register = 0x02; 442 | var value = 0xEE; 443 | 444 | test.expect(9); 445 | 446 | this.particle.i2cWrite(address, register, value); 447 | 448 | validateSent(test, this.socketwrite.args[0][0], [ 449 | 0x31, // command 450 | 0x01, // address 451 | 0x04, 0x00, // data length 452 | 0x02, 0x00, // register 453 | 0x6E, 0x01 // 7bit value 454 | ]); 455 | 456 | test.done(); 457 | }, 458 | multipleDataBytesGetSent: function(test) { 459 | var address = 0x11; 460 | var register = 0x22; 461 | var data = [0x01, 0xCC, 0xFF]; 462 | 463 | test.expect(13); 464 | 465 | this.particle.i2cWrite(address, register, data); 466 | 467 | validateSent(test, this.socketwrite.args[0][0], [ 468 | 0x31, // command 469 | 0x11, // address 470 | 0x08, 0x00, // data length 471 | 0x22, 0x00, // register 472 | 0x01, 0x00, // 7bit data[0] 473 | 0x4C, 0x01, // 7bit data[1] 474 | 0x7F, 0x01, // 7bit data[2] 475 | ]); 476 | 477 | test.done(); 478 | }, 479 | dataCallWithEmbeddedRegister: function(test) { 480 | var address = 0x33; 481 | var register = 0x44; 482 | var data = [register, 0xAA]; 483 | 484 | test.expect(9); 485 | 486 | this.particle.i2cWrite(address, data); 487 | 488 | validateSent(test, this.socketwrite.args[0][0], [ 489 | 0x31, // command 490 | 0x33, // address 491 | 0x04, 0x00, // data length 492 | 0x44, 0x00, // register 493 | 0x2A, 0x01, // 7bit data[0] 494 | ]); 495 | 496 | test.done(); 497 | }, 498 | registerWithoutData: function(test) { 499 | var address = 0x55; 500 | var register = 0x66; 501 | 502 | test.expect(7); 503 | 504 | this.particle.i2cWrite(address, register); 505 | 506 | validateSent(test, this.socketwrite.args[0][0], [ 507 | 0x31, // command 508 | 0x55, // address 509 | 0x02, 0x00, // data length 510 | 0x66, 0x00 // register 511 | ]); 512 | 513 | test.done(); 514 | } 515 | }; 516 | 517 | exports["Particle.protototype.i2cWriteReg"] = { 518 | setUp: function(done) { 519 | this.particle = setupParticle(this); 520 | this.particle.i2cWrite = sinon.spy(); 521 | done(); 522 | }, 523 | tearDown: function(done) { 524 | restore(this); 525 | done(); 526 | }, 527 | callsWriteWithRegData: function(test) { 528 | var address = 0x11; 529 | var register = 0x22; 530 | var value = 99; 531 | 532 | test.expect(1); 533 | 534 | this.particle.i2cWriteReg(address, register, value); 535 | 536 | test.ok(this.particle.i2cWrite.calledWith(address, [register, value])); 537 | 538 | test.done(); 539 | } 540 | }; 541 | 542 | exports["Particle.protototype.i2cRead"] = { 543 | setUp: function(done) { 544 | this.particle = setupParticle(this); 545 | done(); 546 | }, 547 | tearDown: function(done) { 548 | restore(this); 549 | done(); 550 | }, 551 | readWithRegister: function(test) { 552 | var address = 0x11; 553 | var register = 0x22; 554 | var bytesToRead = 4; 555 | var callback = sinon.spy(); 556 | 557 | test.expect(7); 558 | 559 | this.particle.i2cRead(address, register, bytesToRead, callback); 560 | 561 | validateSent(test, this.socketwrite.args[0][0], [ 562 | 0x33, // command 563 | 0x11, // address 564 | 0x22, 0x00, // register 565 | 0x04, 0x00 566 | ]); 567 | 568 | test.done(); 569 | }, 570 | 571 | readWithoutRegister: function(test) { 572 | var address = 0x11; 573 | var bytesToRead = 4; 574 | var callback = sinon.spy(); 575 | 576 | test.expect(7); 577 | 578 | this.particle.i2cRead(address, bytesToRead, callback); 579 | 580 | validateSent(test, this.socketwrite.args[0][0], [ 581 | 0x33, // command 582 | 0x11, // address 583 | 0x7F, 0x01, // no register, send 0xFF 584 | 0x04, 0x00 585 | ]); 586 | 587 | test.done(); 588 | }, 589 | receiveDataWithRegister: function(test) { 590 | var address = 0x11; 591 | var register = 0x22; 592 | var bytesToRead = 4; 593 | 594 | test.expect(1); 595 | 596 | var handler = function(data) { 597 | test.deepEqual(data, [0x11, 0x22, 0x33, 0x44]); 598 | test.done(); 599 | }; 600 | 601 | this.particle.i2cRead(address, register, bytesToRead, handler); 602 | 603 | this.state.socket.emit("data", new Buffer([ 604 | 0x77, // I2C_REPLY 605 | 0x04, // data length 606 | 0x11, // address 607 | 0x22, 0x00, // register 608 | 0x11, 0x22, 0x33, 0x44 // data 609 | ])); 610 | }, 611 | receiveDataWithoutRegister: function(test) { 612 | var address = 0x11; 613 | var bytesToRead = 4; 614 | 615 | test.expect(1); 616 | 617 | var handler = function(data) { 618 | test.deepEqual(data, [0x11, 0x22, 0x33, 0x44]); 619 | test.done(); 620 | }; 621 | 622 | this.particle.i2cRead(address, bytesToRead, handler); 623 | 624 | this.state.socket.emit("data", new Buffer([ 625 | 0x77, // I2C_REPLY 626 | 0x04, // data length 627 | 0x11, // address 628 | 0xFF, 0x00, // no register, receive dummy 0xFF 629 | 0x11, 0x22, 0x33, 0x44 // data 630 | ])); 631 | } 632 | }; 633 | 634 | exports["Particle.protototype.i2cReadOnce"] = { 635 | setUp: function(done) { 636 | this.particle = setupParticle(this); 637 | done(); 638 | }, 639 | tearDown: function(done) { 640 | restore(this); 641 | done(); 642 | }, 643 | readOnceWithRegister: function(test) { 644 | var address = 0x11; 645 | var register = 0x22; 646 | var bytesToRead = 4; 647 | var callback = sinon.spy(); 648 | 649 | test.expect(7); 650 | 651 | this.particle.i2cReadOnce(address, register, bytesToRead, callback); 652 | 653 | validateSent(test, this.socketwrite.args[0][0], [ 654 | 0x32, // command 655 | 0x11, // address 656 | 0x22, 0x00, // register 657 | 0x04, 0x00 658 | ]); 659 | 660 | test.done(); 661 | }, 662 | readOnceWithoutRegister: function(test) { 663 | var address = 0x11; 664 | var bytesToRead = 4; 665 | var callback = sinon.spy(); 666 | 667 | test.expect(7); 668 | 669 | this.particle.i2cReadOnce(address, bytesToRead, callback); 670 | 671 | validateSent(test, this.socketwrite.args[0][0], [ 672 | 0x32, // command 673 | 0x11, // address 674 | 0x7F, 0x01, // no register, send 0xFF 675 | 0x04, 0x00 676 | ]); 677 | 678 | test.done(); 679 | }, 680 | receiveDataWithRegister: function(test) { 681 | var address = 0x11; 682 | var register = 0x22; 683 | var bytesToRead = 4; 684 | 685 | test.expect(1); 686 | 687 | var handler = function(data) { 688 | test.deepEqual(data, [0x11, 0x22, 0x33, 0x44]); 689 | test.done(); 690 | }; 691 | 692 | this.particle.i2cReadOnce(address, register, bytesToRead, handler); 693 | 694 | this.state.socket.emit("data", new Buffer([ 695 | 0x77, // I2C_REPLY 696 | 0x04, // data length 697 | 0x11, // address 698 | 0x22, 0x00, // register 699 | 0x11, 0x22, 0x33, 0x44 // data 700 | ])); 701 | }, 702 | receiveDataWithoutRegister: function(test) { 703 | var address = 0x11; 704 | var bytesToRead = 4; 705 | 706 | test.expect(1); 707 | 708 | var handler = function(data) { 709 | test.deepEqual(data, [0x11, 0x22, 0x33, 0x44]); 710 | test.done(); 711 | }; 712 | 713 | this.particle.i2cReadOnce(address, bytesToRead, handler); 714 | 715 | this.state.socket.emit("data", new Buffer([ 716 | 0x77, // I2C_REPLY 717 | 0x04, // data length 718 | 0x11, // address 719 | 0xFF, 0x00, // no register, receive dummy 0xFF 720 | 0x11, 0x22, 0x33, 0x44 // data 721 | ])); 722 | } 723 | }; 724 | 725 | exports["Particle.prototype.servoWrite"] = { 726 | setUp: function(done) { 727 | this.particle = setupParticle(this); 728 | done(); 729 | }, 730 | tearDown: function(done) { 731 | restore(this); 732 | done(); 733 | }, 734 | analogWriteToDigital: function(test) { 735 | test.expect(3); 736 | 737 | var sent = [2, 0, 180]; 738 | 739 | this.particle.analogWrite("D0", 180); 740 | 741 | var buffer = this.socketwrite.args[0][0]; 742 | 743 | for (var i = 0; i < sent.length; i++) { 744 | test.equal(sent[i], buffer.readUInt8(i)); 745 | } 746 | test.done(); 747 | }, 748 | analogWriteToAnalog: function(test) { 749 | test.expect(3); 750 | 751 | var sent = [2, 10, 255]; 752 | 753 | this.particle.analogWrite("A0", 255); 754 | 755 | var buffer = this.socketwrite.args[0][0]; 756 | 757 | for (var i = 0; i < sent.length; i++) { 758 | test.equal(sent[i], buffer.readUInt8(i)); 759 | } 760 | test.done(); 761 | }, 762 | 763 | servoWriteDigital: function(test) { 764 | test.expect(3); 765 | 766 | var sent = [0x41, 0, 180]; 767 | 768 | this.particle.servoWrite("D0", 180); 769 | 770 | var buffer = this.socketwrite.args[0][0]; 771 | 772 | for (var i = 0; i < sent.length; i++) { 773 | test.equal(sent[i], buffer.readUInt8(i)); 774 | } 775 | test.done(); 776 | }, 777 | 778 | servoWriteAnalog: function(test) { 779 | test.expect(3); 780 | 781 | var sent = [0x41, 10, 180]; 782 | 783 | this.particle.servoWrite("A0", 180); 784 | 785 | var buffer = this.socketwrite.args[0][0]; 786 | 787 | for (var i = 0; i < sent.length; i++) { 788 | test.equal(sent[i], buffer.readUInt8(i)); 789 | } 790 | test.done(); 791 | } 792 | }; 793 | 794 | 795 | 796 | exports["Particle.prototype.pinMode"] = { 797 | setUp: function(done) { 798 | this.particle = setupParticle(this); 799 | done(); 800 | }, 801 | tearDown: function(done) { 802 | restore(this); 803 | done(); 804 | }, 805 | analogOutput: function(test) { 806 | test.expect(4); 807 | 808 | var sent = [0, 11, 1]; 809 | 810 | this.particle.pinMode("A1", 1); 811 | test.ok(this.socketwrite.calledOnce); 812 | 813 | var buffer = this.socketwrite.args[0][0]; 814 | 815 | for (var i = 0; i < sent.length; i++) { 816 | test.equal(sent[i], buffer.readUInt8(i)); 817 | } 818 | test.done(); 819 | }, 820 | analogInput: function(test) { 821 | test.expect(4); 822 | 823 | var sent = [0, 11, 0]; 824 | 825 | this.particle.pinMode("A1", 0); 826 | test.ok(this.socketwrite.calledOnce); 827 | 828 | var buffer = this.socketwrite.args[0][0]; 829 | 830 | for (var i = 0; i < sent.length; i++) { 831 | test.equal(sent[i], buffer.readUInt8(i)); 832 | } 833 | test.done(); 834 | }, 835 | 836 | analogInputMapped: function(test) { 837 | test.expect(4); 838 | 839 | var sent = [0, 11, 2]; 840 | 841 | this.particle.pinMode(1, 2); 842 | test.ok(this.socketwrite.calledOnce); 843 | 844 | var buffer = this.socketwrite.args[0][0]; 845 | 846 | for (var i = 0; i < sent.length; i++) { 847 | test.equal(sent[i], buffer.readUInt8(i)); 848 | } 849 | test.done(); 850 | }, 851 | 852 | digitalOutput: function(test) { 853 | test.expect(4); 854 | 855 | var sent = [0, 0, 1]; 856 | 857 | this.particle.pinMode("D0", 1); 858 | 859 | test.ok(this.socketwrite.calledOnce); 860 | 861 | var buffer = this.socketwrite.args[0][0]; 862 | 863 | for (var i = 0; i < sent.length; i++) { 864 | test.equal(sent[i], buffer.readUInt8(i)); 865 | } 866 | test.done(); 867 | }, 868 | 869 | digitalInput: function(test) { 870 | test.expect(4); 871 | 872 | var sent = [0, 0, 0]; 873 | 874 | this.particle.pinMode("D0", 0); 875 | 876 | test.ok(this.socketwrite.calledOnce); 877 | 878 | var buffer = this.socketwrite.args[0][0]; 879 | 880 | for (var i = 0; i < sent.length; i++) { 881 | test.equal(sent[i], buffer.readUInt8(i)); 882 | } 883 | test.done(); 884 | }, 885 | 886 | servo: function(test) { 887 | test.expect(4); 888 | 889 | var sent = [0, 0, 4]; 890 | 891 | this.particle.pinMode("D0", 4); 892 | 893 | test.ok(this.socketwrite.calledOnce); 894 | 895 | var buffer = this.socketwrite.args[0][0]; 896 | 897 | for (var i = 0; i < sent.length; i++) { 898 | test.equal(sent[i], buffer.readUInt8(i)); 899 | } 900 | test.done(); 901 | }, 902 | 903 | pwmCoercedToOutput: function(test) { 904 | test.expect(4); 905 | 906 | var sent = [0, 0, 1]; 907 | 908 | this.particle.pinMode("D0", 3); 909 | 910 | test.ok(this.socketwrite.calledOnce); 911 | 912 | var buffer = this.socketwrite.args[0][0]; 913 | 914 | for (var i = 0; i < sent.length; i++) { 915 | test.equal(sent[i], buffer.readUInt8(i)); 916 | } 917 | test.done(); 918 | }, 919 | 920 | pwmError: function(test) { 921 | test.expect(7); 922 | 923 | try { 924 | this.particle.pinMode("D0", 3); 925 | this.particle.pinMode("D1", 3); 926 | this.particle.pinMode("D2", 3); 927 | this.particle.pinMode("D3", 3); 928 | this.particle.pinMode("A0", 3); 929 | this.particle.pinMode("A1", 3); 930 | this.particle.pinMode("A4", 3); 931 | this.particle.pinMode("A5", 3); 932 | this.particle.pinMode("A6", 3); 933 | this.particle.pinMode("A7", 3); 934 | 935 | test.ok(true); 936 | } catch(e) { 937 | test.ok(false); 938 | } 939 | 940 | try { 941 | this.particle.pinMode("D4", 3); 942 | test.ok(false); 943 | } catch(e) { 944 | test.ok(true); 945 | } 946 | 947 | try { 948 | this.particle.pinMode("D5", 3); 949 | test.ok(false); 950 | } catch(e) { 951 | test.ok(true); 952 | } 953 | 954 | try { 955 | this.particle.pinMode("D6", 3); 956 | test.ok(false); 957 | } catch(e) { 958 | test.ok(true); 959 | } 960 | 961 | try { 962 | this.particle.pinMode("D7", 3); 963 | test.ok(false); 964 | } catch(e) { 965 | test.ok(true); 966 | } 967 | 968 | try { 969 | this.particle.pinMode("A2", 3); 970 | test.ok(false); 971 | } catch(e) { 972 | test.ok(true); 973 | } 974 | 975 | try { 976 | this.particle.pinMode("A3", 3); 977 | test.ok(false); 978 | } catch(e) { 979 | test.ok(true); 980 | } 981 | 982 | test.done(); 983 | }, 984 | 985 | servoError: function(test) { 986 | test.expect(7); 987 | 988 | try { 989 | this.particle.pinMode("D0", 4); 990 | this.particle.pinMode("D1", 4); 991 | this.particle.pinMode("D2", 4); 992 | this.particle.pinMode("D3", 4); 993 | this.particle.pinMode("A0", 4); 994 | this.particle.pinMode("A1", 4); 995 | this.particle.pinMode("A4", 4); 996 | this.particle.pinMode("A5", 4); 997 | this.particle.pinMode("A6", 4); 998 | this.particle.pinMode("A7", 4); 999 | 1000 | test.ok(true); 1001 | } catch(e) { 1002 | test.ok(false); 1003 | } 1004 | 1005 | try { 1006 | this.particle.pinMode("D4", 4); 1007 | test.ok(false); 1008 | } catch(e) { 1009 | test.ok(true); 1010 | } 1011 | 1012 | try { 1013 | this.particle.pinMode("D5", 4); 1014 | test.ok(false); 1015 | } catch(e) { 1016 | test.ok(true); 1017 | } 1018 | 1019 | try { 1020 | this.particle.pinMode("D6", 4); 1021 | test.ok(false); 1022 | } catch(e) { 1023 | test.ok(true); 1024 | } 1025 | 1026 | try { 1027 | this.particle.pinMode("D7", 4); 1028 | test.ok(false); 1029 | } catch(e) { 1030 | test.ok(true); 1031 | } 1032 | 1033 | try { 1034 | this.particle.pinMode("A2", 4); 1035 | test.ok(false); 1036 | } catch(e) { 1037 | test.ok(true); 1038 | } 1039 | 1040 | try { 1041 | this.particle.pinMode("A3", 4); 1042 | test.ok(false); 1043 | } catch(e) { 1044 | test.ok(true); 1045 | } 1046 | 1047 | test.done(); 1048 | } 1049 | }; 1050 | 1051 | exports["Particle.prototype.internalRGB"] = { 1052 | setUp: function(done) { 1053 | this.particle = setupParticle(this); 1054 | done(); 1055 | }, 1056 | tearDown: function(done) { 1057 | restore(this); 1058 | done(); 1059 | }, 1060 | 1061 | get: function(test) { 1062 | test.expect(3); 1063 | 1064 | test.deepEqual(this.particle.internalRGB(), { 1065 | red: null, green: null, blue: null 1066 | }); 1067 | test.ok(this.socketwrite.notCalled); 1068 | 1069 | this.particle.internalRGB(10, 20, 30); 1070 | test.deepEqual(this.particle.internalRGB(), { 1071 | red: 10, green: 20, blue: 30 1072 | }); 1073 | 1074 | test.done(); 1075 | }, 1076 | 1077 | setReturnsThis: function(test) { 1078 | test.expect(1); 1079 | 1080 | test.equal(this.particle.internalRGB(0, 0, 0), this.particle); 1081 | test.done(); 1082 | }, 1083 | 1084 | setWithThreeArgs: function(test) { 1085 | test.expect(6); 1086 | 1087 | this.particle.internalRGB(0, 0, 0); 1088 | 1089 | test.ok(this.socketwrite.called); 1090 | 1091 | var buffer = this.socketwrite.getCall(0).args[0]; 1092 | 1093 | test.equal(buffer.readUInt8(0), 0x07); 1094 | test.equal(buffer.readUInt8(1), 0); 1095 | test.equal(buffer.readUInt8(2), 0); 1096 | test.equal(buffer.readUInt8(3), 0); 1097 | 1098 | test.deepEqual(this.particle.internalRGB(), { 1099 | red: 0, green: 0, blue: 0 1100 | }); 1101 | 1102 | test.done(); 1103 | }, 1104 | 1105 | setWithArrayOfThreeBytes: function(test) { 1106 | test.expect(6); 1107 | 1108 | this.particle.internalRGB([0, 0, 0]); 1109 | 1110 | test.ok(this.socketwrite.called); 1111 | 1112 | var buffer = this.socketwrite.getCall(0).args[0]; 1113 | 1114 | test.equal(buffer.readUInt8(0), 0x07); 1115 | test.equal(buffer.readUInt8(1), 0); 1116 | test.equal(buffer.readUInt8(2), 0); 1117 | test.equal(buffer.readUInt8(3), 0); 1118 | 1119 | test.deepEqual(this.particle.internalRGB(), { 1120 | red: 0, green: 0, blue: 0 1121 | }); 1122 | 1123 | test.done(); 1124 | }, 1125 | 1126 | setWithObjectContainingPropertiesRGB: function(test) { 1127 | test.expect(6); 1128 | 1129 | this.particle.internalRGB({ 1130 | red: 0, green: 0, blue: 0 1131 | }); 1132 | 1133 | test.ok(this.socketwrite.called); 1134 | 1135 | var buffer = this.socketwrite.getCall(0).args[0]; 1136 | 1137 | test.equal(buffer.readUInt8(0), 0x07); 1138 | test.equal(buffer.readUInt8(1), 0); 1139 | test.equal(buffer.readUInt8(2), 0); 1140 | test.equal(buffer.readUInt8(3), 0); 1141 | 1142 | test.deepEqual(this.particle.internalRGB(), { 1143 | red: 0, green: 0, blue: 0 1144 | }); 1145 | 1146 | test.done(); 1147 | }, 1148 | 1149 | setWithHexString: function(test) { 1150 | test.expect(6); 1151 | 1152 | this.particle.internalRGB("#000000"); 1153 | 1154 | test.ok(this.socketwrite.called); 1155 | 1156 | var buffer = this.socketwrite.getCall(0).args[0]; 1157 | 1158 | test.equal(buffer.readUInt8(0), 0x07); 1159 | test.equal(buffer.readUInt8(1), 0); 1160 | test.equal(buffer.readUInt8(2), 0); 1161 | test.equal(buffer.readUInt8(3), 0); 1162 | 1163 | test.deepEqual(this.particle.internalRGB(), { 1164 | red: 0, green: 0, blue: 0 1165 | }); 1166 | 1167 | test.done(); 1168 | }, 1169 | 1170 | setWithHexStringNoPrefix: function(test) { 1171 | test.expect(6); 1172 | 1173 | this.particle.internalRGB("000000"); 1174 | 1175 | test.ok(this.socketwrite.called); 1176 | 1177 | var buffer = this.socketwrite.getCall(0).args[0]; 1178 | 1179 | test.equal(buffer.readUInt8(0), 0x07); 1180 | test.equal(buffer.readUInt8(1), 0); 1181 | test.equal(buffer.readUInt8(2), 0); 1182 | test.equal(buffer.readUInt8(3), 0); 1183 | 1184 | test.deepEqual(this.particle.internalRGB(), { 1185 | red: 0, green: 0, blue: 0 1186 | }); 1187 | 1188 | test.done(); 1189 | }, 1190 | 1191 | setConstrainsValues: function(test) { 1192 | test.expect(6); 1193 | 1194 | this.particle.internalRGB(300, -1, 256); 1195 | 1196 | test.ok(this.socketwrite.called); 1197 | 1198 | var buffer = this.socketwrite.getCall(0).args[0]; 1199 | 1200 | test.equal(buffer.readUInt8(0), 0x07); 1201 | test.equal(buffer.readUInt8(1), 255); 1202 | test.equal(buffer.readUInt8(2), 0); 1203 | test.equal(buffer.readUInt8(3), 255); 1204 | 1205 | test.deepEqual(this.particle.internalRGB(), { 1206 | red: 255, green: 0, blue: 255 1207 | }); 1208 | 1209 | test.done(); 1210 | }, 1211 | 1212 | setBadValues: function(test) { 1213 | var particle = this.particle; 1214 | 1215 | test.expect(14); 1216 | 1217 | // null 1218 | test.throws(function() { 1219 | particle.internalRGB(null); 1220 | }); 1221 | 1222 | // shorthand not supported 1223 | test.throws(function() { 1224 | particle.internalRGB("#fff"); 1225 | }); 1226 | 1227 | // bad hex 1228 | test.throws(function() { 1229 | particle.internalRGB("#ggffff"); 1230 | }); 1231 | test.throws(function() { 1232 | particle.internalRGB("#ggffffff"); 1233 | }); 1234 | test.throws(function() { 1235 | particle.internalRGB("#ffffffff"); 1236 | }); 1237 | 1238 | // by params 1239 | test.throws(function() { 1240 | particle.internalRGB(10, 20, null); 1241 | }); 1242 | test.throws(function() { 1243 | particle.internalRGB(10, 20); 1244 | }); 1245 | test.throws(function() { 1246 | particle.internalRGB(10, undefined, 30); 1247 | }); 1248 | 1249 | 1250 | // by array 1251 | test.throws(function() { 1252 | particle.internalRGB([10, 20, null]); 1253 | }); 1254 | test.throws(function() { 1255 | particle.internalRGB([10, undefined, 30]); 1256 | }); 1257 | test.throws(function() { 1258 | particle.internalRGB([10, 20]); 1259 | }); 1260 | 1261 | // by object 1262 | test.throws(function() { 1263 | particle.internalRGB({red: 255, green: 100}); 1264 | }); 1265 | test.throws(function() { 1266 | particle.internalRGB({red: 255, green: 100, blue: null}); 1267 | }); 1268 | test.throws(function() { 1269 | particle.internalRGB({red: 255, green: 100, blue: undefined}); 1270 | }); 1271 | 1272 | 1273 | test.done(); 1274 | } 1275 | }; 1276 | --------------------------------------------------------------------------------