├── .gitignore ├── Buck.Animation ├── package.json └── phoenix.js ├── Gardner.Sundial ├── .gitignore ├── package.json ├── servos.js ├── sundial-wiring.fzz └── sundial.js ├── RGB-LEDs-BeagleBone-Black ├── leds-with-button.js ├── leds-with-photocell-and-accelerometer.js ├── leds-with-photocell.js ├── open-pixel-test-1.js ├── open-pixel-test.js └── package.json ├── SpookyLights ├── README.md ├── app.js ├── bin │ └── www ├── matrix-test.js ├── package.json ├── public │ ├── javascripts │ │ ├── matrix-view.js │ │ ├── raphael-min.js │ │ └── zepto.min.js │ └── stylesheets │ │ └── style.css ├── routes │ ├── index.js │ └── lights.js └── views │ ├── error.jade │ ├── index.jade │ └── layout.jade ├── batbot ├── LICENSE ├── drive.js ├── package.json └── sonar-scan.js ├── cheerful-j5 ├── .gitignore ├── breadboard │ ├── cheerfulj5-spark.fzz │ ├── cheerfulj5-spark.png │ ├── cheerfulj5-uno.fzz │ └── cheerfulj5-uno.png ├── cheerful-spark.js ├── cheerful-twit.js ├── cheerful.js ├── cheerlights-colors.js ├── package.json └── readme.md ├── delta ├── delta.js ├── package.json ├── template.pdf └── warmup.js ├── meow-shoes ├── LICENSE ├── README.md ├── css │ ├── bootstrap.min.css │ └── meowshoes.css ├── imgs │ ├── .DS_Store │ ├── d.png │ ├── meow0.png │ ├── meow1.png │ ├── meow2.png │ ├── meow3.png │ ├── voice0.png │ ├── voice1.png │ ├── voice2.png │ └── voice3.png ├── index.html ├── js │ ├── .DS_Store │ ├── abbeyLoad.js │ ├── assets.js │ ├── jquery-1.7.1.min.js │ └── meow.js ├── package.json ├── runshoes.js ├── sounds │ ├── click.mp3 │ ├── drum01.mp3 │ ├── drum02.mp3 │ ├── drum03.mp3 │ ├── drum04.mp3 │ ├── meow01.mp3 │ ├── meow02.mp3 │ ├── meow03.mp3 │ ├── meow04.mp3 │ ├── voice01.mp3 │ ├── voice02.mp3 │ ├── voice03.mp3 │ └── voice04.mp3 └── stls │ └── arduino_micro_cableslot.stl ├── physical-security ├── index.js └── package.json ├── piduino5 ├── piDuino5-webapp │ ├── .gitignore │ ├── LICENSE │ ├── app.js │ ├── bower.json │ ├── package.json │ └── views │ │ └── index.html └── piDuino5 │ ├── .gitignore │ ├── LICENSE │ ├── app.js │ └── package.json ├── readme.md ├── simplebot ├── README.md ├── arduino │ └── SimpleBotFirmata │ │ ├── Boards.h │ │ ├── Firmata.cpp │ │ ├── Firmata.h │ │ └── SimpleBotFirmata.ino ├── examples │ ├── collision-avoid.js │ ├── servo-test.js │ └── simplebot.js ├── package.json └── physical │ ├── simplebot.svg │ ├── simplebot_basic.fzz │ ├── simplebot_sonic.fzz │ ├── simplebot_wireless.fzz │ └── skid.stl └── typebot ├── README.md ├── align_servos.js ├── generate_angle_estimates.js ├── package.json ├── servocontrol.js └── typebot.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Buck.Animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phoenix.js", 3 | "version": "0.1.0", 4 | "description": "Phoenix Hexapod Animation Example", 5 | "main": "phoenix.js", 6 | "dependencies": { 7 | "temporal": "^0.3.8", 8 | "johnny-five": "^0.8.14", 9 | "underscore": "^1.7.0" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "author": "Donovan Buck ", 16 | "license": "ISC" 17 | } 18 | -------------------------------------------------------------------------------- /Gardner.Sundial/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Gardner.Sundial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "indoor-sundial", 3 | "version": "1.0.0", 4 | "description": "arduino- and node- (johnny-five)-driven indoor sundial", 5 | "main": "sundial.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "arduino" 11 | ], 12 | "author": "Lyza Danger Gardner ", 13 | "license": "ISC", 14 | "dependencies": { 15 | "johnny-five": "~0.8.8", 16 | "suncalc": "~1.5.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Gardner.Sundial/servos.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var five = require('johnny-five'); 3 | 4 | var board, 5 | azimuthServo, 6 | elevationServo; 7 | 8 | board = new five.Board(); 9 | 10 | board.on('ready', function() { 11 | azimuthServo = new five.Servo({ 12 | center : true, 13 | isInverted : true, 14 | pin : 9 15 | }); 16 | elevationServo = new five.Servo({ 17 | center : true, 18 | isInverted : true, 19 | pin : 10 20 | }); 21 | 22 | this.repl.inject({ 23 | aServo: azimuthServo, 24 | eServo: elevationServo 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /Gardner.Sundial/sundial-wiring.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/Gardner.Sundial/sundial-wiring.fzz -------------------------------------------------------------------------------- /Gardner.Sundial/sundial.js: -------------------------------------------------------------------------------- 1 | /* global console, require, setTimeout, setInterval, clearTimeout */ 2 | 'use strict'; 3 | var five = require('johnny-five'), 4 | sunCalc = require('suncalc'); 5 | 6 | var board = new five.Board(), 7 | servos, 8 | sundial; 9 | 10 | servos = { 11 | azimuth: { 12 | pin : 9, 13 | range : [7, 172], 14 | isInverted : true, 15 | center : true 16 | }, 17 | elevation: { 18 | pin : 10, 19 | range : [7, 172], 20 | isInverted : true, 21 | center : true 22 | } 23 | }; 24 | 25 | sundial = { 26 | latitude : 45.52, 27 | longitude : -122.63, 28 | tickInterval : 5000, 29 | msPerDegree : 50 30 | }; 31 | 32 | function sunPositionInDegrees(date, latitude, longitude) { 33 | var positionNow = sunCalc.getPosition(date, latitude, longitude); 34 | return { 35 | azimuth: Math.round((positionNow.azimuth + Math.PI) * 180 / Math.PI), 36 | elevation: Math.round(positionNow.altitude * 180 / Math.PI) 37 | }; 38 | } 39 | 40 | board.on('ready', function() { 41 | var azimuthServo = new five.Servo(servos.azimuth), 42 | elevationServo = new five.Servo(servos.elevation), 43 | ticker; 44 | 45 | var tick = function tickTock() { 46 | console.log('tick!'); 47 | var position = sunPositionInDegrees(new Date(), 48 | sundial.latitude, 49 | sundial.longitude), 50 | isFlipped = position.azimuth > 180, 51 | aPos = (isFlipped) ? position.azimuth - 180 : position.azimuth, 52 | ePos = (isFlipped) ? 180 - position.elevation : position.elevation, 53 | aChange = Math.abs(azimuthServo.value - aPos), 54 | eChange = Math.abs(elevationServo.value - ePos), 55 | aTime = aChange * sundial.msPerDegree, 56 | eTime = eChange * sundial.msPerDegree, 57 | servoTime = (aTime >= eTime) ? aTime : eTime; 58 | 59 | if (ticker) { clearTimeout(ticker); } 60 | 61 | if (position.elevation < 0) { 62 | console.log('It is nighttime, silly!'); 63 | return; 64 | } 65 | if (aChange || eChange) { 66 | azimuthServo.to(aPos, aTime); 67 | elevationServo.to(ePos, eTime); 68 | } 69 | ticker = setTimeout(tick, sundial.tickInterval + servoTime); 70 | }; 71 | 72 | tick(); 73 | 74 | this.repl.inject({ 75 | aServo: azimuthServo, 76 | eServo: elevationServo, 77 | tick : tick, 78 | ticker : ticker 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /RGB-LEDs-BeagleBone-Black/leds-with-button.js: -------------------------------------------------------------------------------- 1 | var Five = require('johnny-five'); 2 | var BeagleBone = require('beaglebone-io'); 3 | var board = new Five.Board({ 4 | io: new BeagleBone() 5 | }); 6 | 7 | var opc_client = require('open-pixel-control'); 8 | 9 | var client = new opc_client({ 10 | address: '127.0.0.1', 11 | port: 7890 12 | }); 13 | 14 | 15 | board.on('ready', function () { 16 | this.digitalWrite(5, this.HIGH); 17 | var button = new Five.Button('P9_39'); //A0 in Arduino Mapping 18 | 19 | client.on('connected', function(){ 20 | var strip = client.add_strip({ 21 | length: 25 22 | }); 23 | 24 | var pixels = [], animationInterval; 25 | 26 | reset_strip(); 27 | start_animation(); 28 | 29 | button.on("down", function(){ 30 | reset_strip(); 31 | start_animation(); 32 | }) 33 | 34 | function reset_strip(){ 35 | clearInterval(animationInterval); 36 | 37 | pixels = []; 38 | for(var i = 0; i < strip.length; i++){ 39 | pixels.push([0, 0, 0]); 40 | } 41 | 42 | client.put_pixels(strip.id, pixels); 43 | } 44 | 45 | function start_animation(){ 46 | var index = 0; 47 | animationInterval = setInterval(function(){ 48 | randomColor = [Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256)]; 49 | 50 | client.put_pixel(strip.id, index, randomColor); 51 | 52 | index++; 53 | if(index == strip.length){ 54 | index = 0; 55 | } 56 | }, 100); 57 | } 58 | 59 | 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /RGB-LEDs-BeagleBone-Black/leds-with-photocell-and-accelerometer.js: -------------------------------------------------------------------------------- 1 | var Five = require('johnny-five'); 2 | var BeagleBone = require('beaglebone-io'); 3 | var board = new Five.Board({ 4 | io: new BeagleBone() 5 | }); 6 | 7 | var opc_client = require('open-pixel-control'); 8 | 9 | var client = new opc_client({ 10 | address: '127.0.0.1', 11 | port: 7890 12 | }); 13 | 14 | 15 | board.on('ready', function () { 16 | this.digitalWrite(5, this.HIGH); 17 | var light = new Five.Sensor('P9_40'); //A1 in Arduino Mapping 18 | var accelerometer = new Five.Accelerometer('P9_37', 'P9_38', 'P9_35'); 19 | this.repl.inject({ 20 | light: light 21 | }); 22 | 23 | client.on('connected', function(){ 24 | var strip = client.add_strip({ 25 | length: 25 26 | }); 27 | 28 | var pixels = []; 29 | var maxValue = 0; 30 | 31 | light.scale([0, 255]).on('data', function(){ 32 | maxValue = this.value; 33 | }); 34 | 35 | accelerometer.scale([-1, 1]).on('data', function(){ 36 | pixels = []; 37 | 38 | var xValue = Math.abs(this.x.value), 39 | yValue = Math.abs(this.y.value), 40 | xValue = Math.abs(this.z.value), 41 | red = xValue * maxValue, 42 | green = yValue * maxValue, 43 | blue = zValue * maxValue; 44 | 45 | for(var i = 0; i < strip.length; i++){ 46 | pixels.push([red, green, blue]); 47 | } 48 | 49 | client.put_pixels(strip.id, pixels) 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /RGB-LEDs-BeagleBone-Black/leds-with-photocell.js: -------------------------------------------------------------------------------- 1 | var Five = require('johnny-five'); 2 | var BeagleBone = require('beaglebone-io'); 3 | var board = new Five.Board({ 4 | io: new BeagleBone() 5 | }); 6 | 7 | var opc_client = require('open-pixel-control'); 8 | 9 | var client = new opc_client({ 10 | address: '127.0.0.1', 11 | port: 7890 12 | }); 13 | 14 | 15 | board.on('ready', function () { 16 | this.digitalWrite(5, this.HIGH); 17 | var light = new Five.Sensor('P9_40'); //A1 in Arduino Mapping 18 | 19 | this.repl.inject({ 20 | light: light 21 | }); 22 | 23 | client.on('connected', function(){ 24 | var strip = client.add_strip({ 25 | length: 25 26 | }); 27 | 28 | var pixels = []; 29 | 30 | light.scale([0, 255]).on('data', function(){ 31 | pixels = []; 32 | for(var i = 0; i < 120; i++){ 33 | pixels.push([this.value, this.value, this.value]); 34 | } 35 | client.put_pixels(strip.id, pixels); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /RGB-LEDs-BeagleBone-Black/open-pixel-test-1.js: -------------------------------------------------------------------------------- 1 | var opc_client = require('open-pixel-control'); 2 | 3 | var client = new opc_client({ 4 | address: '127.0.0.1', 5 | port: 7890 6 | }); 7 | 8 | client.on('connected', function(){ 9 | var strip = client.add_strip({ 10 | length: 25 11 | }); 12 | 13 | var pixels = []; 14 | for(var i = 0; i < strip.length; i++){ 15 | pixels.push([50, 50, 50]); 16 | } 17 | 18 | client.put_pixels(strip.id, pixels); 19 | }); 20 | -------------------------------------------------------------------------------- /RGB-LEDs-BeagleBone-Black/open-pixel-test.js: -------------------------------------------------------------------------------- 1 | var opc_client = require('open-pixel-control'); 2 | 3 | var client = new opc_client({ 4 | address: '127.0.0.1', 5 | port: 7890 6 | }); 7 | 8 | client.on('connected', function(){ 9 | var strip = client.add_strip({ 10 | length: 25 11 | }); 12 | 13 | var pixels = []; 14 | for(var i = 0; i < strip.length; i++){ 15 | pixels.push([50, 50, 50]); 16 | } 17 | 18 | client.put_pixels(strip.id, pixels); 19 | 20 | var index = 0, 21 | randomColor; 22 | setInterval(function(){ 23 | randomColor = [Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256)]; 24 | 25 | client.put_pixel(strip.id, index, randomColor); 26 | 27 | index++; 28 | if(index == strip.length){ 29 | index = 0; 30 | } 31 | }, 1000) 32 | }); 33 | -------------------------------------------------------------------------------- /RGB-LEDs-BeagleBone-Black/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BBB-RGB-LED-Code", 3 | "version": "0.0.1", 4 | "description": "Code for my chapter in the Making Robots with JS Book", 5 | "main": "open-pixel-test.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "nodebotanist", 10 | "license": "BSD-2-Clause", 11 | "dependencies": { 12 | "open-pixel-control": "~0.1.2", 13 | "beaglebone-io": "~1.0.0", 14 | "johnny-five": "~0.8.14" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SpookyLights/README.md: -------------------------------------------------------------------------------- 1 | #Spooky Lights 2 | 3 | To run: 4 | 5 | npm install 6 | node bin/www 7 | -------------------------------------------------------------------------------- /SpookyLights/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var routes = require('./routes/index'); 8 | var lights = require('./routes/lights'); 9 | var five = require('johnny-five'); 10 | 11 | // create johnny-five Led.Matrix to communicate with LED matrices 12 | var board = new five.Board(); 13 | board.on("ready",function(){ 14 | var matrices = new five.Led.Matrix({ 15 | devices: 7, 16 | controller: "HT16K33", 17 | isBicolor: true 18 | }); 19 | app.set('matrices', matrices); 20 | }); 21 | 22 | var app = express(); 23 | 24 | // view engine setup 25 | app.set('views', path.join(__dirname, 'views')); 26 | app.set('view engine', 'jade'); 27 | 28 | // uncomment after placing your favicon in /public 29 | //app.use(favicon(__dirname + '/public/favicon.ico')); 30 | app.use(logger('dev')); 31 | app.use(bodyParser.json()); 32 | app.use(bodyParser.urlencoded({ extended: false })); 33 | app.use(cookieParser()); 34 | app.use(express.static(path.join(__dirname, 'public'))); 35 | 36 | // view engine setup 37 | app.set('views', path.join(__dirname, 'views')); 38 | app.set('view engine', 'jade'); 39 | 40 | // uncomment after placing your favicon in /public 41 | //app.use(favicon(__dirname + '/public/favicon.ico')); 42 | app.use(logger('dev')); 43 | app.use(bodyParser.json()); 44 | app.use(bodyParser.urlencoded({ extended: false })); 45 | app.use(cookieParser()); 46 | app.use(express.static(path.join(__dirname, 'public'))); 47 | 48 | app.use('/', routes); 49 | app.use('/lights', lights); 50 | 51 | // catch 404 and forward to error handler 52 | app.use(function(req, res, next) { 53 | var err = new Error('Not Found'); 54 | err.status = 404; 55 | next(err); 56 | }); 57 | 58 | // error handlers 59 | 60 | // development error handler 61 | // will print stacktrace 62 | if (app.get('env') === 'development') { 63 | app.use(function(err, req, res, next) { 64 | res.status(err.status || 500); 65 | res.render('error', { 66 | message: err.message, 67 | error: err 68 | }); 69 | }); 70 | } 71 | 72 | // production error handler 73 | // no stacktraces leaked to user 74 | app.use(function(err, req, res, next) { 75 | res.status(err.status || 500); 76 | res.render('error', { 77 | message: err.message, 78 | error: {} 79 | }); 80 | }); 81 | 82 | module.exports = app; 83 | 84 | 85 | -------------------------------------------------------------------------------- /SpookyLights/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('lights-app'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /SpookyLights/matrix-test.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"); 2 | var board = new five.Board(); 3 | 4 | board.on("ready", function() { 5 | 6 | var matrix = new five.Led.Matrix({ 7 | devices: 7, 8 | controller: "HT16K33", 9 | isBicolor: true 10 | }); 11 | 12 | var heart = [ 13 | "01100110", 14 | "10011001", 15 | "10000001", 16 | "10000001", 17 | "01000010", 18 | "00100100", 19 | "00011000", 20 | "00000000" 21 | ]; 22 | 23 | matrix.draw(heart); 24 | 25 | this.repl.inject({ 26 | m: matrix, 27 | heart: heart 28 | }); 29 | 30 | }); -------------------------------------------------------------------------------- /SpookyLights/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lights-app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.8.1", 10 | "cookie-parser": "~1.3.3", 11 | "debug": "~2.0.0", 12 | "express": "~4.9.0", 13 | "jade": "~1.6.0", 14 | "johnny-five": "^0.8.17", 15 | "morgan": "~1.3.0", 16 | "serve-favicon": "~2.1.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SpookyLights/public/javascripts/matrix-view.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function MatrixView(elementId){ 3 | this.elementId = elementId; 4 | this.matrixSize = 8; 5 | this.cellSize = 30; 6 | this.cellPadding = 2; 7 | this.squares = []; 8 | this.colors = ["Black","Chartreuse","Orange","OrangeRed"]; 9 | 10 | var self = this; 11 | var toggleSquareColor = function(a){ 12 | var square = this; 13 | var squareColor = square.data('color'); 14 | squareColor = (squareColor + 1) % self.colors.length; 15 | square.data('color',squareColor); 16 | square.attr('fill',self.colors[squareColor]); 17 | }; 18 | 19 | this.draw = function(){ 20 | var currentX = this.cellPadding, 21 | currentY = this.cellPadding, 22 | paperDimension = this.matrixSize * 23 | (this.cellSize + this.cellPadding) + this.cellPadding, 24 | paper = Raphael(this.elementId,paperDimension,paperDimension); 25 | // draw matrix of squares using RaphaelJS 26 | for (var w = 0; w < this.matrixSize; w++){ 27 | this.squares[w] = []; 28 | for (var h=0; h < this.matrixSize; h++){ 29 | var square = paper.rect(currentX, currentY, this.cellSize, this.cellSize) 30 | .attr('stroke','white') 31 | .attr('fill','black') 32 | .data('w',w) 33 | .data('h',h) 34 | .data('color',0) 35 | .click(toggleSquareColor); 36 | this.squares[w].push(square); 37 | currentX += (this.cellSize + this.cellPadding); 38 | } 39 | currentX = this.cellPadding; 40 | currentY += (this.cellSize + this.cellPadding); 41 | } 42 | return this; 43 | }; 44 | 45 | this.reset = function(){ 46 | for (var w = 0; w < this.matrixSize; w++){ 47 | for (var h=0; h < this.matrixSize; h++){ 48 | var square = this.squares[w][h]; 49 | square.data('color',0); 50 | square.attr('fill',this.colors[0]); 51 | } 52 | } 53 | return this; 54 | }; 55 | 56 | this.print = function(){ 57 | return JSON.stringify(this.squares.map( 58 | function(a) { 59 | var colors = a.map( 60 | function(b){ 61 | return b.data('color'); 62 | } 63 | ); 64 | var result = ""; 65 | colors.forEach(function(color) { 66 | result += color; 67 | }); 68 | return result; 69 | } 70 | )); 71 | }; 72 | } 73 | 74 | // draw the matrix UI 75 | var view = new MatrixView('matrix').draw(); 76 | 77 | // attach actions to buttons 78 | $('.reset').click(function(){ 79 | view.reset(); 80 | }); 81 | 82 | $('.send').click(function(){ 83 | var device = $('.device').val(); 84 | $.ajax({ 85 | type: 'POST', 86 | url: '/lights/draw/' + device, 87 | data: view.print(), 88 | contentType: 'application/json', 89 | complete: function(xhr, status) { 90 | $('.status').html(xhr.response); 91 | } 92 | }); 93 | }); 94 | 95 | $('.clear').click(function(){ 96 | var device = $('.device').val(); 97 | $.ajax({ 98 | type: 'POST', 99 | url: '/lights/clear/', 100 | contentType: 'application/json', 101 | complete: function(xhr, status) { 102 | $('.status').html(xhr.response); 103 | } 104 | }); 105 | }); 106 | })(); -------------------------------------------------------------------------------- /SpookyLights/public/javascripts/zepto.min.js: -------------------------------------------------------------------------------- 1 | /* Zepto v1.1.4 - zepto event ajax form ie - zeptojs.com/license */ 2 | var Zepto=function(){function L(t){return null==t?String(t):j[S.call(t)]||"object"}function Z(t){return"function"==L(t)}function $(t){return null!=t&&t==t.window}function _(t){return null!=t&&t.nodeType==t.DOCUMENT_NODE}function D(t){return"object"==L(t)}function R(t){return D(t)&&!$(t)&&Object.getPrototypeOf(t)==Object.prototype}function M(t){return"number"==typeof t.length}function k(t){return s.call(t,function(t){return null!=t})}function z(t){return t.length>0?n.fn.concat.apply([],t):t}function F(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function q(t){return t in f?f[t]:f[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function H(t,e){return"number"!=typeof e||c[F(t)]?e:e+"px"}function I(t){var e,n;return u[t]||(e=a.createElement(t),a.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),u[t]=n),u[t]}function V(t){return"children"in t?o.call(t.children):n.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function B(n,i,r){for(e in i)r&&(R(i[e])||A(i[e]))?(R(i[e])&&!R(n[e])&&(n[e]={}),A(i[e])&&!A(n[e])&&(n[e]=[]),B(n[e],i[e],r)):i[e]!==t&&(n[e]=i[e])}function U(t,e){return null==e?n(t):n(t).filter(e)}function J(t,e,n,i){return Z(e)?e.call(t,n,i):e}function X(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function W(e,n){var i=e.className,r=i&&i.baseVal!==t;return n===t?r?i.baseVal:i:void(r?i.baseVal=n:e.className=n)}function Y(t){var e;try{return t?"true"==t||("false"==t?!1:"null"==t?null:/^0/.test(t)||isNaN(e=Number(t))?/^[\[\{]/.test(t)?n.parseJSON(t):t:e):t}catch(i){return t}}function G(t,e){e(t);for(var n=0,i=t.childNodes.length;i>n;n++)G(t.childNodes[n],e)}var t,e,n,i,C,N,r=[],o=r.slice,s=r.filter,a=window.document,u={},f={},c={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},l=/^\s*<(\w+|!)[^>]*>/,h=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,p=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,d=/^(?:body|html)$/i,m=/([A-Z])/g,g=["val","css","html","text","data","width","height","offset"],v=["after","prepend","before","append"],y=a.createElement("table"),x=a.createElement("tr"),b={tr:a.createElement("tbody"),tbody:y,thead:y,tfoot:y,td:x,th:x,"*":a.createElement("div")},w=/complete|loaded|interactive/,E=/^[\w-]*$/,j={},S=j.toString,T={},O=a.createElement("div"),P={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},A=Array.isArray||function(t){return t instanceof Array};return T.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var i,r=t.parentNode,o=!r;return o&&(r=O).appendChild(t),i=~T.qsa(r,e).indexOf(t),o&&O.removeChild(t),i},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return s.call(t,function(e,n){return t.indexOf(e)==n})},T.fragment=function(e,i,r){var s,u,f;return h.test(e)&&(s=n(a.createElement(RegExp.$1))),s||(e.replace&&(e=e.replace(p,"<$1>")),i===t&&(i=l.test(e)&&RegExp.$1),i in b||(i="*"),f=b[i],f.innerHTML=""+e,s=n.each(o.call(f.childNodes),function(){f.removeChild(this)})),R(r)&&(u=n(s),n.each(r,function(t,e){g.indexOf(t)>-1?u[t](e):u.attr(t,e)})),s},T.Z=function(t,e){return t=t||[],t.__proto__=n.fn,t.selector=e||"",t},T.isZ=function(t){return t instanceof T.Z},T.init=function(e,i){var r;if(!e)return T.Z();if("string"==typeof e)if(e=e.trim(),"<"==e[0]&&l.test(e))r=T.fragment(e,RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}else{if(Z(e))return n(a).ready(e);if(T.isZ(e))return e;if(A(e))r=k(e);else if(D(e))r=[e],e=null;else if(l.test(e))r=T.fragment(e.trim(),RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}}return T.Z(r,e)},n=function(t,e){return T.init(t,e)},n.extend=function(t){var e,n=o.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){B(t,n,e)}),t},T.qsa=function(t,e){var n,i="#"==e[0],r=!i&&"."==e[0],s=i||r?e.slice(1):e,a=E.test(s);return _(t)&&a&&i?(n=t.getElementById(s))?[n]:[]:1!==t.nodeType&&9!==t.nodeType?[]:o.call(a&&!i?r?t.getElementsByClassName(s):t.getElementsByTagName(e):t.querySelectorAll(e))},n.contains=a.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},n.type=L,n.isFunction=Z,n.isWindow=$,n.isArray=A,n.isPlainObject=R,n.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},n.inArray=function(t,e,n){return r.indexOf.call(e,t,n)},n.camelCase=C,n.trim=function(t){return null==t?"":String.prototype.trim.call(t)},n.uuid=0,n.support={},n.expr={},n.map=function(t,e){var n,r,o,i=[];if(M(t))for(r=0;r=0?e:e+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return r.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return Z(t)?this.not(this.not(t)):n(s.call(this,function(e){return T.matches(e,t)}))},add:function(t,e){return n(N(this.concat(n(t,e))))},is:function(t){return this.length>0&&T.matches(this[0],t)},not:function(e){var i=[];if(Z(e)&&e.call!==t)this.each(function(t){e.call(this,t)||i.push(this)});else{var r="string"==typeof e?this.filter(e):M(e)&&Z(e.item)?o.call(e):n(e);this.forEach(function(t){r.indexOf(t)<0&&i.push(t)})}return n(i)},has:function(t){return this.filter(function(){return D(t)?n.contains(this,t):n(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!D(t)?t:n(t)},last:function(){var t=this[this.length-1];return t&&!D(t)?t:n(t)},find:function(t){var e,i=this;return e=t?"object"==typeof t?n(t).filter(function(){var t=this;return r.some.call(i,function(e){return n.contains(e,t)})}):1==this.length?n(T.qsa(this[0],t)):this.map(function(){return T.qsa(this,t)}):[]},closest:function(t,e){var i=this[0],r=!1;for("object"==typeof t&&(r=n(t));i&&!(r?r.indexOf(i)>=0:T.matches(i,t));)i=i!==e&&!_(i)&&i.parentNode;return n(i)},parents:function(t){for(var e=[],i=this;i.length>0;)i=n.map(i,function(t){return(t=t.parentNode)&&!_(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return U(e,t)},parent:function(t){return U(N(this.pluck("parentNode")),t)},children:function(t){return U(this.map(function(){return V(this)}),t)},contents:function(){return this.map(function(){return o.call(this.childNodes)})},siblings:function(t){return U(this.map(function(t,e){return s.call(V(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return n.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=I(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=Z(t);if(this[0]&&!e)var i=n(t).get(0),r=i.parentNode||this.length>1;return this.each(function(o){n(this).wrapAll(e?t.call(this,o):r?i.cloneNode(!0):i)})},wrapAll:function(t){if(this[0]){n(this[0]).before(t=n(t));for(var e;(e=t.children()).length;)t=e.first();n(t).append(this)}return this},wrapInner:function(t){var e=Z(t);return this.each(function(i){var r=n(this),o=r.contents(),s=e?t.call(this,i):t;o.length?o.wrapAll(s):r.append(s)})},unwrap:function(){return this.parent().each(function(){n(this).replaceWith(n(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(e){return this.each(function(){var i=n(this);(e===t?"none"==i.css("display"):e)?i.show():i.hide()})},prev:function(t){return n(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return n(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var i=this.innerHTML;n(this).empty().append(J(this,t,e,i))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=J(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this[0].textContent:null},attr:function(n,i){var r;return"string"!=typeof n||1 in arguments?this.each(function(t){if(1===this.nodeType)if(D(n))for(e in n)X(this,e,n[e]);else X(this,n,J(this,i,t,this.getAttribute(n)))}):this.length&&1===this[0].nodeType?!(r=this[0].getAttribute(n))&&n in this[0]?this[0][n]:r:t},removeAttr:function(t){return this.each(function(){1===this.nodeType&&X(this,t)})},prop:function(t,e){return t=P[t]||t,1 in arguments?this.each(function(n){this[t]=J(this,e,n,this[t])}):this[0]&&this[0][t]},data:function(e,n){var i="data-"+e.replace(m,"-$1").toLowerCase(),r=1 in arguments?this.attr(i,n):this.attr(i);return null!==r?Y(r):t},val:function(t){return 0 in arguments?this.each(function(e){this.value=J(this,t,e,this.value)}):this[0]&&(this[0].multiple?n(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var i=n(this),r=J(this,t,e,i.offset()),o=i.offsetParent().offset(),s={top:r.top-o.top,left:r.left-o.left};"static"==i.css("position")&&(s.position="relative"),i.css(s)});if(!this.length)return null;var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(t,i){if(arguments.length<2){var r=this[0],o=getComputedStyle(r,"");if(!r)return;if("string"==typeof t)return r.style[C(t)]||o.getPropertyValue(t);if(A(t)){var s={};return n.each(A(t)?t:[t],function(t,e){s[e]=r.style[C(e)]||o.getPropertyValue(e)}),s}}var a="";if("string"==L(t))i||0===i?a=F(t)+":"+H(t,i):this.each(function(){this.style.removeProperty(F(t))});else for(e in t)t[e]||0===t[e]?a+=F(e)+":"+H(e,t[e])+";":this.each(function(){this.style.removeProperty(F(e))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(n(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?r.some.call(this,function(t){return this.test(W(t))},q(t)):!1},addClass:function(t){return t?this.each(function(e){i=[];var r=W(this),o=J(this,t,e,r);o.split(/\s+/g).forEach(function(t){n(this).hasClass(t)||i.push(t)},this),i.length&&W(this,r+(r?" ":"")+i.join(" "))}):this},removeClass:function(e){return this.each(function(n){return e===t?W(this,""):(i=W(this),J(this,e,n,i).split(/\s+/g).forEach(function(t){i=i.replace(q(t)," ")}),void W(this,i.trim()))})},toggleClass:function(e,i){return e?this.each(function(r){var o=n(this),s=J(this,e,r,W(this));s.split(/\s+/g).forEach(function(e){(i===t?!o.hasClass(e):i)?o.addClass(e):o.removeClass(e)})}):this},scrollTop:function(e){if(this.length){var n="scrollTop"in this[0];return e===t?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=e}:function(){this.scrollTo(this.scrollX,e)})}},scrollLeft:function(e){if(this.length){var n="scrollLeft"in this[0];return e===t?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=e}:function(){this.scrollTo(e,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),i=this.offset(),r=d.test(e[0].nodeName)?{top:0,left:0}:e.offset();return i.top-=parseFloat(n(t).css("margin-top"))||0,i.left-=parseFloat(n(t).css("margin-left"))||0,r.top+=parseFloat(n(e[0]).css("border-top-width"))||0,r.left+=parseFloat(n(e[0]).css("border-left-width"))||0,{top:i.top-r.top,left:i.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||a.body;t&&!d.test(t.nodeName)&&"static"==n(t).css("position");)t=t.offsetParent;return t})}},n.fn.detach=n.fn.remove,["width","height"].forEach(function(e){var i=e.replace(/./,function(t){return t[0].toUpperCase()});n.fn[e]=function(r){var o,s=this[0];return r===t?$(s)?s["inner"+i]:_(s)?s.documentElement["scroll"+i]:(o=this.offset())&&o[e]:this.each(function(t){s=n(this),s.css(e,J(this,r,t,s[e]()))})}}),v.forEach(function(t,e){var i=e%2;n.fn[t]=function(){var t,o,r=n.map(arguments,function(e){return t=L(e),"object"==t||"array"==t||null==e?e:T.fragment(e)}),s=this.length>1;return r.length<1?this:this.each(function(t,u){o=i?u:u.parentNode,u=0==e?u.nextSibling:1==e?u.firstChild:2==e?u:null;var f=n.contains(a.documentElement,o);r.forEach(function(t){if(s)t=t.cloneNode(!0);else if(!o)return n(t).remove();o.insertBefore(t,u),f&&G(t,function(t){null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src||window.eval.call(window,t.innerHTML)})})})},n.fn[i?t+"To":"insert"+(e?"Before":"After")]=function(e){return n(e)[t](this),this}}),T.Z.prototype=n.fn,T.uniq=N,T.deserializeValue=Y,n.zepto=T,n}();window.Zepto=Zepto,void 0===window.$&&(window.$=Zepto),function(t){function l(t){return t._zid||(t._zid=e++)}function h(t,e,n,i){if(e=p(e),e.ns)var r=d(e.ns);return(s[l(t)]||[]).filter(function(t){return!(!t||e.e&&t.e!=e.e||e.ns&&!r.test(t.ns)||n&&l(t.fn)!==l(n)||i&&t.sel!=i)})}function p(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function d(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function m(t,e){return t.del&&!u&&t.e in f||!!e}function g(t){return c[t]||u&&f[t]||t}function v(e,i,r,o,a,u,f){var h=l(e),d=s[h]||(s[h]=[]);i.split(/\s/).forEach(function(i){if("ready"==i)return t(document).ready(r);var s=p(i);s.fn=r,s.sel=a,s.e in c&&(r=function(e){var n=e.relatedTarget;return!n||n!==this&&!t.contains(this,n)?s.fn.apply(this,arguments):void 0}),s.del=u;var l=u||r;s.proxy=function(t){if(t=j(t),!t.isImmediatePropagationStopped()){t.data=o;var i=l.apply(e,t._args==n?[t]:[t].concat(t._args));return i===!1&&(t.preventDefault(),t.stopPropagation()),i}},s.i=d.length,d.push(s),"addEventListener"in e&&e.addEventListener(g(s.e),s.proxy,m(s,f))})}function y(t,e,n,i,r){var o=l(t);(e||"").split(/\s/).forEach(function(e){h(t,e,n,i).forEach(function(e){delete s[o][e.i],"removeEventListener"in t&&t.removeEventListener(g(e.e),e.proxy,m(e,r))})})}function j(e,i){return(i||!e.isDefaultPrevented)&&(i||(i=e),t.each(E,function(t,n){var r=i[t];e[t]=function(){return this[n]=x,r&&r.apply(i,arguments)},e[n]=b}),(i.defaultPrevented!==n?i.defaultPrevented:"returnValue"in i?i.returnValue===!1:i.getPreventDefault&&i.getPreventDefault())&&(e.isDefaultPrevented=x)),e}function S(t){var e,i={originalEvent:t};for(e in t)w.test(e)||t[e]===n||(i[e]=t[e]);return j(i,t)}var n,e=1,i=Array.prototype.slice,r=t.isFunction,o=function(t){return"string"==typeof t},s={},a={},u="onfocusin"in window,f={focus:"focusin",blur:"focusout"},c={mouseenter:"mouseover",mouseleave:"mouseout"};a.click=a.mousedown=a.mouseup=a.mousemove="MouseEvents",t.event={add:v,remove:y},t.proxy=function(e,n){var s=2 in arguments&&i.call(arguments,2);if(r(e)){var a=function(){return e.apply(n,s?s.concat(i.call(arguments)):arguments)};return a._zid=l(e),a}if(o(n))return s?(s.unshift(e[n],e),t.proxy.apply(null,s)):t.proxy(e[n],e);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,i){return this.on(t,e,n,i,1)};var x=function(){return!0},b=function(){return!1},w=/^([A-Z]|returnValue$|layer[XY]$)/,E={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,s,a,u,f){var c,l,h=this;return e&&!o(e)?(t.each(e,function(t,e){h.on(t,s,a,e,f)}),h):(o(s)||r(u)||u===!1||(u=a,a=s,s=n),(r(a)||a===!1)&&(u=a,a=n),u===!1&&(u=b),h.each(function(n,r){f&&(c=function(t){return y(r,t.type,u),u.apply(this,arguments)}),s&&(l=function(e){var n,o=t(e.target).closest(s,r).get(0);return o&&o!==r?(n=t.extend(S(e),{currentTarget:o,liveFired:r}),(c||u).apply(o,[n].concat(i.call(arguments,1)))):void 0}),v(r,e,u,a,s,l||c)}))},t.fn.off=function(e,i,s){var a=this;return e&&!o(e)?(t.each(e,function(t,e){a.off(t,i,e)}),a):(o(i)||r(s)||s===!1||(s=i,i=n),s===!1&&(s=b),a.each(function(){y(this,e,s,i)}))},t.fn.trigger=function(e,n){return e=o(e)||t.isPlainObject(e)?t.Event(e):j(e),e._args=n,this.each(function(){"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,n){var i,r;return this.each(function(s,a){i=S(o(e)?t.Event(e):e),i._args=n,i.target=a,t.each(h(a,e.type||e),function(t,e){return r=e.proxy(i),i.isImmediatePropagationStopped()?!1:void 0})}),r},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.trigger(e)}}),["focus","blur"].forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.each(function(){try{this[e]()}catch(t){}}),this}}),t.Event=function(t,e){o(t)||(e=t,t=e.type);var n=document.createEvent(a[t]||"Events"),i=!0;if(e)for(var r in e)"bubbles"==r?i=!!e[r]:n[r]=e[r];return n.initEvent(t,i,!0),j(n)}}(Zepto),function(t){function l(e,n,i){var r=t.Event(n);return t(e).trigger(r,i),!r.isDefaultPrevented()}function h(t,e,i,r){return t.global?l(e||n,i,r):void 0}function p(e){e.global&&0===t.active++&&h(e,null,"ajaxStart")}function d(e){e.global&&!--t.active&&h(e,null,"ajaxStop")}function m(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||h(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void h(e,n,"ajaxSend",[t,e])}function g(t,e,n,i){var r=n.context,o="success";n.success.call(r,t,o,e),i&&i.resolveWith(r,[t,o,e]),h(n,r,"ajaxSuccess",[e,n,t]),y(o,e,n)}function v(t,e,n,i,r){var o=i.context;i.error.call(o,n,e,t),r&&r.rejectWith(o,[n,e,t]),h(i,o,"ajaxError",[n,i,t||e]),y(e,n,i)}function y(t,e,n){var i=n.context;n.complete.call(i,e,t),h(n,i,"ajaxComplete",[e,n]),d(n)}function x(){}function b(t){return t&&(t=t.split(";",2)[0]),t&&(t==f?"html":t==u?"json":s.test(t)?"script":a.test(t)&&"xml")||"text"}function w(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function E(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()||(e.url=w(e.url,e.data),e.data=void 0)}function j(e,n,i,r){return t.isFunction(n)&&(r=i,i=n,n=void 0),t.isFunction(i)||(r=i,i=void 0),{url:e,data:n,success:i,dataType:r}}function T(e,n,i,r){var o,s=t.isArray(n),a=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),r&&(n=i?r:r+"["+(a||"object"==o||"array"==o?n:"")+"]"),!r&&s?e.add(u.name,u.value):"array"==o||!i&&"object"==o?T(e,u,i,n):e.add(n,u)})}var i,r,e=0,n=window.document,o=/)<[^<]*)*<\/script>/gi,s=/^(?:text|application)\/javascript/i,a=/^(?:text|application)\/xml/i,u="application/json",f="text/html",c=/^\s*$/;t.active=0,t.ajaxJSONP=function(i,r){if(!("type"in i))return t.ajax(i);var f,h,o=i.jsonpCallback,s=(t.isFunction(o)?o():o)||"jsonp"+ ++e,a=n.createElement("script"),u=window[s],c=function(e){t(a).triggerHandler("error",e||"abort")},l={abort:c};return r&&r.promise(l),t(a).on("load error",function(e,n){clearTimeout(h),t(a).off().remove(),"error"!=e.type&&f?g(f[0],l,i,r):v(null,n||"error",l,i,r),window[s]=u,f&&t.isFunction(u)&&u(f[0]),u=f=void 0}),m(l,i)===!1?(c("abort"),l):(window[s]=function(){f=arguments},a.src=i.url.replace(/\?(.+)=\?/,"?$1="+s),n.head.appendChild(a),i.timeout>0&&(h=setTimeout(function(){c("timeout")},i.timeout)),l)},t.ajaxSettings={type:"GET",beforeSend:x,success:x,error:x,complete:x,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:u,xml:"application/xml, text/xml",html:f,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},t.ajax=function(e){var n=t.extend({},e||{}),o=t.Deferred&&t.Deferred();for(i in t.ajaxSettings)void 0===n[i]&&(n[i]=t.ajaxSettings[i]);p(n),n.crossDomain||(n.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(n.url)&&RegExp.$2!=window.location.host),n.url||(n.url=window.location.toString()),E(n);var s=n.dataType,a=/\?.+=\?/.test(n.url);if(a&&(s="jsonp"),n.cache!==!1&&(e&&e.cache===!0||"script"!=s&&"jsonp"!=s)||(n.url=w(n.url,"_="+Date.now())),"jsonp"==s)return a||(n.url=w(n.url,n.jsonp?n.jsonp+"=?":n.jsonp===!1?"":"callback=?")),t.ajaxJSONP(n,o);var j,u=n.accepts[s],f={},l=function(t,e){f[t.toLowerCase()]=[t,e]},h=/^([\w-]+:)\/\//.test(n.url)?RegExp.$1:window.location.protocol,d=n.xhr(),y=d.setRequestHeader;if(o&&o.promise(d),n.crossDomain||l("X-Requested-With","XMLHttpRequest"),l("Accept",u||"*/*"),(u=n.mimeType||u)&&(u.indexOf(",")>-1&&(u=u.split(",",2)[0]),d.overrideMimeType&&d.overrideMimeType(u)),(n.contentType||n.contentType!==!1&&n.data&&"GET"!=n.type.toUpperCase())&&l("Content-Type",n.contentType||"application/x-www-form-urlencoded"),n.headers)for(r in n.headers)l(r,n.headers[r]);if(d.setRequestHeader=l,d.onreadystatechange=function(){if(4==d.readyState){d.onreadystatechange=x,clearTimeout(j);var e,i=!1;if(d.status>=200&&d.status<300||304==d.status||0==d.status&&"file:"==h){s=s||b(n.mimeType||d.getResponseHeader("content-type")),e=d.responseText;try{"script"==s?(1,eval)(e):"xml"==s?e=d.responseXML:"json"==s&&(e=c.test(e)?null:t.parseJSON(e))}catch(r){i=r}i?v(i,"parsererror",d,n,o):g(e,d,n,o)}else v(d.statusText||null,d.status?"error":"abort",d,n,o)}},m(d,n)===!1)return d.abort(),v(null,"abort",d,n,o),d;if(n.xhrFields)for(r in n.xhrFields)d[r]=n.xhrFields[r];var S="async"in n?n.async:!0;d.open(n.type,n.url,S,n.username,n.password);for(r in f)y.apply(d,f[r]);return n.timeout>0&&(j=setTimeout(function(){d.onreadystatechange=x,d.abort(),v(null,"timeout",d,n,o)},n.timeout)),d.send(n.data?n.data:null),d},t.get=function(){return t.ajax(j.apply(null,arguments))},t.post=function(){var e=j.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=j.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,i){if(!this.length)return this;var a,r=this,s=e.split(/\s/),u=j(e,n,i),f=u.success;return s.length>1&&(u.url=s[0],a=s[1]),u.success=function(e){r.html(a?t("
").html(e.replace(o,"")).find(a):e),f&&f.apply(r,arguments)},t.ajax(u),this};var S=encodeURIComponent;t.param=function(t,e){var n=[];return n.add=function(t,e){this.push(S(t)+"="+S(e))},T(n,t,e),n.join("&").replace(/%20/g,"+")}}(Zepto),function(t){t.fn.serializeArray=function(){var n,e=[];return t([].slice.call(this.get(0).elements)).each(function(){n=t(this);var i=n.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=i&&"reset"!=i&&"button"!=i&&("radio"!=i&&"checkbox"!=i||this.checked)&&e.push({name:n.attr("name"),value:n.val()})}),e},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(e)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(Zepto),function(t){"__proto__"in{}||t.extend(t.zepto,{Z:function(e,n){return e=e||[],t.extend(e,t.fn),e.selector=n||"",e.__Z=!0,e},isZ:function(e){return"array"===t.type(e)&&"__Z"in e}});try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;window.getComputedStyle=function(t){try{return n(t)}catch(e){return null}}}}(Zepto); 3 | -------------------------------------------------------------------------------- /SpookyLights/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | 10 | .matrix { 11 | width: 100%; 12 | padding-top: 5px; 13 | } -------------------------------------------------------------------------------- /SpookyLights/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res) { 6 | res.render('index', { title: 'Spooky Lights App' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /SpookyLights/routes/lights.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | // clear a single matrix 5 | router.post('/clear/:device', function(req,res) { 6 | var device = req.params.device; 7 | var matrices = req.app.get("matrices"); 8 | if (matrices) { 9 | matrices.device(device).clear(); 10 | res.send("Cleared matrix " + device); 11 | } else { 12 | res.status(500); 13 | res.send("Matrices not ready"); 14 | } 15 | }); 16 | 17 | // clear all matrixes 18 | router.post('/clear', function(req,res) { 19 | var matrices = req.app.get("matrices"); 20 | if (matrices) { 21 | matrices.clear(); 22 | res.send("Cleared all matrices"); 23 | } else { 24 | res.status(500); 25 | res.send("Matrices not ready"); 26 | } 27 | }); 28 | 29 | // draw pattern for a single matrix 30 | router.post('/draw/:device', function(req, res) { 31 | var device = req.params.device; 32 | var data = req.body; 33 | // get johnny-five matrix object from express app 34 | var matrices = req.app.get("matrices"); 35 | if (matrices) { 36 | matrices.device(device).draw(data); 37 | res.send("Updated matrix " + device); 38 | } else { 39 | res.status(500); 40 | res.send("Matrices not ready"); 41 | } 42 | }); 43 | 44 | module.exports = router; 45 | -------------------------------------------------------------------------------- /SpookyLights/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /SpookyLights/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | div 6 | button.reset Reset pattern 7 | div#matrix.matrix 8 | div 9 | button.send Send to matrix 10 | select.device 11 | option(value="0") 0 12 | option(value="1") 1 13 | option(value="2") 2 14 | option(value="3") 3 15 | option(value="4") 4 16 | option(value="5") 5 17 | option(value="6") 6 18 | div 19 | button.clear Clear all 20 | div.status 21 | script(src="/javascripts/raphael-min.js") 22 | script(src="/javascripts/zepto.min.js") 23 | script(src="/javascripts/matrix-view.js") 24 | -------------------------------------------------------------------------------- /SpookyLights/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /batbot/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Raquel Vélez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /batbot/drive.js: -------------------------------------------------------------------------------- 1 | 2 | var five = require('johnny-five'), 3 | dualshock = require('dualshock-controller'); 4 | 5 | var board, leftServo, rightServo, ds; 6 | 7 | ds = dualshock({ 8 | config: 'dualShock3' 9 | }); 10 | 11 | ds.on('error', function (data) { 12 | console.log('ruh roh something broke'); 13 | }); 14 | 15 | board = new five.Board(); 16 | 17 | board.on("ready", function () { 18 | rightServo = new five.Servo({ 19 | pin: 10, 20 | type: 'continuous' 21 | }); 22 | leftServo = new five.Servo({ 23 | pin: 11, 24 | type: 'continuous' 25 | }); 26 | 27 | leftServo.stop(); 28 | rightServo.stop(); 29 | 30 | var moveSpeed = 0.1; 31 | 32 | function stop() { 33 | leftServo.stop(); 34 | rightServo.stop(); 35 | } 36 | 37 | function turn (rightOn, leftOn, timeout) { 38 | if (rightOn) { 39 | rightServo.cw(moveSpeed); 40 | } else { 41 | rightServo.ccw(moveSpeed); 42 | } 43 | 44 | if (leftOn) { 45 | leftServo.ccw(moveSpeed); 46 | } else { 47 | leftServo.cw(moveSpeed); 48 | } 49 | 50 | if (timeout) { 51 | setTimeout(stop, timeout); 52 | } 53 | } 54 | 55 | function turnLeft (timeout) { 56 | console.log('turning left!'); 57 | turn (false, true, timeout); 58 | } 59 | 60 | function turnRight (timeout) { 61 | console.log('turning right!'); 62 | turn (true, false, timeout); 63 | } 64 | 65 | function goStraight (timeout) { 66 | console.log('going straight!'); 67 | turn (true, true, timeout); 68 | } 69 | 70 | function goBack (timeout) { 71 | console.log('back it up!'); 72 | turn (false, false, timeout); 73 | } 74 | 75 | ds.on('square:press', function () { 76 | turnLeft(); 77 | }); 78 | 79 | ds.on('square:release', function () { 80 | stop(); 81 | }); 82 | 83 | ds.on('circle:press', function () { 84 | turnRight(); 85 | }); 86 | 87 | ds.on('circle:release', function () { 88 | stop(); 89 | }); 90 | 91 | ds.on('triangle:press', function () { 92 | goStraight(); 93 | }); 94 | 95 | ds.on('triangle:release', function () { 96 | stop(); 97 | }); 98 | 99 | ds.on('x:press', function () { 100 | goBack(); 101 | }); 102 | 103 | ds.on('x:release', function () { 104 | stop(); 105 | }); 106 | 107 | }); 108 | 109 | ds.connect(); 110 | -------------------------------------------------------------------------------- /batbot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batbot", 3 | "version": "0.3.0", 4 | "dependencies": { 5 | "johnny-five": "^0.7.30", 6 | "temporal": "0.2.8", 7 | "dualshock-controller": "^0.6.3", 8 | "array-extended": "0.0.9" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /batbot/sonar-scan.js: -------------------------------------------------------------------------------- 1 | var five = require('johnny-five'), 2 | array = require('array-extended'), 3 | temporal = require('temporal'), 4 | ds = require('dualshock-controller')({config: 'dualShock3'}); 5 | 6 | var board, sonarServo, range, leftServo, rightServo; 7 | 8 | board = new five.Board(); 9 | range = [10, 170]; 10 | 11 | board.on("ready", function () { 12 | rightServo = new five.Servo({ 13 | pin: 10, 14 | type: 'continuous' 15 | }); 16 | leftServo = new five.Servo({ 17 | pin: 11, 18 | type: 'continuous' 19 | }); 20 | sonarServo = new five.Servo({ 21 | pin: 12, 22 | range: range 23 | }); 24 | sonar = new five.Sonar({ 25 | pin: 'A2', 26 | freq: 20 27 | }); 28 | 29 | leftServo.stop(); 30 | rightServo.stop(); 31 | 32 | sonarServo.move(15); 33 | var angle = 15, 34 | sonarStep = 10, 35 | moveSpeed = 0.1, 36 | moveTimeout = 500; 37 | 38 | ds.on('r2:press', function () { 39 | console.log(sonar.cm); 40 | }); 41 | ds.on('l2:press', function () { 42 | angle = (range[0] + range[1]) / 2; 43 | sonarServo.center(); 44 | }); 45 | ds.on('dPadLeft:press', function () { 46 | angle = angle < range[0] ? range[0] : angle + sonarStep; 47 | sonarServo.move(angle); 48 | }); 49 | ds.on('dpadRight:press', function () { 50 | angle = angle > range[1] ? range[1] : angle - sonarStep; 51 | sonarServo.move(angle); 52 | }); 53 | ds.on('dpadUp:press', function () { 54 | angle = range[1]; 55 | sonarServo.max(); 56 | }); 57 | ds.on('dpadDown:press', function () { 58 | angle = range[0]; 59 | sonarServo.min(); 60 | }); 61 | 62 | function stop() { 63 | leftServo.stop(); 64 | rightServo.stop(); 65 | } 66 | 67 | function turn (rightOn, leftOn, timeout) { 68 | if (rightOn) { 69 | rightServo.cw(moveSpeed); 70 | } else { 71 | rightServo.ccw(moveSpeed); 72 | } 73 | 74 | if (leftOn) { 75 | leftServo.ccw(moveSpeed); 76 | } else { 77 | leftServo.cw(moveSpeed); 78 | } 79 | 80 | if (timeout) { 81 | setTimeout(stop, timeout); 82 | } 83 | } 84 | 85 | function turnLeft (timeout) { 86 | turn (false, true, timeout); 87 | } 88 | 89 | function turnRight (timeout) { 90 | turn (true, false, timeout); 91 | } 92 | 93 | function goStraight (timeout) { 94 | turn (true, true, timeout); 95 | } 96 | 97 | function goBack (timeout) { 98 | turn (false, false, timeout); 99 | } 100 | 101 | ds.on('square:press', function (data) { 102 | turnLeft(); 103 | }); 104 | 105 | ds.on('square:release', function (data) { 106 | stop(); 107 | }); 108 | 109 | ds.on('circle:press', function (data) { 110 | turnRight(); 111 | }); 112 | 113 | ds.on('circle:release', function (data) { 114 | stop(); 115 | }); 116 | 117 | ds.on('triangle:press', function () { 118 | goStraight(); 119 | }); 120 | 121 | ds.on('triangle:release', function (data) { 122 | stop(); 123 | }); 124 | 125 | ds.on('x:press', function () { 126 | goBack(); 127 | }); 128 | 129 | ds.on('x:release', function (data) { 130 | stop(); 131 | }); 132 | 133 | var scanSpot = function (cb) { 134 | var sonarServoReadings = []; 135 | var read = setInterval(function () { 136 | sonarServoReadings.push(sonar.cm); 137 | if (sonarServoReadings.length === 10) { 138 | clearInterval(read); 139 | console.log(array.avg(sonarServoReadings)); 140 | cb(null, array.avg(sonarServoReadings)); 141 | } 142 | }, 100); 143 | } 144 | 145 | ds.on('select:press', function () { 146 | console.log('IN AUTO MODE'); 147 | // scan box 148 | var minVal = 0; 149 | var temporalLoop = setInterval(function () { 150 | ds.on('r1:press', function () { 151 | clearInterval(temporalLoop); 152 | }); 153 | 154 | var scans = []; 155 | temporal.queue([ 156 | { 157 | delay: 0, 158 | task: function () { 159 | sonarServo.max(); 160 | scanSpot(function (err, val) { 161 | scans.push({dir: 'left', val: val}) 162 | // console.log('left: ', val); 163 | }); 164 | } 165 | }, 166 | { 167 | delay: 1500, 168 | task: function () { 169 | sonarServo.center(); 170 | scanSpot(function (err, val) { 171 | scans.push({dir: 'center', val: val}) 172 | // console.log('center: ', val); 173 | }); 174 | } 175 | }, 176 | { 177 | delay: 1500, 178 | task: function () { 179 | sonarServo.min(); 180 | scanSpot(function (err, val) { 181 | scans.push({dir: 'right', val: val}) 182 | // console.log('right: ', val); 183 | }); 184 | } 185 | }, 186 | { 187 | delay: 1500, 188 | task: function () { 189 | WALL_THRESHOLD = 15; 190 | minVal = array.min(scans, 'val').val; 191 | var maxVal = array.max(scans, 'val'); 192 | console.log(maxVal); 193 | var direction = maxVal.val > WALL_THRESHOLD ? maxVal.dir : 'right'; 194 | console.log(direction); 195 | if (direction === 'center') { 196 | goStraight(1000); 197 | } else if (direction === 'left') { 198 | turnLeft(700); 199 | } else { 200 | turnRight(700); 201 | } 202 | } 203 | } 204 | ]) 205 | }, 6000); 206 | }); 207 | 208 | }); 209 | 210 | ds.connect(); 211 | -------------------------------------------------------------------------------- /cheerful-j5/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | # API keys 28 | .sparkrc 29 | .twitterrc -------------------------------------------------------------------------------- /cheerful-j5/breadboard/cheerfulj5-spark.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/cheerful-j5/breadboard/cheerfulj5-spark.fzz -------------------------------------------------------------------------------- /cheerful-j5/breadboard/cheerfulj5-spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/cheerful-j5/breadboard/cheerfulj5-spark.png -------------------------------------------------------------------------------- /cheerful-j5/breadboard/cheerfulj5-uno.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/cheerful-j5/breadboard/cheerfulj5-uno.fzz -------------------------------------------------------------------------------- /cheerful-j5/breadboard/cheerfulj5-uno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/cheerful-j5/breadboard/cheerfulj5-uno.png -------------------------------------------------------------------------------- /cheerful-j5/cheerful-spark.js: -------------------------------------------------------------------------------- 1 | var request = require("request"), 2 | five = require("johnny-five"), 3 | twit = require("node-tweet-stream"), 4 | colorMap = require("./cheerlights-colors"), 5 | Spark = require("spark-io") 6 | board = new five.Board({ 7 | io: new Spark({ 8 | token: process.env.SPARK_TOKEN, 9 | deviceId: process.env.SPARK_DEVICE_ID 10 | }) 11 | }); 12 | 13 | board.on("ready", function() { 14 | 15 | var lastColor = "white", 16 | led = new five.Led.RGB({ 17 | pins: { 18 | red: "A5", 19 | green: "A6", 20 | blue: "A7" 21 | } 22 | }); 23 | 24 | led.color( colorMap[lastColor] ); 25 | 26 | t = new twit({ 27 | consumer_key: process.env.TWITTER_API_KEY, 28 | consumer_secret: process.env.TWITTER_API_SECRET, 29 | token: process.env.TWITTER_TOKEN, 30 | token_secret: process.env.TWITTER_TOKEN_SECRET 31 | }); 32 | 33 | t.track("@cheerlights"); 34 | t.track("#cheerlights"); 35 | 36 | t.on("tweet", function (tweet) { 37 | // grab first matching supported color in the tweet 38 | Object.keys(colorMap).some(function(color) { 39 | if ( tweet.text.indexOf(color) >= 0 ) { 40 | if ( color != lastColor ) { 41 | lastColor = color; 42 | console.log( "Changing to " + color ); 43 | led.color( colorMap[color] ); 44 | } 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | }); 50 | }); 51 | 52 | t.on("error", function (err) { 53 | console.log("Error with Twitter stream: %o", err); 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /cheerful-j5/cheerful-twit.js: -------------------------------------------------------------------------------- 1 | var request = require("request"), 2 | five = require("johnny-five"), 3 | twit = require("node-tweet-stream"), 4 | colorMap = require("./cheerlights-colors"), 5 | board = new five.Board(); 6 | 7 | board.on("ready", function() { 8 | 9 | var lastColor = "white", 10 | led = new five.Led.RGB([3,5,6]); 11 | 12 | led.color( colorMap[lastColor] ); 13 | 14 | t = new twit({ 15 | consumer_key: process.env.TWITTER_API_KEY, 16 | consumer_secret: process.env.TWITTER_API_SECRET, 17 | token: process.env.TWITTER_TOKEN, 18 | token_secret: process.env.TWITTER_TOKEN_SECRET 19 | }); 20 | 21 | t.track("@cheerlights"); 22 | t.track("#cheerlights"); 23 | 24 | t.on("tweet", function (tweet) { 25 | // grab first matching supported color in the tweet 26 | Object.keys(colorMap).some(function(color) { 27 | if ( tweet.text.indexOf(color) >= 0 ) { 28 | if ( color != lastColor ) { 29 | lastColor = color; 30 | console.log( "Changing to " + color ); 31 | led.color( colorMap[color] ); 32 | } 33 | return true; 34 | } else { 35 | return false; 36 | } 37 | }); 38 | }); 39 | 40 | t.on("error", function (err) { 41 | console.log("Error with Twitter stream: %o", err); 42 | }); 43 | 44 | }); -------------------------------------------------------------------------------- /cheerful-j5/cheerful.js: -------------------------------------------------------------------------------- 1 | var request = require("request"), 2 | five = require("johnny-five"), 3 | colorMap = require("./cheerlights-colors.js"), 4 | board = new five.Board(); 5 | 6 | board.on("ready", function() { 7 | console.log("Connected"); 8 | 9 | var lastColor = "white", 10 | led = new five.Led.RGB([3,5,6]); 11 | 12 | this.repl.inject({ 13 | led: led 14 | }); 15 | 16 | led.color( colorMap[lastColor] ); 17 | 18 | setInterval(function() { 19 | getLatestColor(function(err, color) { 20 | if ( !err && colorMap[color] ) { 21 | if ( color != lastColor ) { 22 | lastColor = color; 23 | console.log( "Changing to " + color ); 24 | led.color( colorMap[color] ); 25 | } 26 | } 27 | }); 28 | }, 3000); 29 | 30 | }); 31 | 32 | function getLatestColor(callback) { 33 | request({ 34 | url: "https://api.thingspeak.com/channels/1417/feed/last.json", 35 | json: true 36 | }, function (error, response, body) { 37 | if (!error && response.statusCode === 200) { 38 | var color = body.field1; 39 | callback(null, color); 40 | } else { 41 | callback(error, null); 42 | } 43 | }); 44 | } -------------------------------------------------------------------------------- /cheerful-j5/cheerlights-colors.js: -------------------------------------------------------------------------------- 1 | var colorMap = { 2 | "red" : "#ff0000", 3 | "green" : "#00ff00", 4 | "blue" : "#0000ff", 5 | "cyan" : "#00ffff", 6 | "white" : "#ffffff", 7 | "warmwhite" : "#fdf5e6", 8 | "oldlace" : "#fdf5e6", // same as warmwhite 9 | "purple" : "#a020f0", 10 | "magenta" : "#ff00ff", 11 | "pink" : "#ff00ff", 12 | "yellow" : "#ffff00", 13 | "orange" : "#ff8c00" 14 | }; 15 | 16 | module.exports = colorMap; -------------------------------------------------------------------------------- /cheerful-j5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cheerful-j5", 3 | "version": "1.0.0", 4 | "description": "Query Cheerlights and update an RGB LED using Johnny-Five", 5 | "dependencies": { 6 | "johnny-five": "^0.8.14", 7 | "node-tweet-stream": "^1.4.0", 8 | "request": "^2.45.0", 9 | "spark-io": "^0.5.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /cheerful-j5/readme.md: -------------------------------------------------------------------------------- 1 | ## Cheerful J5 2 | 3 | Monitors the [@Cheerlights](http://www.cheerlights.com/) service and updates an RGB LED using Johnny-Five. 4 | 5 | Change the color by tweeting to @cheerlights and mentioning a color. 6 | 7 | Assumes wiring as in the [Johnny-Five RGB LED example](https://github.com/rwaldron/johnny-five/blob/master/docs/led-rgb.md). 8 | 9 | 10 | ### cheerful.js 11 | 12 | Queries the CheerLights Thingspeak channel and updates an RGB LED using Johnny-Five. 13 | Uses pins [3, 5, 6] for RGB respectively. 14 | 15 | 16 | ### cheerful-twit.js 17 | 18 | Listens to the Twitter stream for commands sent to @Cheerlights or #cheerlights. 19 | Uses pins [3, 5, 6] for RGB respectively. 20 | 21 | 22 | ### cheerful-spark.js 23 | 24 | Same as cheerful-twit.js but uses a Spark Core to go wireless. 25 | 26 | Assumes SPARK_DEVICE_ID and SPARK_TOKEN environment variables are set for passing Spark access credentials. 27 | Uses pins ["A5", "A6", "A7"] for RGB respectively. 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /delta/delta.js: -------------------------------------------------------------------------------- 1 | /* 2 | ============================ 3 | Moving around a delta robot! 4 | ============================ 5 | 6 | Code adapted from mzavatsky's post on Trossen Robotics: 7 | 8 | http://forums.trossenrobotics.com/tutorials/introduction-129/delta-robot-kinematics-3276/ 9 | 10 | and from TapsterBot by Jason Huggins: 11 | 12 | https://github.com/hugs/tapsterbot 13 | 14 | */ 15 | 16 | var five = require("johnny-five"), 17 | temporal = require("temporal"); 18 | 19 | // Delta Geometry - put your measurements here! 20 | var e = 80.25, 21 | f = 163, 22 | re = 155, 23 | rf = 128.75; 24 | 25 | // Calculates angle theta1 (for YZ-pane) 26 | function delta_calcAngleYZ(x0, y0, z0) { 27 | var y1 = -0.5 * 0.57735 * f; // f/2 * tan(30 degrees) 28 | y0 -= 0.5 * 0.57735 * e; // Shift center to edge of effector 29 | 30 | // z = a + b*y 31 | var a = (x0 * x0 + y0 * y0 + z0 * z0 + rf * rf - re * re - y1 * y1) / (2.0 * z0), 32 | b = (y1 - y0) / z0; 33 | 34 | // Discriminant 35 | var d = -(a + b * y1) * (a + b * y1) + rf * (b * b * rf + rf); 36 | if (d < 0) { 37 | // Non-existing position. return early with error. 38 | return [1, 0]; 39 | } 40 | 41 | // Choose outer position of cicle 42 | var yj = (y1 - a * b - Math.sqrt(d)) / (b * b + 1); 43 | var zj = a + b * yj; 44 | var theta = Math.atan(-zj / (y1 - yj)) * 180.0 / Math.PI + ((yj > y1) ? 180.0 : 0.0); 45 | 46 | return [0, theta]; // Return error, theta 47 | }; 48 | 49 | // Calculate theta for each arm 50 | function inverse(x0, y0, z0) { 51 | var theta1 = 0, 52 | theta2 = 0, 53 | theta3 = 0, 54 | cos120 = Math.cos(Math.PI * (120/180)), 55 | sin120 = Math.sin(Math.PI * (120/180)), 56 | status = delta_calcAngleYZ(x0, y0, z0); 57 | 58 | if (status[0] === 0) { 59 | theta1 = status[1]; 60 | status = delta_calcAngleYZ(x0 * cos120 + y0 * sin120, y0 * cos120 - x0 * sin120, z0, theta2); 61 | } 62 | 63 | if (status[0] === 0) { 64 | theta2 = status[1]; 65 | status = delta_calcAngleYZ(x0 * cos120 - y0 * sin120, y0 * cos120 + x0 * sin120, z0, theta3); 66 | theta3 = status[1]; 67 | } 68 | 69 | return [status[0], theta1, theta2, theta3]; 70 | }; 71 | 72 | 73 | var board = new five.Board(); 74 | 75 | board.on("ready", function() { 76 | 77 | // Setup 78 | var servo1 = five.Servo({ 79 | pin: 9, 80 | range: [0,90] 81 | }); 82 | var servo2 = five.Servo({ 83 | pin: 10, 84 | range: [0,90] 85 | }); 86 | var servo3 = five.Servo({ 87 | pin: 11, 88 | range: [0, 90] 89 | }); 90 | 91 | function go(x, y, z, ms) { 92 | var angles = inverse(x, y, z); 93 | servo1.to(angles[1], ms); 94 | servo2.to(angles[2], ms); 95 | servo3.to(angles[3], ms); 96 | console.log(angles); 97 | }; 98 | 99 | function box() { 100 | temporal.queue([ 101 | { delay: 250, task: function() { go( 30, 30, -160, 250); } }, 102 | { delay: 250, task: function() { go( 30, -30, -160, 250); } }, 103 | { delay: 250, task: function() { go(-30, -30, -160, 250); } }, 104 | { delay: 250, task: function() { go(-30, 30, -160, 250); } }, 105 | { delay: 250, task: function() { go( 30, 30, -160, 250); } } 106 | ]); 107 | } 108 | 109 | board.repl.inject({ 110 | go: go, 111 | box: box 112 | }); 113 | 114 | // Initial position 115 | go(0,0,-150); 116 | 117 | }); 118 | -------------------------------------------------------------------------------- /delta/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delta.js", 3 | "version": "0.1.0", 4 | "description": "Delta Robot control example", 5 | "main": "delta.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Pawel Szymczykowski ", 10 | "contributors": [ 11 | { 12 | "name": "Jason Huggins", 13 | "email": "jrhuggins@gmail.com" 14 | } 15 | ], 16 | "license": "BSD-2-Clause", 17 | "dependencies": { 18 | "johnny-five": "^0.8.18", 19 | "temporal": "^0.3.8" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /delta/template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/delta/template.pdf -------------------------------------------------------------------------------- /delta/warmup.js: -------------------------------------------------------------------------------- 1 | /* 2 | ========================= 3 | Warming up a delta robot! 4 | ========================= 5 | 6 | Running this code will show you if your delta is put together 7 | correct and has the right range of motion and enough flexibility 8 | in its joints. 9 | 10 | */ 11 | 12 | var five = require("johnny-five"), 13 | temporal = require("temporal"), 14 | board = new five.Board(); 15 | 16 | board.on("ready", function() { 17 | var servo1 = five.Servo({ pin: 9, range: [0,90] }), 18 | servo2 = five.Servo({ pin: 10, range: [0,90] }), 19 | servo3 = five.Servo({ pin: 11, range: [0,90] }); 20 | 21 | // Initialize position 22 | servo1.to(20); 23 | servo2.to(20); 24 | servo3.to(20); 25 | 26 | // Warmup Routine 27 | var repeat = function() { 28 | temporal.queue([ 29 | { delay: 250, task: function() { servo1.to(60); } }, 30 | { delay: 250, task: function() { servo2.to(60); } }, 31 | { delay: 250, task: function() { servo3.to(60); } }, 32 | { delay: 250, task: function() { servo1.to(20); } }, 33 | { delay: 250, task: function() { servo2.to(20); } }, 34 | { delay: 250, task: function() { servo3.to(20); } }, 35 | { delay: 250, task: repeat } 36 | ]); 37 | }; 38 | repeat(); 39 | }); -------------------------------------------------------------------------------- /meow-shoes/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Suz Hinton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /meow-shoes/README.md: -------------------------------------------------------------------------------- 1 | # Meow Shoes 2 | 3 | Musical sensor enabled shoes project. The first version of these were created at the Vegas Hack Fashion Tech Hackathon 2013. 4 | 5 | Force sensitive resisters placed at the heel and toe of each shoe communicate via Arduino and serial to sequence musical loops in the browser. 6 | 7 | Demo video from the hackathon is [here](http://www.youtube.com/watch?v=1g3M6PILqqQ "Meow Shoes Demo on Youtube"). 8 | 9 | ![meow shoes](http://f.cl.ly/items/0l2Z2U3t023F1T2J1D21/twinset.jpg "Meow Shoes") 10 | 11 | 12 | ### Other potential uses 13 | 14 | * Make each tap emit a different 'meow' sound effect. Your pets and family might not be all that impressed with this as the novelty wears off. Take it from someone who knows. 15 | * Navigate through an RPG game with your feet 16 | * Send secret morse code messages to your loved ones via the art of interpretive dance 17 | * Create a painting application that lets you make art with simple choreography 18 | * Make another pair for a friend, and have interactive balancing/running/sports competitions 19 | 20 | 21 | ### Stuff I used, written by talented people 22 | 23 | + Web Audio API (Chrome) - [link](http://chimera.labs.oreilly.com/books/1234000001552/ch01.html "O'Reilly Guide to Audio API") 24 | + Abbey Load helper - [link](http://stuartmemo.com/abbey-load/ "Abbey Load website") 25 | + Node.js - [link](http://nodejs.org "Node JS website") 26 | + Socket.io - [link](https://npmjs.org/package/socket.io) 27 | + Johnny-Five - [link](https://npmjs.org/package/johnny-five) 28 | 29 | Thank you, all of the authors of the above libs/modules are way better programmers than me. -------------------------------------------------------------------------------- /meow-shoes/css/meowshoes.css: -------------------------------------------------------------------------------- 1 | #buttonpad { 2 | position: absolute; 3 | top:50px; 4 | left: 50px; 5 | z-index: 9999; 6 | } 7 | 8 | .vish { 9 | position:absolute; 10 | width: 100px; 11 | opacity: 0; 12 | } -------------------------------------------------------------------------------- /meow-shoes/imgs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/.DS_Store -------------------------------------------------------------------------------- /meow-shoes/imgs/d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/d.png -------------------------------------------------------------------------------- /meow-shoes/imgs/meow0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/meow0.png -------------------------------------------------------------------------------- /meow-shoes/imgs/meow1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/meow1.png -------------------------------------------------------------------------------- /meow-shoes/imgs/meow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/meow2.png -------------------------------------------------------------------------------- /meow-shoes/imgs/meow3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/meow3.png -------------------------------------------------------------------------------- /meow-shoes/imgs/voice0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/voice0.png -------------------------------------------------------------------------------- /meow-shoes/imgs/voice1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/voice1.png -------------------------------------------------------------------------------- /meow-shoes/imgs/voice2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/voice2.png -------------------------------------------------------------------------------- /meow-shoes/imgs/voice3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/imgs/voice3.png -------------------------------------------------------------------------------- /meow-shoes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Meow Shoes 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | Stop 20 | Freestyle! 21 |
22 | 23 |
24 | Meow Mode 25 | Voice Mode 26 | Drum Mode 27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /meow-shoes/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/js/.DS_Store -------------------------------------------------------------------------------- /meow-shoes/js/abbeyLoad.js: -------------------------------------------------------------------------------- 1 | /* 2 | * abbeyload.js 3 | * A music asset loader by Stuart Memo 4 | */ 5 | 6 | (function (window, undefined) { 7 | var filesLoaded = 0, 8 | numberOfFiles = 0, 9 | context = new webkitAudioContext(), 10 | buffers = []; 11 | 12 | var AbbeyLoad = function (files, callback) { 13 | this.files = files || {}; 14 | filesLoaded = 0; 15 | numberOfFiles = 0; 16 | loadFiles(this.files, callback); 17 | }; 18 | 19 | var loadFile = function (fileKey, file, returnObj, callback) { 20 | var request = new XMLHttpRequest(); 21 | 22 | request.open('GET', file[fileKey], true); 23 | request.responseType = 'arraybuffer'; 24 | 25 | request.onload = function () { 26 | filesLoaded++; 27 | context.decodeAudioData(request.response, function (decodedBuffer) { 28 | returnObj[fileKey] = decodedBuffer; 29 | if (Object.size(returnObj) === numberOfFiles) { 30 | callback(returnObj); 31 | } 32 | }); 33 | }; 34 | 35 | request.send(); 36 | }; 37 | 38 | var loadFiles = function (files, callback) { 39 | var returnObj = {}; 40 | 41 | files.forEach(function (file, index) { 42 | numberOfFiles = Object.size(file); 43 | for (var key in file) { 44 | if (file.hasOwnProperty(key)) { 45 | loadFile(key, file, returnObj, callback); 46 | } 47 | } 48 | 49 | }); 50 | }; 51 | 52 | window.AbbeyLoad = AbbeyLoad; 53 | })(window); 54 | 55 | Object.size = function(obj) { 56 | var size = 0, key; 57 | for (key in obj) { 58 | if (obj.hasOwnProperty(key)) size++; 59 | } 60 | return size; 61 | }; -------------------------------------------------------------------------------- /meow-shoes/js/assets.js: -------------------------------------------------------------------------------- 1 | // set up the voices and sounds 2 | var voiceSet = { 3 | 'meow_lefttoe' : '../sounds/meow01.mp3', 4 | 'meow_leftheel' : '../sounds/meow02.mp3', 5 | 'meow_righttoe' : '../sounds/meow03.mp3', 6 | 'meow_rightheel' : '../sounds/meow04.mp3', 7 | 'voice_lefttoe' : '../sounds/voice01.mp3', 8 | 'voice_leftheel' : '../sounds/voice02.mp3', 9 | 'voice_righttoe' : '../sounds/voice03.mp3', 10 | 'voice_rightheel': '../sounds/voice04.mp3', 11 | 'drum_lefttoe' : '../sounds/drum01.mp3', 12 | 'drum_leftheel' : '../sounds/drum02.mp3', 13 | 'drum_righttoe' : '../sounds/drum03.mp3', 14 | 'drum_rightheel' : '../sounds/drum04.mp3', 15 | 'm' : '../sounds/click.mp3' 16 | }; 17 | 18 | // set up the visuals 19 | var visualSet = { 20 | 'meow_lefttoe' : 'meow0.png', 21 | 'meow_leftheel' : 'meow1.png', 22 | 'meow_righttoe' : 'meow2.png', 23 | 'meow_rightheel' : 'meow3.png', 24 | 'voice_lefttoe' : 'voice0.png', 25 | 'voice_leftheel' : 'voice1.png', 26 | 'voice_righttoe' : 'voice2.png', 27 | 'voice_rightheel': 'voice3.png', 28 | 'drum_lefttoe' : 'drum1.png', 29 | 'drum_leftheel' : 'drum2.png', 30 | 'drum_righttoe' : 'drum3.png', 31 | 'drum_rightheel' : 'drum4.png', 32 | 'm' : 'd.png' 33 | }; -------------------------------------------------------------------------------- /meow-shoes/js/meow.js: -------------------------------------------------------------------------------- 1 | // playback object, will contain all sequenced sounds 2 | var playback = []; 3 | 4 | // set up the defaults 5 | var freestyle = false, 6 | currentVoice = 'meow', 7 | context = new webkitAudioContext(), 8 | browWidth = $(window).width(), 9 | browHeight = $(window).height(), 10 | source, 11 | sequencer; 12 | 13 | var bar = 16, // 16 beats in the bar 14 | tempo = 120, // bpm 15 | beat = 60 / tempo * 1000, // beat duration 16 | curBeat = 0; 17 | 18 | // load all of the sounds and then when ready kick off the meow shoes setup and bindings 19 | var assets = new AbbeyLoad([voiceSet], function (buffers) { 20 | setupMeowShoes(buffers) 21 | }); 22 | 23 | // this will push a short sound for each beat to the playback object to help the user time their foot taps 24 | function createMetronome() { 25 | for (i = 0; i < bar; i++) { 26 | playback.push({position: i, sensor: 'm'}); 27 | } 28 | } 29 | 30 | // play that sound 31 | function playSound(buffer, time) { 32 | source = context.createBufferSource(); 33 | source.buffer = buffer; 34 | source.connect(context.destination); 35 | source.start(time); 36 | } 37 | 38 | // pretty pictures appearing in random places 39 | function playVisual(image) { 40 | // create node string 41 | var picture = $(''); 42 | // add the image node in the body 43 | $('body').append(picture); 44 | 45 | // place it on the page at random, animate in and out 46 | picture.css({ 47 | 'top': Math.floor((Math.random() * browHeight) + 1 ) + 'px', 48 | 'left': Math.floor((Math.random() * browWidth) + 1 ) + 'px' 49 | }) 50 | .animate({'opacity':1}, 500) 51 | .animate({'opacity':0}, 500, function() { 52 | picture.remove(); 53 | }); 54 | } 55 | 56 | function bindClicks() { 57 | // stop button 58 | $('#stopMusic').click(function(e) { 59 | e.preventDefault(); 60 | clearInterval(sequencer); 61 | }); 62 | 63 | // let's go freestyle! This button is a toggle 64 | $('#freeStyle').click(function(e) { 65 | e.preventDefault(); 66 | freestyle ? false : true; 67 | }); 68 | 69 | // change voices 70 | $('.changeMode').click(function(e) { 71 | e.preventDefault(); 72 | // should I change this to current target? 73 | var newMode = e.target.id.substr(0, e.target.id.length - 4); 74 | currentVoice = newMode; 75 | }); 76 | }; 77 | 78 | // set up the shoes 79 | function setupMeowShoes(buffers) { 80 | 81 | // add metronome to bopper 82 | createMetronome(); 83 | // Loop every n milliseconds, executing a task each time 84 | // the most primitive form of a loop sequencer as a simple example 85 | sequencer = setInterval(function() { 86 | 87 | playback.forEach(function(note){ 88 | 89 | if (note.position === curBeat) { 90 | // play the sound 91 | playSound(buffers[note.sensor], 0); 92 | // play a visual 93 | playVisual(visualSet[note.sensor]); 94 | } 95 | 96 | }); 97 | 98 | console.log(curBeat); 99 | // reset beat back to 0 100 | curBeat = (curBeat === bar - 1) ? 0 : curBeat += 1; 101 | 102 | }, beat); 103 | 104 | // set up the socket connection 105 | socket = io('http://localhost'); 106 | 107 | socket.on('tap', function (data) { 108 | // sensorNum is no longer an accurately descriptive variable name 109 | var sensorNum = data, 110 | sound = currentVoice + '_' + sensorNum; 111 | 112 | // testing... 113 | console.log(sound); 114 | console.log(data); 115 | 116 | // play matching sound immediately to confirm to user 117 | playSound(buffers[sound], 0); 118 | // play a visual also 119 | playVisual(visualSet[sound]); 120 | 121 | // if it's not freestyle queue the sound up 122 | if (!freestyle) { 123 | // push the note to the queue, the temporal way 124 | playback.push({'position': curBeat, 'sensor': sound}) 125 | } 126 | 127 | }); // end socket.on 128 | 129 | bindClicks(); 130 | 131 | }; // end setupMeowShoes -------------------------------------------------------------------------------- /meow-shoes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meowshoes", 3 | "description": "musical shoes", 4 | "author": "Suz Hinton ", 5 | "dependencies": { 6 | "johnny-five": "~0.8.8", 7 | "socket.io": "^1.1.0" 8 | }, 9 | "engines": { 10 | "node": ">= 0.10.0" 11 | } 12 | } -------------------------------------------------------------------------------- /meow-shoes/runshoes.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | five = require('johnny-five'), 3 | board = new five.Board(), 4 | fs = require('fs'); 5 | 6 | function handler(req, res) { 7 | var path = __dirname; 8 | 9 | if (req.url === "/") { 10 | path += "/index.html"; 11 | } else { 12 | path += req.url; 13 | } 14 | 15 | fs.readFile(path, function(err, data) { 16 | if (err) { 17 | res.writeHead(500); 18 | return res.end("Error loading " + path); 19 | } 20 | 21 | res.writeHead(200); 22 | res.end(data); 23 | }); 24 | } 25 | 26 | var app = http.createServer(handler); 27 | var io = require('socket.io')(app); 28 | 29 | app.listen(3000); 30 | 31 | var tapTimeoutThresh = 30; 32 | var pressureThresh = 800; 33 | 34 | var shoes = { 35 | left: { 36 | toe: { val: 0, timeout: 0 }, 37 | heel: { val: 0, timeout: 0 } 38 | }, 39 | right: { 40 | toe: { val: 0, timeout: 0 }, 41 | heel: { val: 0, timeout: 0 } 42 | } 43 | }; 44 | 45 | board.on('ready', function() { 46 | console.log('MAOW!'); 47 | 48 | var leftToe = new five.Sensor({ 49 | pin: 'A0', 50 | freq: 25 51 | }); 52 | 53 | var leftHeel = new five.Sensor({ 54 | pin: 'A1', 55 | freq: 25 56 | }); 57 | 58 | var rightToe = new five.Sensor({ 59 | pin: 'A2', 60 | freq: 25 61 | }); 62 | 63 | var rightHeel = new five.Sensor({ 64 | pin: 'A3', 65 | freq: 25 66 | }); 67 | 68 | rightToe.on('data', function() { 69 | console.log(this.value); 70 | shoes.right.toe.val = this.value; 71 | }); 72 | 73 | rightHeel.on('data', function() { 74 | console.log(this.value); 75 | shoes.right.heel.val = this.value; 76 | }); 77 | 78 | leftToe.on('data', function() { 79 | console.log(this.value); 80 | shoes.left.toe.val = this.value; 81 | }); 82 | 83 | leftHeel.on('data', function() { 84 | console.log(this.value); 85 | shoes.left.heel.val = this.value; 86 | }); 87 | 88 | // main loop to calc foot tap logic 89 | this.loop(10, function() { 90 | 91 | // loop through left and right shoes 92 | for (var foot in shoes) { 93 | 94 | if (!shoes.hasOwnProperty(foot)) continue; 95 | // if user is not standing (both sensors down in a foot) 96 | if (!isStanding(shoes[foot])) { 97 | // loop through heel and toe 98 | for (var sensor in shoes[foot]) { 99 | 100 | if (!shoes[foot].hasOwnProperty(sensor)) continue; 101 | var footSensor = shoes[foot][sensor]; 102 | var timeout = shoes[foot][sensor].timeout; 103 | 104 | if (isPressed(shoes[foot][sensor].val)) { 105 | shoes[foot][sensor].timeout += 1; 106 | // if sensor pressed for long enough, it'll become an official tap 107 | if (timeout > tapTimeoutThresh) { 108 | console.log('emit', foot + sensor); 109 | io.emit('tap', foot + sensor); 110 | shoes[foot][sensor].timeout = 0; 111 | } 112 | 113 | } else { 114 | shoes[foot][sensor].timeout = 0; 115 | } 116 | } 117 | // if standing, reset feet sensor timeouts 118 | } else { 119 | shoes[foot].toe.timeout = 0; 120 | shoes[foot].heel.timeout = 0; 121 | } 122 | 123 | } // end loop 124 | 125 | }); // end j5 main loop 126 | 127 | }); //end board 128 | 129 | function isPressed(val) { 130 | if (val > pressureThresh) { 131 | return true; 132 | } else { 133 | return false; 134 | } 135 | } 136 | 137 | function isStanding(foot) { 138 | if (isPressed(foot.toe.val) && isPressed(foot.heel.val)) { 139 | return true; 140 | } else { 141 | return false; 142 | } 143 | } -------------------------------------------------------------------------------- /meow-shoes/sounds/click.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/click.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/drum01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/drum01.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/drum02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/drum02.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/drum03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/drum03.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/drum04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/drum04.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/meow01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/meow01.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/meow02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/meow02.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/meow03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/meow03.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/meow04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/meow04.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/voice01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/voice01.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/voice02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/voice02.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/voice03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/voice03.mp3 -------------------------------------------------------------------------------- /meow-shoes/sounds/voice04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/sounds/voice04.mp3 -------------------------------------------------------------------------------- /meow-shoes/stls/arduino_micro_cableslot.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/meow-shoes/stls/arduino_micro_cableslot.stl -------------------------------------------------------------------------------- /physical-security/index.js: -------------------------------------------------------------------------------- 1 | var five = require('johnny-five') 2 | var twilio = require('twilio') 3 | 4 | var board 5 | var init = false 6 | var trips = 0 7 | var isArmed = false 8 | 9 | var armButton 10 | var resetButton 11 | var statusLight 12 | 13 | var ultraSensor 14 | var ultraBaseline 15 | var ultraReadings = [ ] 16 | var ultraThreshold = 24 17 | var ultraTriggered = false 18 | 19 | var magnetSensor 20 | var magnetTriggered = false 21 | 22 | var photoSensor 23 | var photoReading = 0 24 | var photoThreshold = 100 25 | var photoTriggered = false 26 | 27 | var accountSid = 'AC4a15b85283a3d7f79395276ffc805970' 28 | var authToken = 'f5cb23e4cfdf522277b510541ee30468' 29 | var client = twilio(accountSid, authToken) 30 | var lastSMS = 0 31 | var ratelimit = 5000 32 | 33 | board = new five.Board() 34 | board.on('ready', function ready() { 35 | 36 | armButton = new five.Button({ 37 | 38 | isPullup : true 39 | , pin : 8 40 | }) 41 | armButton.on('up', armDisarm) 42 | 43 | resetButton = new five.Button({ 44 | 45 | isPullup : true 46 | , pin : 10 47 | }) 48 | resetButton.on('up', ultraReset) 49 | 50 | statusLight = new five.Led.RGB({ 51 | pins : { 52 | red : 6 53 | , green : 5 54 | , blue : 3 55 | } 56 | }) 57 | 58 | ultraSensor = new five.Ping(11) 59 | ultraSensor.on('change', ultraChange) 60 | ultraSensor.on('data', ultraData) 61 | 62 | magnetSensor = new five.Button({ 63 | 64 | isPullup : true 65 | , pin : 12 66 | }) 67 | magnetSensor.on('up', function() { 68 | 69 | trigger('magnet') 70 | }) 71 | 72 | photoSensor = new five.Sensor({ 73 | 74 | pin : "A0" 75 | , freq : 250 76 | }) 77 | photoSensor.on('data', photoData) 78 | 79 | }) 80 | 81 | /** 82 | * Called when the sensor has detected a change in distance 83 | * from the last measurement. Activate alarm if we've already 84 | * initialized, and the change is greater than our threshhold. 85 | */ 86 | function ultraChange() { 87 | 88 | if(!init) { return } 89 | var data = this.inches 90 | 91 | if(Math.abs(data - ultraBaseline) > ultraThreshold) { 92 | 93 | // if we haven't already triggered the alarm, do it! 94 | if(!ultraTriggered) { 95 | 96 | trigger('ultrasonic') 97 | return ultraTriggered = true 98 | } 99 | } 100 | ultraTriggered = false 101 | } 102 | 103 | /** 104 | * Called when the ultrasonic sensor reports data, which is done 105 | * on an interval. Fires even when no change was detected. 106 | */ 107 | function ultraData() { 108 | 109 | var inches = this.inches 110 | if(ultraReadings.length >= 10) { 111 | 112 | ultraReadings.shift() 113 | if(!init) { 114 | 115 | ultraBaseline = ultraReadings.sort()[4] 116 | console.log("Calculated baseline: %s", ultraBaseline) 117 | armDisarm(true) 118 | } 119 | init = true 120 | } 121 | else { 122 | 123 | statusLight.color('#0000FF') 124 | } 125 | ultraReadings.push(inches) 126 | } 127 | 128 | /** 129 | * Reset the baseline measurement for the ultrasonic sensor 130 | * Called when the reset button is pressed 131 | */ 132 | function ultraReset() { 133 | 134 | console.log("* Resetting...") 135 | ultraReadings = [ ] 136 | init = false 137 | } 138 | 139 | /** 140 | * Called when the photovoltaic sensor reports data. Also done 141 | * on an interval. Fires even when no change was detected. 142 | */ 143 | function photoData() { 144 | 145 | var data = this.value 146 | if(Math.abs(data - photoReading) > photoThreshold) { 147 | 148 | if(!photoTriggered) { 149 | 150 | trigger('laser') 151 | } 152 | return photoTriggered = true 153 | } 154 | photoTriggered = false 155 | photoReading = data 156 | } 157 | 158 | /** 159 | * Called when the arm/disarm button is pressed. 160 | * Changes the color of the status LED, and prevents 161 | * SMS messages from being sent 162 | */ 163 | function armDisarm(override) { 164 | 165 | if(typeof override == 'boolean') { 166 | 167 | isArmed = override 168 | } 169 | else { isArmed = !isArmed } 170 | 171 | if(isArmed) { 172 | 173 | console.log("* Arming") 174 | statusLight.color('#FF0000') 175 | } 176 | else { 177 | 178 | console.log("* Disarming") 179 | statusLight.color('#00FF00') 180 | } 181 | } 182 | 183 | /** 184 | * Called when a sensor has detected an event 185 | * Actually sends the SMS via Twilio, and logs 186 | * which sensor caused the trigger 187 | */ 188 | function trigger(sensor) { 189 | 190 | if(!isArmed) { return } 191 | var now = (new Date()).valueOf() 192 | 193 | console.log("* Alarm has been triggered (%s) [%s]" 194 | , sensor 195 | , trips 196 | ) 197 | if(now - ratelimit < lastSMS) { 198 | 199 | return console.log("> Ratelimiting.") 200 | } 201 | 202 | ++trips 203 | lastSMS = now 204 | client.messages.create({ 205 | 206 | body : "Alarm has been triggered by " + sensor 207 | , to : "+1 415-374-9129" 208 | , from : "+1 415-599-2671" 209 | }, function smsResults(err, msg) { 210 | 211 | if(err) { 212 | 213 | console.log("*** ERROR ***\n") 214 | return console.log(err) 215 | } 216 | if(!msg.errorCode) { 217 | 218 | return console.log("> Success!") 219 | } 220 | console.log("> Problem: %s", msg.errorCode) 221 | }) 222 | console.log("> Sending SMS.") 223 | } -------------------------------------------------------------------------------- /physical-security/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orobos", 3 | "version": "1.0.0", 4 | "description": "johnny-five does physical security", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/nexxy/orobos.git" 9 | }, 10 | "keywords": [ 11 | "johnny-five", 12 | "arduino", 13 | "physical", 14 | "security", 15 | "alarm", 16 | ], 17 | "author": "Emily Rose ", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/nexxy/orobos/issues" 21 | }, 22 | "homepage": "https://github.com/nexxy/orobos", 23 | "dependencies": { 24 | "johnny-five": "^0.8.14", 25 | "twilio": "^1.7.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /piduino5/piDuino5-webapp/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | bower_components 27 | 28 | # Users Environment Variables 29 | .lock-wscript -------------------------------------------------------------------------------- /piduino5/piDuino5-webapp/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /piduino5/piDuino5-webapp/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | app = express(), 3 | PORT = 3000, 4 | lastLocation = { 5 | localip: '127.0.0.1', 6 | publicurl: 'http://example.com', 7 | lastUpdate: new Date().toString() 8 | }; 9 | 10 | //configure Express 11 | app.use('/bower_components', express.static(__dirname + '/bower_components')); 12 | app.engine('.html', require('ejs').__express); 13 | app.set('view engine', 'html'); 14 | 15 | var server = app.listen(PORT, function() { 16 | console.log('Listening on port %d', server.address().port); 17 | }); 18 | 19 | app.get('/', function(req, res) { 20 | res.render('index', lastLocation); 21 | }); 22 | 23 | app.post('/locate', function(req, res) { 24 | lastLocation = { 25 | localip: req.param('local_ip'), 26 | publicurl: req.param('public_url'), 27 | lastUpdate: new Date().toString() 28 | }; 29 | console.log('lastLocation updated to %O', lastLocation); 30 | }); -------------------------------------------------------------------------------- /piduino5/piDuino5-webapp/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "piDuino5-webapp", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/beriberikix/piDuino5", 5 | "authors": [ 6 | "Jonathan Beri " 7 | ], 8 | "description": "Frontend for piDuino5", 9 | "main": "index.html", 10 | "keywords": [ 11 | "raspberrypi", 12 | "rpi", 13 | "arduno", 14 | "johnny-five" 15 | ], 16 | "license": "Apache-2.0", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ], 24 | "dependencies": { 25 | "virtualjoystick.js": "*", 26 | "uri.js": "~1.14.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /piduino5/piDuino5-webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "piDuino5-webapp", 3 | "description": "Frontend for piDuino5 - a mobile robot built on johnny-five", 4 | "homepage": "https://github.com/beriberikix/piDuino5-webapp", 5 | "version": "0.0.1", 6 | "author": { 7 | "name": "Jonathan Beri ", 8 | "email": "jmberi@gmail.com" 9 | }, 10 | "keywords": [ 11 | "arduino", 12 | "raspberrypi", 13 | "firmata", 14 | "robot", 15 | "raspberry pi", 16 | "rpi", 17 | "johnny-five" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/beriberikix/piDuino5-webapp.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/beriberikix/piDuino5-webapp/issues" 25 | }, 26 | "licenses": [ 27 | { 28 | "type": "Apache-2.0", 29 | "url": "https://github.com/beriberikix/piDuino5-webapp/blob/master/LICENSE" 30 | } 31 | ], 32 | "dependencies": { 33 | "express": "4.x", 34 | "ejs": "^1.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /piduino5/piDuino5-webapp/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 23 | 24 | PiDuino 25 | 26 | 27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | 35 | 69 | 70 | -------------------------------------------------------------------------------- /piduino5/piDuino5/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | bower_components 27 | 28 | # Users Environment Variables 29 | .lock-wscript 30 | -------------------------------------------------------------------------------- /piduino5/piDuino5/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /piduino5/piDuino5/app.js: -------------------------------------------------------------------------------- 1 | var five = require('johnny-five'), 2 | board = new five.Board(), 3 | PORT = 8080, 4 | WebSocketServer = require('ws').Server, 5 | localtunnel = require('localtunnel'), 6 | request = require('request'), 7 | networkInterfaces = require('os').networkInterfaces(), 8 | motors = {}, 9 | led = {}; 10 | 11 | var wss = new WebSocketServer({port: PORT}); 12 | 13 | // board setup 14 | board.on('ready', function() { 15 | motors = { 16 | left: new five.Motor({ 17 | pins: { 18 | pwm: 3, 19 | dir: 12 20 | }, 21 | invertPWM: true 22 | }), 23 | right: new five.Motor({ 24 | pins: { 25 | pwm: 5, 26 | dir: 8 27 | }, 28 | invertPWM: true 29 | }) 30 | }; 31 | 32 | led = new five.Led(13); 33 | }); 34 | 35 | // ws setup 36 | wss.on('connection', function(ws) { 37 | ws.on('message', function(data, flags) { 38 | if(data === 'forward') { 39 | forward(255); 40 | } else if(data === 'reverse') { 41 | reverse(255); 42 | } else if(data === 'turnRight') { 43 | turnRight(255); 44 | } else if(data === 'turnLeft') { 45 | turnLeft(255); 46 | } else if(data === 'stop') { 47 | stop(); 48 | } else if(data === 'blink') { 49 | blink(); 50 | } else if(data === 'noBlink') { 51 | noBlink(); 52 | } 53 | }); 54 | 55 | ws.on('close', function() { 56 | console.log('WebSocket connection closed'); 57 | }); 58 | 59 | ws.on('error', function(e) { 60 | console.log('WebSocket error: %s', e.message); 61 | }); 62 | 63 | }); 64 | 65 | // motor functions 66 | var stop = function() { 67 | motors.left.stop(); 68 | motors.right.stop(); 69 | }; 70 | 71 | var forward = function(speed) { 72 | motors.left.forward(speed); 73 | motors.right.forward(speed); 74 | }; 75 | 76 | var reverse = function(speed) { 77 | motors.left.reverse(speed); 78 | motors.right.reverse(speed); 79 | }; 80 | 81 | var turnRight = function(speed) { 82 | motors.left.forward(speed); 83 | motors.right.reverse(speed); 84 | }; 85 | 86 | var turnLeft = function(speed) { 87 | motors.left.reverse(speed); 88 | motors.right.forward(speed); 89 | }; 90 | 91 | var blink = function() { 92 | led.strobe(300); 93 | }; 94 | 95 | var noBlink = function() { 96 | led.stop(); 97 | }; 98 | 99 | // create localtunnel and send to the webapp 100 | localtunnel(PORT, function(err, tunnel) { 101 | var webappURL = 'http://localhost:3000', 102 | localIP; 103 | 104 | console.log('localtunnel address is %s', tunnel.url); 105 | 106 | // local_ip is useful for debugging 107 | // use en0 if on mac while developing 108 | if(networkInterfaces.wlan0) { 109 | localIP = networkInterfaces.wlan0[0].address; 110 | } else { 111 | localIP = networkInterfaces.en0[1].address; 112 | } 113 | 114 | webappURL += '/locate?local_ip=' + localIP; 115 | webappURL += '&public_url=' + tunnel.url; 116 | 117 | request.post(webappURL, function(e, r, body) { 118 | if (err) { 119 | return console.error('POST request failed:', err); 120 | } 121 | }); 122 | }); -------------------------------------------------------------------------------- /piduino5/piDuino5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "piDuino5", 3 | "description": "A mobile robot built on johnny-five", 4 | "homepage": "https://github.com/beriberikix/piDuino5", 5 | "version": "1.0.0", 6 | "author": { 7 | "name": "Jonathan Beri ", 8 | "email": "jmberi@gmail.com" 9 | }, 10 | "keywords": [ 11 | "arduino", 12 | "raspberrypi", 13 | "firmata", 14 | "robot", 15 | "raspberry pi", 16 | "rpi", 17 | "johnny-five" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/beriberikix/piDuino5.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/beriberikix/piDuino5/issues" 25 | }, 26 | "licenses": [ 27 | { 28 | "type": "Apache-2.0", 29 | "url": "https://github.com/beriberikix/piDuino5/blob/master/LICENSE" 30 | } 31 | ], 32 | "dependencies": { 33 | "ws": "~0.4.32", 34 | "localtunnel": "~1.4.0", 35 | "johnny-five": "~0.8.14", 36 | "request": "~2.45.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/readme.md -------------------------------------------------------------------------------- /simplebot/README.md: -------------------------------------------------------------------------------- 1 | # SimpleBot Examples 2 | 3 | There are several folders here you can use for the simplebot project. 4 | 5 | ## JavaScript 6 | 7 | In the examples folder you'll find several code examples present in the book 8 | that you can play with. 9 | 10 | * servo-test.js will test that the servos are wired correctly 11 | * simplebot.js allows you to drive your bot using the keys 12 | * collision-avoid.js allows your bot to drive more autonomously avoiding collisions 13 | 14 | To run any of these, install the dependencies using npm 15 | 16 | ``` 17 | npm install 18 | ``` 19 | 20 | Then just run the examples passing in the serial port required (will vary 21 | depending on your OS). Use /dev/tty* for Linux / Mac or COM* for Windows where 22 | the * is the relevant port number / name required. 23 | 24 | EG on Linux: 25 | 26 | ``` 27 | node examples/simplebot.js /dev/ttyUSB0 28 | ``` 29 | 30 | ## Design files 31 | 32 | In the physical folder are a set of design files for the SimpleBot. 33 | 34 | * simplebot.svg is a vector file that can be printed as a template to stick 35 | on cardboard or corflute and cut out your simplebot 36 | * skid.stl is an object you can print on a 3D printer that will take a 30mm M3 37 | bolt and allows your SimpleBot to balance and not drag on the floor. 38 | 39 | ## SimpleBot Firmata (Arduino) 40 | 41 | In the arduino folder is the SimpleBotFirmata sketch. This is a custom version 42 | of Firmata that has a couple of newer firmata features included, in particular 43 | the ability to use ultrasonic sensors using the pulseIn API. 44 | -------------------------------------------------------------------------------- /simplebot/arduino/SimpleBotFirmata/Boards.h: -------------------------------------------------------------------------------- 1 | /* Boards.h - Hardware Abstraction Layer for Firmata library */ 2 | 3 | #ifndef Firmata_Boards_h 4 | #define Firmata_Boards_h 5 | 6 | #include 7 | 8 | #if defined(ARDUINO) && ARDUINO >= 100 9 | #include "Arduino.h" // for digitalRead, digitalWrite, etc 10 | #else 11 | #include "WProgram.h" 12 | #endif 13 | 14 | // Normally Servo.h must be included before Firmata.h (which then includes 15 | // this file). If Servo.h wasn't included, this allows the code to still 16 | // compile, but without support for any Servos. Hopefully that's what the 17 | // user intended by not including Servo.h 18 | #ifndef MAX_SERVOS 19 | #define MAX_SERVOS 0 20 | #endif 21 | 22 | /* 23 | Firmata Hardware Abstraction Layer 24 | 25 | Firmata is built on top of the hardware abstraction functions of Arduino, 26 | specifically digitalWrite, digitalRead, analogWrite, analogRead, and 27 | pinMode. While these functions offer simple integer pin numbers, Firmata 28 | needs more information than is provided by Arduino. This file provides 29 | all other hardware specific details. To make Firmata support a new board, 30 | only this file should require editing. 31 | 32 | The key concept is every "pin" implemented by Firmata may be mapped to 33 | any pin as implemented by Arduino. Usually a simple 1-to-1 mapping is 34 | best, but such mapping should not be assumed. This hardware abstraction 35 | layer allows Firmata to implement any number of pins which map onto the 36 | Arduino implemented pins in almost any arbitrary way. 37 | 38 | 39 | General Constants: 40 | 41 | These constants provide basic information Firmata requires. 42 | 43 | TOTAL_PINS: The total number of pins Firmata implemented by Firmata. 44 | Usually this will match the number of pins the Arduino functions 45 | implement, including any pins pins capable of analog or digital. 46 | However, Firmata may implement any number of pins. For example, 47 | on Arduino Mini with 8 analog inputs, 6 of these may be used 48 | for digital functions, and 2 are analog only. On such boards, 49 | Firmata can implement more pins than Arduino's pinMode() 50 | function, in order to accommodate those special pins. The 51 | Firmata protocol supports a maximum of 128 pins, so this 52 | constant must not exceed 128. 53 | 54 | TOTAL_ANALOG_PINS: The total number of analog input pins implemented. 55 | The Firmata protocol allows up to 16 analog inputs, accessed 56 | using offsets 0 to 15. Because Firmata presents the analog 57 | inputs using different offsets than the actual pin numbers 58 | (a legacy of Arduino's analogRead function, and the way the 59 | analog input capable pins are physically labeled on all 60 | Arduino boards), the total number of analog input signals 61 | must be specified. 16 is the maximum. 62 | 63 | VERSION_BLINK_PIN: When Firmata starts up, it will blink the version 64 | number. This constant is the Arduino pin number where a 65 | LED is connected. 66 | 67 | 68 | Pin Mapping Macros: 69 | 70 | These macros provide the mapping between pins as implemented by 71 | Firmata protocol and the actual pin numbers used by the Arduino 72 | functions. Even though such mappings are often simple, pin 73 | numbers received by Firmata protocol should always be used as 74 | input to these macros, and the result of the macro should be 75 | used with with any Arduino function. 76 | 77 | When Firmata is extended to support a new pin mode or feature, 78 | a pair of macros should be added and used for all hardware 79 | access. For simple 1:1 mapping, these macros add no actual 80 | overhead, yet their consistent use allows source code which 81 | uses them consistently to be easily adapted to all other boards 82 | with different requirements. 83 | 84 | IS_PIN_XXXX(pin): The IS_PIN macros resolve to true or non-zero 85 | if a pin as implemented by Firmata corresponds to a pin 86 | that actually implements the named feature. 87 | 88 | PIN_TO_XXXX(pin): The PIN_TO macros translate pin numbers as 89 | implemented by Firmata to the pin numbers needed as inputs 90 | to the Arduino functions. The corresponding IS_PIN macro 91 | should always be tested before using a PIN_TO macro, so 92 | these macros only need to handle valid Firmata pin 93 | numbers for the named feature. 94 | 95 | 96 | Port Access Inline Funtions: 97 | 98 | For efficiency, Firmata protocol provides access to digital 99 | input and output pins grouped by 8 bit ports. When these 100 | groups of 8 correspond to actual 8 bit ports as implemented 101 | by the hardware, these inline functions can provide high 102 | speed direct port access. Otherwise, a default implementation 103 | using 8 calls to digitalWrite or digitalRead is used. 104 | 105 | When porting Firmata to a new board, it is recommended to 106 | use the default functions first and focus only on the constants 107 | and macros above. When those are working, if optimized port 108 | access is desired, these inline functions may be extended. 109 | The recommended approach defines a symbol indicating which 110 | optimization to use, and then conditional complication is 111 | used within these functions. 112 | 113 | readPort(port, bitmask): Read an 8 bit port, returning the value. 114 | port: The port number, Firmata pins port*8 to port*8+7 115 | bitmask: The actual pins to read, indicated by 1 bits. 116 | 117 | writePort(port, value, bitmask): Write an 8 bit port. 118 | port: The port number, Firmata pins port*8 to port*8+7 119 | value: The 8 bit value to write 120 | bitmask: The actual pins to write, indicated by 1 bits. 121 | */ 122 | 123 | /*============================================================================== 124 | * Board Specific Configuration 125 | *============================================================================*/ 126 | 127 | #ifndef digitalPinHasPWM 128 | #define digitalPinHasPWM(p) IS_PIN_DIGITAL(p) 129 | #endif 130 | 131 | // Arduino Duemilanove, Diecimila, and NG 132 | #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) 133 | #if defined(NUM_ANALOG_INPUTS) && NUM_ANALOG_INPUTS == 6 134 | #define TOTAL_ANALOG_PINS 6 135 | #define TOTAL_PINS 20 // 14 digital + 6 analog 136 | #else 137 | #define TOTAL_ANALOG_PINS 8 138 | #define TOTAL_PINS 22 // 14 digital + 8 analog 139 | #endif 140 | #define VERSION_BLINK_PIN 13 141 | #define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) 142 | #define IS_PIN_ANALOG(p) ((p) >= 14 && (p) < 14 + TOTAL_ANALOG_PINS) 143 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 144 | #define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) 145 | #define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) 146 | #define PIN_TO_DIGITAL(p) (p) 147 | #define PIN_TO_ANALOG(p) ((p) - 14) 148 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 149 | #define PIN_TO_SERVO(p) ((p) - 2) 150 | #define ARDUINO_PINOUT_OPTIMIZE 1 151 | 152 | 153 | // Wiring (and board) 154 | #elif defined(WIRING) 155 | #define VERSION_BLINK_PIN WLED 156 | #define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) 157 | #define IS_PIN_ANALOG(p) ((p) >= FIRST_ANALOG_PIN && (p) < (FIRST_ANALOG_PIN+TOTAL_ANALOG_PINS)) 158 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 159 | #define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) 160 | #define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL) 161 | #define PIN_TO_DIGITAL(p) (p) 162 | #define PIN_TO_ANALOG(p) ((p) - FIRST_ANALOG_PIN) 163 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 164 | #define PIN_TO_SERVO(p) (p) 165 | 166 | 167 | // old Arduinos 168 | #elif defined(__AVR_ATmega8__) 169 | #define TOTAL_ANALOG_PINS 6 170 | #define TOTAL_PINS 20 // 14 digital + 6 analog 171 | #define VERSION_BLINK_PIN 13 172 | #define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) <= 19) 173 | #define IS_PIN_ANALOG(p) ((p) >= 14 && (p) <= 19) 174 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 175 | #define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && (p) - 2 < MAX_SERVOS) 176 | #define IS_PIN_I2C(p) ((p) == 18 || (p) == 19) 177 | #define PIN_TO_DIGITAL(p) (p) 178 | #define PIN_TO_ANALOG(p) ((p) - 14) 179 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 180 | #define PIN_TO_SERVO(p) ((p) - 2) 181 | #define ARDUINO_PINOUT_OPTIMIZE 1 182 | 183 | 184 | // Arduino Mega 185 | #elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) 186 | #define TOTAL_ANALOG_PINS 16 187 | #define TOTAL_PINS 70 // 54 digital + 16 analog 188 | #define VERSION_BLINK_PIN 13 189 | #define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) 190 | #define IS_PIN_ANALOG(p) ((p) >= 54 && (p) < TOTAL_PINS) 191 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 192 | #define IS_PIN_SERVO(p) ((p) >= 2 && (p) - 2 < MAX_SERVOS) 193 | #define IS_PIN_I2C(p) ((p) == 20 || (p) == 21) 194 | #define PIN_TO_DIGITAL(p) (p) 195 | #define PIN_TO_ANALOG(p) ((p) - 54) 196 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 197 | #define PIN_TO_SERVO(p) ((p) - 2) 198 | 199 | 200 | // Teensy 1.0 201 | #elif defined(__AVR_AT90USB162__) 202 | #define TOTAL_ANALOG_PINS 0 203 | #define TOTAL_PINS 21 // 21 digital + no analog 204 | #define VERSION_BLINK_PIN 6 205 | #define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) 206 | #define IS_PIN_ANALOG(p) (0) 207 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 208 | #define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) 209 | #define IS_PIN_I2C(p) (0) 210 | #define PIN_TO_DIGITAL(p) (p) 211 | #define PIN_TO_ANALOG(p) (0) 212 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 213 | #define PIN_TO_SERVO(p) (p) 214 | 215 | 216 | // Teensy 2.0 217 | #elif defined(__AVR_ATmega32U4__) 218 | #define TOTAL_ANALOG_PINS 12 219 | #define TOTAL_PINS 25 // 11 digital + 12 analog 220 | #define VERSION_BLINK_PIN 11 221 | #define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) 222 | #define IS_PIN_ANALOG(p) ((p) >= 11 && (p) <= 22) 223 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 224 | #define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) 225 | #define IS_PIN_I2C(p) ((p) == 5 || (p) == 6) 226 | #define PIN_TO_DIGITAL(p) (p) 227 | #define PIN_TO_ANALOG(p) (((p)<22)?21-(p):11) 228 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 229 | #define PIN_TO_SERVO(p) (p) 230 | 231 | 232 | // Teensy++ 1.0 and 2.0 233 | #elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) 234 | #define TOTAL_ANALOG_PINS 8 235 | #define TOTAL_PINS 46 // 38 digital + 8 analog 236 | #define VERSION_BLINK_PIN 6 237 | #define IS_PIN_DIGITAL(p) ((p) >= 0 && (p) < TOTAL_PINS) 238 | #define IS_PIN_ANALOG(p) ((p) >= 38 && (p) < TOTAL_PINS) 239 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 240 | #define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) 241 | #define IS_PIN_I2C(p) ((p) == 0 || (p) == 1) 242 | #define PIN_TO_DIGITAL(p) (p) 243 | #define PIN_TO_ANALOG(p) ((p) - 38) 244 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 245 | #define PIN_TO_SERVO(p) (p) 246 | 247 | 248 | // Sanguino 249 | #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) 250 | #define TOTAL_ANALOG_PINS 8 251 | #define TOTAL_PINS 32 // 24 digital + 8 analog 252 | #define VERSION_BLINK_PIN 0 253 | #define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) 254 | #define IS_PIN_ANALOG(p) ((p) >= 24 && (p) < TOTAL_PINS) 255 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 256 | #define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) 257 | #define IS_PIN_I2C(p) ((p) == 16 || (p) == 17) 258 | #define PIN_TO_DIGITAL(p) (p) 259 | #define PIN_TO_ANALOG(p) ((p) - 24) 260 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 261 | #define PIN_TO_SERVO(p) ((p) - 2) 262 | 263 | 264 | // Illuminato 265 | #elif defined(__AVR_ATmega645__) 266 | #define TOTAL_ANALOG_PINS 6 267 | #define TOTAL_PINS 42 // 36 digital + 6 analog 268 | #define VERSION_BLINK_PIN 13 269 | #define IS_PIN_DIGITAL(p) ((p) >= 2 && (p) < TOTAL_PINS) 270 | #define IS_PIN_ANALOG(p) ((p) >= 36 && (p) < TOTAL_PINS) 271 | #define IS_PIN_PWM(p) digitalPinHasPWM(p) 272 | #define IS_PIN_SERVO(p) ((p) >= 0 && (p) < MAX_SERVOS) 273 | #define IS_PIN_I2C(p) ((p) == 4 || (p) == 5) 274 | #define PIN_TO_DIGITAL(p) (p) 275 | #define PIN_TO_ANALOG(p) ((p) - 36) 276 | #define PIN_TO_PWM(p) PIN_TO_DIGITAL(p) 277 | #define PIN_TO_SERVO(p) ((p) - 2) 278 | 279 | 280 | // anything else 281 | #else 282 | #error "Please edit Boards.h with a hardware abstraction for this board" 283 | #endif 284 | 285 | 286 | /*============================================================================== 287 | * readPort() - Read an 8 bit port 288 | *============================================================================*/ 289 | 290 | static inline unsigned char readPort(byte, byte) __attribute__((always_inline, unused)); 291 | static inline unsigned char readPort(byte port, byte bitmask) 292 | { 293 | #if defined(ARDUINO_PINOUT_OPTIMIZE) 294 | if (port == 0) return (PIND & 0xFC) & bitmask; // ignore Rx/Tx 0/1 295 | if (port == 1) return ((PINB & 0x3F) | ((PINC & 0x03) << 6)) & bitmask; 296 | if (port == 2) return ((PINC & 0x3C) >> 2) & bitmask; 297 | return 0; 298 | #else 299 | unsigned char out=0, pin=port*8; 300 | if (IS_PIN_DIGITAL(pin+0) && (bitmask & 0x01) && digitalRead(PIN_TO_DIGITAL(pin+0))) out |= 0x01; 301 | if (IS_PIN_DIGITAL(pin+1) && (bitmask & 0x02) && digitalRead(PIN_TO_DIGITAL(pin+1))) out |= 0x02; 302 | if (IS_PIN_DIGITAL(pin+2) && (bitmask & 0x04) && digitalRead(PIN_TO_DIGITAL(pin+2))) out |= 0x04; 303 | if (IS_PIN_DIGITAL(pin+3) && (bitmask & 0x08) && digitalRead(PIN_TO_DIGITAL(pin+3))) out |= 0x08; 304 | if (IS_PIN_DIGITAL(pin+4) && (bitmask & 0x10) && digitalRead(PIN_TO_DIGITAL(pin+4))) out |= 0x10; 305 | if (IS_PIN_DIGITAL(pin+5) && (bitmask & 0x20) && digitalRead(PIN_TO_DIGITAL(pin+5))) out |= 0x20; 306 | if (IS_PIN_DIGITAL(pin+6) && (bitmask & 0x40) && digitalRead(PIN_TO_DIGITAL(pin+6))) out |= 0x40; 307 | if (IS_PIN_DIGITAL(pin+7) && (bitmask & 0x80) && digitalRead(PIN_TO_DIGITAL(pin+7))) out |= 0x80; 308 | return out; 309 | #endif 310 | } 311 | 312 | /*============================================================================== 313 | * writePort() - Write an 8 bit port, only touch pins specified by a bitmask 314 | *============================================================================*/ 315 | 316 | static inline unsigned char writePort(byte, byte, byte) __attribute__((always_inline, unused)); 317 | static inline unsigned char writePort(byte port, byte value, byte bitmask) 318 | { 319 | #if defined(ARDUINO_PINOUT_OPTIMIZE) 320 | if (port == 0) { 321 | bitmask = bitmask & 0xFC; // do not touch Tx & Rx pins 322 | byte valD = value & bitmask; 323 | byte maskD = ~bitmask; 324 | cli(); 325 | PORTD = (PORTD & maskD) | valD; 326 | sei(); 327 | } else if (port == 1) { 328 | byte valB = (value & bitmask) & 0x3F; 329 | byte valC = (value & bitmask) >> 6; 330 | byte maskB = ~(bitmask & 0x3F); 331 | byte maskC = ~((bitmask & 0xC0) >> 6); 332 | cli(); 333 | PORTB = (PORTB & maskB) | valB; 334 | PORTC = (PORTC & maskC) | valC; 335 | sei(); 336 | } else if (port == 2) { 337 | bitmask = bitmask & 0x0F; 338 | byte valC = (value & bitmask) << 2; 339 | byte maskC = ~(bitmask << 2); 340 | cli(); 341 | PORTC = (PORTC & maskC) | valC; 342 | sei(); 343 | } 344 | #else 345 | byte pin=port*8; 346 | if ((bitmask & 0x01)) digitalWrite(PIN_TO_DIGITAL(pin+0), (value & 0x01)); 347 | if ((bitmask & 0x02)) digitalWrite(PIN_TO_DIGITAL(pin+1), (value & 0x02)); 348 | if ((bitmask & 0x04)) digitalWrite(PIN_TO_DIGITAL(pin+2), (value & 0x04)); 349 | if ((bitmask & 0x08)) digitalWrite(PIN_TO_DIGITAL(pin+3), (value & 0x08)); 350 | if ((bitmask & 0x10)) digitalWrite(PIN_TO_DIGITAL(pin+4), (value & 0x10)); 351 | if ((bitmask & 0x20)) digitalWrite(PIN_TO_DIGITAL(pin+5), (value & 0x20)); 352 | if ((bitmask & 0x40)) digitalWrite(PIN_TO_DIGITAL(pin+6), (value & 0x40)); 353 | if ((bitmask & 0x80)) digitalWrite(PIN_TO_DIGITAL(pin+7), (value & 0x80)); 354 | #endif 355 | } 356 | 357 | 358 | 359 | 360 | #ifndef TOTAL_PORTS 361 | #define TOTAL_PORTS ((TOTAL_PINS + 7) / 8) 362 | #endif 363 | 364 | 365 | #endif /* Firmata_Boards_h */ 366 | 367 | -------------------------------------------------------------------------------- /simplebot/arduino/SimpleBotFirmata/Firmata.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Firmata.cpp - Firmata library 3 | Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | See file LICENSE.txt for further informations on licensing terms. 11 | */ 12 | 13 | //****************************************************************************** 14 | //* Includes 15 | //****************************************************************************** 16 | 17 | #include "./Firmata.h" 18 | #include "HardwareSerial.h" 19 | 20 | extern "C" { 21 | #include 22 | #include 23 | } 24 | 25 | //****************************************************************************** 26 | //* Support Functions 27 | //****************************************************************************** 28 | 29 | void FirmataClass::sendValueAsTwo7bitBytes(int value) 30 | { 31 | FirmataSerial.write(value & B01111111); // LSB 32 | FirmataSerial.write(value >> 7 & B01111111); // MSB 33 | } 34 | 35 | void FirmataClass::startSysex(void) 36 | { 37 | FirmataSerial.write(START_SYSEX); 38 | } 39 | 40 | void FirmataClass::endSysex(void) 41 | { 42 | FirmataSerial.write(END_SYSEX); 43 | } 44 | 45 | //****************************************************************************** 46 | //* Constructors 47 | //****************************************************************************** 48 | 49 | FirmataClass::FirmataClass(Stream &s) : FirmataSerial(s) 50 | { 51 | firmwareVersionCount = 0; 52 | systemReset(); 53 | } 54 | 55 | //****************************************************************************** 56 | //* Public Methods 57 | //****************************************************************************** 58 | 59 | /* begin method for overriding default serial bitrate */ 60 | void FirmataClass::begin(void) 61 | { 62 | begin(57600); 63 | } 64 | 65 | /* begin method for overriding default serial bitrate */ 66 | void FirmataClass::begin(long speed) 67 | { 68 | Serial.begin(speed); 69 | FirmataSerial = Serial; 70 | blinkVersion(); 71 | printVersion(); 72 | printFirmwareVersion(); 73 | } 74 | 75 | void FirmataClass::begin(Stream &s) 76 | { 77 | FirmataSerial = s; 78 | systemReset(); 79 | printVersion(); 80 | printFirmwareVersion(); 81 | } 82 | 83 | // output the protocol version message to the serial port 84 | void FirmataClass::printVersion(void) { 85 | FirmataSerial.write(REPORT_VERSION); 86 | FirmataSerial.write(FIRMATA_MAJOR_VERSION); 87 | FirmataSerial.write(FIRMATA_MINOR_VERSION); 88 | } 89 | 90 | void FirmataClass::blinkVersion(void) 91 | { 92 | // flash the pin with the protocol version 93 | pinMode(VERSION_BLINK_PIN,OUTPUT); 94 | pin13strobe(FIRMATA_MAJOR_VERSION, 40, 210); 95 | delay(250); 96 | pin13strobe(FIRMATA_MINOR_VERSION, 40, 210); 97 | delay(125); 98 | } 99 | 100 | void FirmataClass::printFirmwareVersion(void) 101 | { 102 | byte i; 103 | 104 | if(firmwareVersionCount) { // make sure that the name has been set before reporting 105 | startSysex(); 106 | FirmataSerial.write(REPORT_FIRMWARE); 107 | FirmataSerial.write(firmwareVersionVector[0]); // major version number 108 | FirmataSerial.write(firmwareVersionVector[1]); // minor version number 109 | for(i=2; i 0) && (inputData < 128) ) { 197 | waitForData--; 198 | storedInputData[waitForData] = inputData; 199 | if( (waitForData==0) && executeMultiByteCommand ) { // got the whole message 200 | switch(executeMultiByteCommand) { 201 | case ANALOG_MESSAGE: 202 | if(currentAnalogCallback) { 203 | (*currentAnalogCallback)(multiByteChannel, 204 | (storedInputData[0] << 7) 205 | + storedInputData[1]); 206 | } 207 | break; 208 | case DIGITAL_MESSAGE: 209 | if(currentDigitalCallback) { 210 | (*currentDigitalCallback)(multiByteChannel, 211 | (storedInputData[0] << 7) 212 | + storedInputData[1]); 213 | } 214 | break; 215 | case SET_PIN_MODE: 216 | if(currentPinModeCallback) 217 | (*currentPinModeCallback)(storedInputData[1], storedInputData[0]); 218 | break; 219 | case REPORT_ANALOG: 220 | if(currentReportAnalogCallback) 221 | (*currentReportAnalogCallback)(multiByteChannel,storedInputData[0]); 222 | break; 223 | case REPORT_DIGITAL: 224 | if(currentReportDigitalCallback) 225 | (*currentReportDigitalCallback)(multiByteChannel,storedInputData[0]); 226 | break; 227 | } 228 | executeMultiByteCommand = 0; 229 | } 230 | } else { 231 | // remove channel info from command byte if less than 0xF0 232 | if(inputData < 0xF0) { 233 | command = inputData & 0xF0; 234 | multiByteChannel = inputData & 0x0F; 235 | } else { 236 | command = inputData; 237 | // commands in the 0xF* range don't use channel data 238 | } 239 | switch (command) { 240 | case ANALOG_MESSAGE: 241 | case DIGITAL_MESSAGE: 242 | case SET_PIN_MODE: 243 | waitForData = 2; // two data bytes needed 244 | executeMultiByteCommand = command; 245 | break; 246 | case REPORT_ANALOG: 247 | case REPORT_DIGITAL: 248 | waitForData = 1; // two data bytes needed 249 | executeMultiByteCommand = command; 250 | break; 251 | case START_SYSEX: 252 | parsingSysex = true; 253 | sysexBytesRead = 0; 254 | break; 255 | case SYSTEM_RESET: 256 | systemReset(); 257 | break; 258 | case REPORT_VERSION: 259 | Firmata.printVersion(); 260 | break; 261 | } 262 | } 263 | } 264 | 265 | //------------------------------------------------------------------------------ 266 | // Serial Send Handling 267 | 268 | // send an analog message 269 | void FirmataClass::sendAnalog(byte pin, int value) 270 | { 271 | // pin can only be 0-15, so chop higher bits 272 | FirmataSerial.write(ANALOG_MESSAGE | (pin & 0xF)); 273 | sendValueAsTwo7bitBytes(value); 274 | } 275 | 276 | // send a single digital pin in a digital message 277 | void FirmataClass::sendDigital(byte pin, int value) 278 | { 279 | /* TODO add single pin digital messages to the protocol, this needs to 280 | * track the last digital data sent so that it can be sure to change just 281 | * one bit in the packet. This is complicated by the fact that the 282 | * numbering of the pins will probably differ on Arduino, Wiring, and 283 | * other boards. The DIGITAL_MESSAGE sends 14 bits at a time, but it is 284 | * probably easier to send 8 bit ports for any board with more than 14 285 | * digital pins. 286 | */ 287 | 288 | // TODO: the digital message should not be sent on the serial port every 289 | // time sendDigital() is called. Instead, it should add it to an int 290 | // which will be sent on a schedule. If a pin changes more than once 291 | // before the digital message is sent on the serial port, it should send a 292 | // digital message for each change. 293 | 294 | // if(value == 0) 295 | // sendDigitalPortPair(); 296 | } 297 | 298 | 299 | // send 14-bits in a single digital message (protocol v1) 300 | // send an 8-bit port in a single digital message (protocol v2) 301 | void FirmataClass::sendDigitalPort(byte portNumber, int portData) 302 | { 303 | FirmataSerial.write(DIGITAL_MESSAGE | (portNumber & 0xF)); 304 | FirmataSerial.write((byte)portData % 128); // Tx bits 0-6 305 | FirmataSerial.write(portData >> 7); // Tx bits 7-13 306 | } 307 | 308 | 309 | void FirmataClass::sendSysex(byte command, byte bytec, byte* bytev) 310 | { 311 | byte i; 312 | startSysex(); 313 | FirmataSerial.write(command); 314 | for(i=0; i 33 | #include 34 | #include "./Firmata.h" 35 | 36 | // move the following defines to Firmata.h? 37 | #define I2C_WRITE B00000000 38 | #define I2C_READ B00001000 39 | #define I2C_READ_CONTINUOUSLY B00010000 40 | #define I2C_STOP_READING B00011000 41 | #define I2C_READ_WRITE_MODE_MASK B00011000 42 | #define I2C_10BIT_ADDRESS_MODE_MASK B00100000 43 | 44 | #define MAX_QUERIES 8 45 | #define MINIMUM_SAMPLING_INTERVAL 10 46 | 47 | #define REGISTER_NOT_SPECIFIED -1 48 | 49 | #define PULSE_IN 0x74 // send a pulse in command 50 | 51 | /*============================================================================== 52 | * GLOBAL VARIABLES 53 | *============================================================================*/ 54 | 55 | /* analog inputs */ 56 | int analogInputsToReport = 0; // bitwise array to store pin reporting 57 | 58 | /* digital input ports */ 59 | byte reportPINs[TOTAL_PORTS]; // 1 = report this port, 0 = silence 60 | byte previousPINs[TOTAL_PORTS]; // previous 8 bits sent 61 | 62 | /* pins configuration */ 63 | byte pinConfig[TOTAL_PINS]; // configuration of every pin 64 | byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else 65 | int pinState[TOTAL_PINS]; // any value that has been written 66 | 67 | /* timer variables */ 68 | unsigned long currentMillis; // store the current value from millis() 69 | unsigned long previousMillis; // for comparison with currentMillis 70 | int samplingInterval = 19; // how often to run the main loop (in ms) 71 | 72 | /* i2c data */ 73 | struct i2c_device_info { 74 | byte addr; 75 | byte reg; 76 | byte bytes; 77 | }; 78 | 79 | /* for i2c read continuous more */ 80 | i2c_device_info query[MAX_QUERIES]; 81 | 82 | byte i2cRxData[32]; 83 | boolean isI2CEnabled = false; 84 | signed char queryIndex = -1; 85 | unsigned int i2cReadDelayTime = 0; // default delay time between i2c read request and Wire.requestFrom() 86 | 87 | Servo servos[MAX_SERVOS]; 88 | /*============================================================================== 89 | * FUNCTIONS 90 | *============================================================================*/ 91 | 92 | void readAndReportData(byte address, int theRegister, byte numBytes) { 93 | // allow I2C requests that don't require a register read 94 | // for example, some devices using an interrupt pin to signify new data available 95 | // do not always require the register read so upon interrupt you call Wire.requestFrom() 96 | if (theRegister != REGISTER_NOT_SPECIFIED) { 97 | Wire.beginTransmission(address); 98 | #if ARDUINO >= 100 99 | Wire.write((byte)theRegister); 100 | #else 101 | Wire.send((byte)theRegister); 102 | #endif 103 | Wire.endTransmission(); 104 | delayMicroseconds(i2cReadDelayTime); // delay is necessary for some devices such as WiiNunchuck 105 | } else { 106 | theRegister = 0; // fill the register with a dummy value 107 | } 108 | 109 | Wire.requestFrom(address, numBytes); // all bytes are returned in requestFrom 110 | 111 | // check to be sure correct number of bytes were returned by slave 112 | if(numBytes == Wire.available()) { 113 | i2cRxData[0] = address; 114 | i2cRxData[1] = theRegister; 115 | for (int i = 0; i < numBytes; i++) { 116 | #if ARDUINO >= 100 117 | i2cRxData[2 + i] = Wire.read(); 118 | #else 119 | i2cRxData[2 + i] = Wire.receive(); 120 | #endif 121 | } 122 | } 123 | else { 124 | if(numBytes > Wire.available()) { 125 | Firmata.sendString("I2C Read Error: Too many bytes received"); 126 | } else { 127 | Firmata.sendString("I2C Read Error: Too few bytes received"); 128 | } 129 | } 130 | 131 | // send slave address, register and received bytes 132 | Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData); 133 | } 134 | 135 | void outputPort(byte portNumber, byte portValue, byte forceSend) 136 | { 137 | // pins not configured as INPUT are cleared to zeros 138 | portValue = portValue & portConfigInputs[portNumber]; 139 | // only send if the value is different than previously sent 140 | if(forceSend || previousPINs[portNumber] != portValue) { 141 | Firmata.sendDigitalPort(portNumber, portValue); 142 | previousPINs[portNumber] = portValue; 143 | } 144 | } 145 | 146 | /* ----------------------------------------------------------------------------- 147 | * check all the active digital inputs for change of state, then add any events 148 | * to the Serial output queue using Serial.print() */ 149 | void checkDigitalInputs(void) 150 | { 151 | /* Using non-looping code allows constants to be given to readPort(). 152 | * The compiler will apply substantial optimizations if the inputs 153 | * to readPort() are compile-time constants. */ 154 | if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false); 155 | if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false); 156 | if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false); 157 | if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false); 158 | if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false); 159 | if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false); 160 | if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false); 161 | if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false); 162 | if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false); 163 | if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false); 164 | if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false); 165 | if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false); 166 | if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false); 167 | if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false); 168 | if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false); 169 | if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false); 170 | } 171 | 172 | // ----------------------------------------------------------------------------- 173 | /* sets the pin mode to the correct state and sets the relevant bits in the 174 | * two bit-arrays that track Digital I/O and PWM status 175 | */ 176 | void setPinModeCallback(byte pin, int mode) 177 | { 178 | if (pinConfig[pin] == I2C && isI2CEnabled && mode != I2C) { 179 | // disable i2c so pins can be used for other functions 180 | // the following if statements should reconfigure the pins properly 181 | disableI2CPins(); 182 | } 183 | if (IS_PIN_SERVO(pin) && mode != SERVO && servos[PIN_TO_SERVO(pin)].attached()) { 184 | servos[PIN_TO_SERVO(pin)].detach(); 185 | } 186 | if (IS_PIN_ANALOG(pin)) { 187 | reportAnalogCallback(PIN_TO_ANALOG(pin), mode == ANALOG ? 1 : 0); // turn on/off reporting 188 | } 189 | if (IS_PIN_DIGITAL(pin)) { 190 | if (mode == INPUT) { 191 | portConfigInputs[pin/8] |= (1 << (pin & 7)); 192 | } else { 193 | portConfigInputs[pin/8] &= ~(1 << (pin & 7)); 194 | } 195 | } 196 | pinState[pin] = 0; 197 | switch(mode) { 198 | case ANALOG: 199 | if (IS_PIN_ANALOG(pin)) { 200 | if (IS_PIN_DIGITAL(pin)) { 201 | pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver 202 | digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups 203 | } 204 | pinConfig[pin] = ANALOG; 205 | } 206 | break; 207 | case INPUT: 208 | if (IS_PIN_DIGITAL(pin)) { 209 | pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver 210 | digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups 211 | pinConfig[pin] = INPUT; 212 | } 213 | break; 214 | case OUTPUT: 215 | if (IS_PIN_DIGITAL(pin)) { 216 | digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable PWM 217 | pinMode(PIN_TO_DIGITAL(pin), OUTPUT); 218 | pinConfig[pin] = OUTPUT; 219 | } 220 | break; 221 | case PWM: 222 | if (IS_PIN_PWM(pin)) { 223 | pinMode(PIN_TO_PWM(pin), OUTPUT); 224 | analogWrite(PIN_TO_PWM(pin), 0); 225 | pinConfig[pin] = PWM; 226 | } 227 | break; 228 | case SERVO: 229 | if (IS_PIN_SERVO(pin)) { 230 | pinConfig[pin] = SERVO; 231 | if (!servos[PIN_TO_SERVO(pin)].attached()) { 232 | servos[PIN_TO_SERVO(pin)].attach(PIN_TO_DIGITAL(pin)); 233 | } 234 | } 235 | break; 236 | case I2C: 237 | if (IS_PIN_I2C(pin)) { 238 | // mark the pin as i2c 239 | // the user must call I2C_CONFIG to enable I2C for a device 240 | pinConfig[pin] = I2C; 241 | } 242 | break; 243 | default: 244 | Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM 245 | } 246 | // TODO: save status to EEPROM here, if changed 247 | } 248 | 249 | void analogWriteCallback(byte pin, int value) 250 | { 251 | if (pin < TOTAL_PINS) { 252 | switch(pinConfig[pin]) { 253 | case SERVO: 254 | if (IS_PIN_SERVO(pin)) 255 | servos[PIN_TO_SERVO(pin)].write(value); 256 | pinState[pin] = value; 257 | break; 258 | case PWM: 259 | if (IS_PIN_PWM(pin)) 260 | analogWrite(PIN_TO_PWM(pin), value); 261 | pinState[pin] = value; 262 | break; 263 | } 264 | } 265 | } 266 | 267 | void digitalWriteCallback(byte port, int value) 268 | { 269 | byte pin, lastPin, mask=1, pinWriteMask=0; 270 | 271 | if (port < TOTAL_PORTS) { 272 | // create a mask of the pins on this port that are writable. 273 | lastPin = port*8+8; 274 | if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS; 275 | for (pin=port*8; pin < lastPin; pin++) { 276 | // do not disturb non-digital pins (eg, Rx & Tx) 277 | if (IS_PIN_DIGITAL(pin)) { 278 | // only write to OUTPUT and INPUT (enables pullup) 279 | // do not touch pins in PWM, ANALOG, SERVO or other modes 280 | if (pinConfig[pin] == OUTPUT || pinConfig[pin] == INPUT) { 281 | pinWriteMask |= mask; 282 | pinState[pin] = ((byte)value & mask) ? 1 : 0; 283 | } 284 | } 285 | mask = mask << 1; 286 | } 287 | writePort(port, (byte)value, pinWriteMask); 288 | } 289 | } 290 | 291 | 292 | // ----------------------------------------------------------------------------- 293 | /* sets bits in a bit array (int) to toggle the reporting of the analogIns 294 | */ 295 | //void FirmataClass::setAnalogPinReporting(byte pin, byte state) { 296 | //} 297 | void reportAnalogCallback(byte analogPin, int value) 298 | { 299 | if (analogPin < TOTAL_ANALOG_PINS) { 300 | if(value == 0) { 301 | analogInputsToReport = analogInputsToReport &~ (1 << analogPin); 302 | } else { 303 | analogInputsToReport = analogInputsToReport | (1 << analogPin); 304 | } 305 | } 306 | // TODO: save status to EEPROM here, if changed 307 | } 308 | 309 | void reportDigitalCallback(byte port, int value) 310 | { 311 | if (port < TOTAL_PORTS) { 312 | reportPINs[port] = (byte)value; 313 | } 314 | // do not disable analog reporting on these 8 pins, to allow some 315 | // pins used for digital, others analog. Instead, allow both types 316 | // of reporting to be enabled, but check if the pin is configured 317 | // as analog when sampling the analog inputs. Likewise, while 318 | // scanning digital pins, portConfigInputs will mask off values from any 319 | // pins configured as analog 320 | } 321 | 322 | /*============================================================================== 323 | * SYSEX-BASED commands 324 | *============================================================================*/ 325 | 326 | void sysexCallback(byte command, byte argc, byte *argv) 327 | { 328 | byte mode; 329 | byte slaveAddress; 330 | byte slaveRegister; 331 | byte data; 332 | unsigned int delayTime; 333 | 334 | switch(command) { 335 | case I2C_REQUEST: 336 | mode = argv[1] & I2C_READ_WRITE_MODE_MASK; 337 | if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) { 338 | Firmata.sendString("10-bit addressing mode is not yet supported"); 339 | return; 340 | } 341 | else { 342 | slaveAddress = argv[0]; 343 | } 344 | 345 | switch(mode) { 346 | case I2C_WRITE: 347 | Wire.beginTransmission(slaveAddress); 348 | for (byte i = 2; i < argc; i += 2) { 349 | data = argv[i] + (argv[i + 1] << 7); 350 | #if ARDUINO >= 100 351 | Wire.write(data); 352 | #else 353 | Wire.send(data); 354 | #endif 355 | } 356 | Wire.endTransmission(); 357 | delayMicroseconds(70); 358 | break; 359 | case I2C_READ: 360 | if (argc == 6) { 361 | // a slave register is specified 362 | slaveRegister = argv[2] + (argv[3] << 7); 363 | data = argv[4] + (argv[5] << 7); // bytes to read 364 | readAndReportData(slaveAddress, (int)slaveRegister, data); 365 | } 366 | else { 367 | // a slave register is NOT specified 368 | data = argv[2] + (argv[3] << 7); // bytes to read 369 | readAndReportData(slaveAddress, (int)REGISTER_NOT_SPECIFIED, data); 370 | } 371 | break; 372 | case I2C_READ_CONTINUOUSLY: 373 | if ((queryIndex + 1) >= MAX_QUERIES) { 374 | // too many queries, just ignore 375 | Firmata.sendString("too many queries"); 376 | break; 377 | } 378 | queryIndex++; 379 | query[queryIndex].addr = slaveAddress; 380 | query[queryIndex].reg = argv[2] + (argv[3] << 7); 381 | query[queryIndex].bytes = argv[4] + (argv[5] << 7); 382 | break; 383 | case I2C_STOP_READING: 384 | byte queryIndexToSkip; 385 | // if read continuous mode is enabled for only 1 i2c device, disable 386 | // read continuous reporting for that device 387 | if (queryIndex <= 0) { 388 | queryIndex = -1; 389 | } else { 390 | // if read continuous mode is enabled for multiple devices, 391 | // determine which device to stop reading and remove it's data from 392 | // the array, shifiting other array data to fill the space 393 | for (byte i = 0; i < queryIndex + 1; i++) { 394 | if (query[i].addr = slaveAddress) { 395 | queryIndexToSkip = i; 396 | break; 397 | } 398 | } 399 | 400 | for (byte i = queryIndexToSkip; i 0) { 418 | i2cReadDelayTime = delayTime; 419 | } 420 | 421 | if (!isI2CEnabled) { 422 | enableI2CPins(); 423 | } 424 | 425 | break; 426 | case SERVO_CONFIG: 427 | if(argc > 4) { 428 | // these vars are here for clarity, they'll optimized away by the compiler 429 | byte pin = argv[0]; 430 | int minPulse = argv[1] + (argv[2] << 7); 431 | int maxPulse = argv[3] + (argv[4] << 7); 432 | 433 | if (IS_PIN_SERVO(pin)) { 434 | if (servos[PIN_TO_SERVO(pin)].attached()) 435 | servos[PIN_TO_SERVO(pin)].detach(); 436 | servos[PIN_TO_SERVO(pin)].attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse); 437 | setPinModeCallback(pin, SERVO); 438 | } 439 | } 440 | break; 441 | case SAMPLING_INTERVAL: 442 | if (argc > 1) { 443 | samplingInterval = argv[0] + (argv[1] << 7); 444 | if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) { 445 | samplingInterval = MINIMUM_SAMPLING_INTERVAL; 446 | } 447 | } else { 448 | //Firmata.sendString("Not enough data"); 449 | } 450 | break; 451 | case EXTENDED_ANALOG: 452 | if (argc > 1) { 453 | int val = argv[1]; 454 | if (argc > 2) val |= (argv[2] << 7); 455 | if (argc > 3) val |= (argv[3] << 14); 456 | analogWriteCallback(argv[0], val); 457 | } 458 | break; 459 | case CAPABILITY_QUERY: 460 | Serial.write(START_SYSEX); 461 | Serial.write(CAPABILITY_RESPONSE); 462 | for (byte pin=0; pin < TOTAL_PINS; pin++) { 463 | if (IS_PIN_DIGITAL(pin)) { 464 | Serial.write((byte)INPUT); 465 | Serial.write(1); 466 | Serial.write((byte)OUTPUT); 467 | Serial.write(1); 468 | } 469 | if (IS_PIN_ANALOG(pin)) { 470 | Serial.write(ANALOG); 471 | Serial.write(10); 472 | } 473 | if (IS_PIN_PWM(pin)) { 474 | Serial.write(PWM); 475 | Serial.write(8); 476 | } 477 | if (IS_PIN_SERVO(pin)) { 478 | Serial.write(SERVO); 479 | Serial.write(14); 480 | } 481 | if (IS_PIN_I2C(pin)) { 482 | Serial.write(I2C); 483 | Serial.write(1); // to do: determine appropriate value 484 | } 485 | Serial.write(127); 486 | } 487 | Serial.write(END_SYSEX); 488 | break; 489 | case PIN_STATE_QUERY: 490 | if (argc > 0) { 491 | byte pin=argv[0]; 492 | Serial.write(START_SYSEX); 493 | Serial.write(PIN_STATE_RESPONSE); 494 | Serial.write(pin); 495 | if (pin < TOTAL_PINS) { 496 | Serial.write((byte)pinConfig[pin]); 497 | Serial.write((byte)pinState[pin] & 0x7F); 498 | if (pinState[pin] & 0xFF80) Serial.write((byte)(pinState[pin] >> 7) & 0x7F); 499 | if (pinState[pin] & 0xC000) Serial.write((byte)(pinState[pin] >> 14) & 0x7F); 500 | } 501 | Serial.write(END_SYSEX); 502 | } 503 | break; 504 | case ANALOG_MAPPING_QUERY: 505 | Serial.write(START_SYSEX); 506 | Serial.write(ANALOG_MAPPING_RESPONSE); 507 | for (byte pin=0; pin < TOTAL_PINS; pin++) { 508 | Serial.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127); 509 | } 510 | Serial.write(END_SYSEX); 511 | break; 512 | case PULSE_IN:{ 513 | byte pulseDurationArray[4] = { 514 | (argv[2] & 0x7F) | ((argv[3] & 0x7F) << 7) 515 | ,(argv[4] & 0x7F) | ((argv[5] & 0x7F) << 7) 516 | ,(argv[6] & 0x7F) | ((argv[7] & 0x7F) << 7) 517 | ,(argv[8] & 0x7F) | ((argv[9] & 0x7F) << 7) 518 | }; 519 | unsigned long pulseDuration = ((unsigned long)pulseDurationArray[0] << 24) 520 | + ((unsigned long)pulseDurationArray[1] << 16) 521 | + ((unsigned long)pulseDurationArray[2] << 8) 522 | + ((unsigned long)pulseDurationArray[3]); 523 | if(argv[1] == HIGH){ 524 | pinMode(argv[0],OUTPUT); 525 | digitalWrite(argv[0],LOW); 526 | delayMicroseconds(2); 527 | digitalWrite(argv[0],HIGH); 528 | delayMicroseconds(pulseDuration); 529 | digitalWrite(argv[0],LOW); 530 | } else { 531 | digitalWrite(argv[0],HIGH); 532 | delayMicroseconds(2); 533 | digitalWrite(argv[0],LOW); 534 | delayMicroseconds(pulseDuration); 535 | digitalWrite(argv[0],HIGH); 536 | } 537 | unsigned long duration; 538 | byte responseArray[5]; 539 | byte timeoutArray[4] = { 540 | (argv[10] & 0x7F) | ((argv[11] & 0x7F) << 7) 541 | ,(argv[12] & 0x7F) | ((argv[13] & 0x7F) << 7) 542 | ,(argv[14] & 0x7F) | ((argv[15] & 0x7F) << 7) 543 | ,(argv[16] & 0x7F) | ((argv[17] & 0x7F) << 7) 544 | }; 545 | unsigned long timeout = ((unsigned long)timeoutArray[0] << 24) 546 | + ((unsigned long)timeoutArray[1] << 16) 547 | + ((unsigned long)timeoutArray[2] << 8) 548 | + ((unsigned long)timeoutArray[3]); 549 | pinMode(argv[0],INPUT); 550 | duration = pulseIn(argv[0], argv[1],timeout); 551 | responseArray[0] = argv[0]; 552 | responseArray[1] = (((unsigned long)duration >> 24) & 0xFF) ; 553 | responseArray[2] = (((unsigned long)duration >> 16) & 0xFF) ; 554 | responseArray[3] = (((unsigned long)duration >> 8) & 0xFF); 555 | responseArray[4] = (((unsigned long)duration & 0xFF)); 556 | Firmata.sendSysex(PULSE_IN,5,responseArray); 557 | break; 558 | } 559 | } 560 | } 561 | 562 | void enableI2CPins() 563 | { 564 | byte i; 565 | // is there a faster way to do this? would probaby require importing 566 | // Arduino.h to get SCL and SDA pins 567 | for (i=0; i < TOTAL_PINS; i++) { 568 | if(IS_PIN_I2C(i)) { 569 | // mark pins as i2c so they are ignore in non i2c data requests 570 | setPinModeCallback(i, I2C); 571 | } 572 | } 573 | 574 | isI2CEnabled = true; 575 | 576 | // is there enough time before the first I2C request to call this here? 577 | Wire.begin(); 578 | } 579 | 580 | /* disable the i2c pins so they can be used for other functions */ 581 | void disableI2CPins() { 582 | isI2CEnabled = false; 583 | // disable read continuous mode for all devices 584 | queryIndex = -1; 585 | // uncomment the following if or when the end() method is added to Wire library 586 | // Wire.end(); 587 | } 588 | 589 | /*============================================================================== 590 | * SETUP() 591 | *============================================================================*/ 592 | 593 | void systemResetCallback() 594 | { 595 | // initialize a defalt state 596 | // TODO: option to load config from EEPROM instead of default 597 | if (isI2CEnabled) { 598 | disableI2CPins(); 599 | } 600 | for (byte i=0; i < TOTAL_PORTS; i++) { 601 | reportPINs[i] = false; // by default, reporting off 602 | portConfigInputs[i] = 0; // until activated 603 | previousPINs[i] = 0; 604 | } 605 | // pins with analog capability default to analog input 606 | // otherwise, pins default to digital output 607 | for (byte i=0; i < TOTAL_PINS; i++) { 608 | if (IS_PIN_ANALOG(i)) { 609 | // turns off pullup, configures everything 610 | setPinModeCallback(i, ANALOG); 611 | } else { 612 | // sets the output to 0, configures portConfigInputs 613 | setPinModeCallback(i, OUTPUT); 614 | } 615 | } 616 | // by default, do not report any analog inputs 617 | analogInputsToReport = 0; 618 | 619 | /* send digital inputs to set the initial state on the host computer, 620 | * since once in the loop(), this firmware will only send on change */ 621 | /* 622 | TODO: this can never execute, since no pins default to digital input 623 | but it will be needed when/if we support EEPROM stored config 624 | for (byte i=0; i < TOTAL_PORTS; i++) { 625 | outputPort(i, readPort(i, portConfigInputs[i]), true); 626 | } 627 | */ 628 | } 629 | 630 | 631 | void setup() 632 | { 633 | 634 | while (!Serial) {}; 635 | 636 | Firmata.setFirmwareVersion(FIRMATA_MAJOR_VERSION, FIRMATA_MINOR_VERSION); 637 | 638 | Firmata.attach(ANALOG_MESSAGE, analogWriteCallback); 639 | Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback); 640 | Firmata.attach(REPORT_ANALOG, reportAnalogCallback); 641 | Firmata.attach(REPORT_DIGITAL, reportDigitalCallback); 642 | Firmata.attach(SET_PIN_MODE, setPinModeCallback); 643 | Firmata.attach(START_SYSEX, sysexCallback); 644 | Firmata.attach(SYSTEM_RESET, systemResetCallback); 645 | 646 | Firmata.begin(57600); 647 | systemResetCallback(); // reset to default config 648 | } 649 | 650 | /*============================================================================== 651 | * LOOP() 652 | *============================================================================*/ 653 | void loop() 654 | { 655 | byte pin, analogPin; 656 | 657 | /* DIGITALREAD - as fast as possible, check for changes and output them to the 658 | * FTDI buffer using Serial.print() */ 659 | checkDigitalInputs(); 660 | 661 | /* SERIALREAD - processing incoming messagse as soon as possible, while still 662 | * checking digital inputs. */ 663 | while(Firmata.available()) 664 | Firmata.processInput(); 665 | 666 | /* SEND FTDI WRITE BUFFER - make sure that the FTDI buffer doesn't go over 667 | * 60 bytes. use a timer to sending an event character every 4 ms to 668 | * trigger the buffer to dump. */ 669 | 670 | currentMillis = millis(); 671 | if (currentMillis - previousMillis > samplingInterval) { 672 | previousMillis += samplingInterval; 673 | /* ANALOGREAD - do all analogReads() at the configured sampling interval */ 674 | for(pin=0; pin -1) { 684 | for (byte i = 0; i < queryIndex + 1; i++) { 685 | readAndReportData(query[i].addr, query[i].reg, query[i].bytes); 686 | } 687 | } 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /simplebot/examples/collision-avoid.js: -------------------------------------------------------------------------------- 1 | var five = require("johnny-five"); 2 | var temporal = require("temporal"); 3 | 4 | var opts = {}; 5 | opts.port = process.argv[2] || ""; 6 | 7 | var board = new five.Board(opts); 8 | 9 | board.on("ready", function() { 10 | 11 | console.log("Bot will drive forward until it senses an obstacle."); 12 | console.log("It will then back up, change direction and go forward again"); 13 | 14 | var left_wheel = new five.Servo({ pin: 9, type: 'continuous' }); 15 | var right_wheel = new five.Servo({ pin: 8, type: 'continuous' }); 16 | var ping = new five.Ping({pin: 7}); //<1> 17 | var state = "DRIVING"; 18 | forward(); 19 | 20 | ping.on("change", function() { //<2> 21 | if (this.cm < 15 && state == "DRIVING") { 22 | state = "AVOID_COLLISION"; 23 | temporal.queue([ //<3> 24 | { 25 | // stop dead 26 | delay: 10, 27 | task: stop, 28 | }, 29 | { 30 | // back up 31 | delay: 3000, 32 | task: backward, 33 | }, 34 | { 35 | // stop 36 | delay: 1500, 37 | task: stop, 38 | }, 39 | { 40 | // spin 41 | delay: 3000, 42 | task: spin, 43 | }, 44 | { 45 | // stop 46 | delay: 1000, 47 | task: stop, 48 | }, 49 | { 50 | // fwd 51 | delay: 3000, 52 | task: forward, //<4> 53 | }, 54 | ]) 55 | } 56 | }); 57 | 58 | // helper functions for driving. 59 | function forward() { 60 | console.log("forward"); 61 | state = "DRIVING"; 62 | left_wheel.cw(); 63 | right_wheel.ccw(); 64 | } 65 | 66 | function backward() { 67 | console.log("back"); 68 | left_wheel.ccw(); 69 | right_wheel.cw(); 70 | } 71 | 72 | function stop() { 73 | console.log("stop"); 74 | left_wheel.to(92); 75 | right_wheel.to(94); 76 | } 77 | 78 | function spin() { 79 | console.log("spin"); 80 | left_wheel.cw(); 81 | right_wheel.cw(); 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /simplebot/examples/servo-test.js: -------------------------------------------------------------------------------- 1 | // Tests whether your servos are going the right way and all working 2 | // properly or not. Assumes that the left servo is on pin 9 and the right 3 | // wheel is on pin 8. 4 | // 5 | // call as: 6 | // node examples/test_servos.js PORT 7 | 8 | var five = require("johnny-five"); 9 | var temporal = require("temporal"); 10 | 11 | var opts = {}; 12 | opts.port = process.argv[2] || ""; 13 | 14 | var board = new five.Board(opts); 15 | 16 | board.on("ready", function() { 17 | 18 | console.log("This is a servo tester to see if everything is running properly"); 19 | console.log("Servos should run forward for 3s, stop for 3s, reverse 3s and stop"); 20 | 21 | var left_wheel = new five.Servo({ pin: 9, type: 'continuous' }); 22 | var right_wheel = new five.Servo({ pin: 8, type: 'continuous' }); 23 | left_wheel.stop(); 24 | right_wheel.stop(); 25 | 26 | temporal.queue([ 27 | { 28 | delay: 5000, 29 | task: function() { 30 | console.log("going forward"); 31 | left_wheel.cw(); 32 | right_wheel.ccw(); 33 | }, 34 | }, 35 | { 36 | delay: 3000, 37 | task: function() { 38 | console.log("stopping"); 39 | left_wheel.stop(); 40 | right_wheel.stop(); 41 | }, 42 | }, 43 | { 44 | delay: 3000, 45 | task: function() { 46 | console.log("going backward"); 47 | left_wheel.ccw(); 48 | right_wheel.cw(); 49 | }, 50 | }, 51 | { 52 | delay: 3000, 53 | task: function() { 54 | console.log("stopping"); 55 | left_wheel.stop(); 56 | right_wheel.stop(); 57 | }, 58 | }, 59 | { 60 | delay: 1500, 61 | task: function() { 62 | console.log("Test complete. Exiting."); 63 | process.exit(); 64 | }, 65 | }, 66 | ]); 67 | 68 | }); 69 | 70 | 71 | -------------------------------------------------------------------------------- /simplebot/examples/simplebot.js: -------------------------------------------------------------------------------- 1 | // ======================= 2 | // Derived from the work done by @makenai on the SumoBot Jr 3 | // ======================= 4 | 5 | var five = require("johnny-five"); 6 | var keypress = require('keypress'); 7 | 8 | var opts = {}; 9 | opts.port = process.argv[2] || ""; 10 | 11 | keypress(process.stdin); 12 | 13 | var board = new five.Board(opts); 14 | 15 | board.on("ready", function() { 16 | 17 | console.log("Control the bot with the arrow keys, SPACE to stop, Q to exit.") 18 | 19 | var left_wheel = new five.Servo({ pin: 9, type: 'continuous' }); 20 | var right_wheel = new five.Servo({ pin: 8, type: 'continuous' }); 21 | left_wheel.stop(); 22 | right_wheel.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 ) return; 31 | 32 | if ( key.name == 'q' ) { 33 | console.log('Quitting'); 34 | process.exit(); 35 | } else if ( key.name == 'up' ) { 36 | 37 | console.log('Forward'); 38 | left_wheel.cw(); 39 | right_wheel.ccw(); 40 | 41 | } else if ( key.name == 'down' ) { 42 | 43 | console.log('Backward'); 44 | left_wheel.ccw(); 45 | right_wheel.cw(); 46 | 47 | } else if ( key.name == 'left' ) { 48 | 49 | console.log('Left'); 50 | left_wheel.ccw(); 51 | right_wheel.ccw(); 52 | 53 | } else if ( key.name == 'right' ) { 54 | 55 | console.log('Right'); 56 | left_wheel.cw(); 57 | right_wheel.cw(); 58 | 59 | } else if ( key.name == 'space' ) { 60 | 61 | console.log('Stopping'); 62 | left_wheel.to(90); 63 | right_wheel.to(90); 64 | } 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /simplebot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplebot", 3 | "version": "0.0.1", 4 | "description": "SimpleBot files and examples for NodeBots book", 5 | "main": "examples/simplebot.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rwaldron/the-book" 15 | }, 16 | "keywords": [ 17 | "nodebots", 18 | "johnny-five", 19 | "simplebot", 20 | "robot", 21 | "robotics", 22 | "arduino", 23 | "firmata" 24 | ], 25 | "author": "Andrew Fisher ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/rwaldron/the-book/issues" 29 | }, 30 | "homepage": "https://github.com/rwaldron/the-book", 31 | "dependencies": { 32 | "johnny-five": "^0.8.11", 33 | "keypress": "^0.2.1", 34 | "temporal": "^0.3.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /simplebot/physical/simplebot_basic.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/simplebot/physical/simplebot_basic.fzz -------------------------------------------------------------------------------- /simplebot/physical/simplebot_sonic.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/simplebot/physical/simplebot_sonic.fzz -------------------------------------------------------------------------------- /simplebot/physical/simplebot_wireless.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/simplebot/physical/simplebot_wireless.fzz -------------------------------------------------------------------------------- /simplebot/physical/skid.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwaldron/javascript-robotics/ee15373f55f49e7d0f4c2a38eee5ce3ea280bdae/simplebot/physical/skid.stl -------------------------------------------------------------------------------- /typebot/README.md: -------------------------------------------------------------------------------- 1 | TypeBot 2 | ======= 3 | 4 | TypeBot is a NodeBot that can type on a keyboard. This folder contains the source code for TypeBot and two helper applications. To run these examples, first install the dependencies: 5 | 6 | ``` 7 | npm install 8 | ``` 9 | 10 | ### TypeBot 11 | 12 | To run TypeBot, connect your arm's Arduino and run: 13 | 14 | ``` 15 | node typebot.js 16 | ```` 17 | 18 | For more details on TypeBot, be sure to read the make book! 19 | 20 | ### Aligning Servos 21 | 22 | It is often necessary when working on the arm to remove and re-attach segments. Doing so requires that the arm segment be re-attached at the appropriate angle, however. The align servos helper app will set all servos to 90 degrees. In practice, it's pretty easy to eyeball a 90 degree angle, so this makes it easy to re-assemble the arm. To the align servos app, connect the arm's Arduino and run: 23 | 24 | ``` 25 | node align_servos.js 26 | ``` 27 | 28 | ### Generating Servo Angle Estimates 29 | 30 | One of the trickiest parts of creating TypeBot is coming up with the initial estimate of servo angles. This app will generate estimates for all of the servos for each key. It uses a set of parameters about the keyboard and uses some basic Trig formulas to calculate an estimate of the angles. To generate the angle estimates, first edit the constants defined at the top of "generate_angle_estimates.js" if need be. Then run: 31 | 32 | ``` 33 | node generate_angle_estimates.js 34 | ``` -------------------------------------------------------------------------------- /typebot/align_servos.js: -------------------------------------------------------------------------------- 1 | var five = require('johnny-five'); 2 | var board = new five.Board(); 3 | 4 | board.on('ready', function() { 5 | var shoulder = new five.Servo(3); 6 | shoulder.to(90); 7 | 8 | var elbow = new five.Servo(6); 9 | elbow.to(90); 10 | 11 | var wrist = new five.Servo(5); 12 | wrist.to(90); 13 | 14 | setTimeout(function() { 15 | process.exit(); 16 | }, 1000); 17 | }); 18 | -------------------------------------------------------------------------------- /typebot/generate_angle_estimates.js: -------------------------------------------------------------------------------- 1 | // Modify the below constants to match your keyboard and arm 2 | 3 | // The wrist and finger length are the distance between the servo arm center 4 | // and the opposite side's servo axis. 5 | var WRIST_LENGTH = 5.5; 6 | var FINGER_LENGTH = 5.5; 7 | 8 | // The y and z offset is the offset from the shoulder servo's axis and the 9 | // "B" key on the keyboard 10 | var Y_OFFSET = 4.5; 11 | var Z_OFFSET = -3.5; 12 | 13 | // The x and y key distance are the distances between keys on the keyboard 14 | var X_KEY_DISTANCE = 0.743; 15 | var Y_KEY_DISTANCE = 0.75; 16 | 17 | // The row offsets are the x axis offsets between each row and the previous row. 18 | // In other words, the second row offset is the x distance between the center of 19 | // the "B" key and the center of the "G" key. The third row offset is the x distance 20 | // between the "G" and "T" keys. 21 | var SECOND_ROW_OFFSET = -1.1875; 22 | var THIRD_ROW_OFFSET = -0.375; 23 | 24 | // Only edit this object if you are not using a standard US keyboard. 25 | // The x/y values are relative to the "B" key on a standard US keyboard. Units 26 | // are in "keys". For example, the "V" key is one key to the left of "B", so 27 | // its x value is -1, and it's y value is 0. The "G" and "T" keys are considered 28 | // to have an x value of 0, even though they are slightly offset from the "B" key. 29 | var KEYS = { 30 | a: { 31 | x: -4, 32 | y: 1 33 | }, 34 | b: { 35 | x: 0, 36 | y: 0 37 | }, 38 | c: { 39 | x: -2, 40 | y: 0 41 | }, 42 | d: { 43 | x: -2, 44 | y: 1 45 | }, 46 | e: { 47 | x: -2, 48 | y: 2 49 | }, 50 | f: { 51 | x: -1, 52 | y: 1 53 | }, 54 | g: { 55 | x: 0, 56 | y: 1 57 | }, 58 | h: { 59 | x: 1, 60 | y: 1 61 | }, 62 | i: { 63 | x: 3, 64 | y: 2 65 | }, 66 | j: { 67 | x: 2, 68 | y: 1 69 | }, 70 | k: { 71 | x: 3, 72 | y: 1 73 | }, 74 | l: { 75 | x: 4, 76 | y: 1 77 | }, 78 | m: { 79 | x: 2, 80 | y: 0 81 | }, 82 | n: { 83 | x: 1, 84 | y: 0 85 | }, 86 | o: { 87 | x: 4, 88 | y: 2 89 | }, 90 | p: { 91 | x: 5, 92 | y: 2 93 | }, 94 | q: { 95 | x: -4, 96 | y: 2 97 | }, 98 | r: { 99 | x: -1, 100 | y: 2 101 | }, 102 | s: { 103 | x: -3, 104 | y: 1 105 | }, 106 | t: { 107 | x: 0, 108 | y: 2 109 | }, 110 | u: { 111 | x: 2, 112 | y: 2 113 | }, 114 | v: { 115 | x: -1, 116 | y: 0 117 | }, 118 | w: { 119 | x: -3, 120 | y: 2 121 | }, 122 | x: { 123 | x: -3, 124 | y: 0 125 | }, 126 | y: { 127 | x: 1, 128 | y: 2 129 | }, 130 | z: { 131 | x: -4, 132 | y: 0 133 | } 134 | }; 135 | 136 | function calculateAngles(x, y, z) { 137 | var cos = Math.cos; 138 | var sin = Math.sin; 139 | var sqrt = Math.sqrt; 140 | var arctan = Math.atan; 141 | function radians(degrees) { 142 | return degrees * Math.PI / 180; 143 | } 144 | var theta_s = arctan(y / Math.abs(x)); 145 | theta_s = theta_s * 180 / Math.PI; 146 | if (x < 0) { 147 | theta_s = 180 - theta_s; 148 | } 149 | theta_s = Math.round(theta_s); 150 | var yp = sqrt(x * x + y * y); 151 | var theta_e; 152 | var theta_w; 153 | var diff = Infinity; 154 | for(var theta_elbow = 0; theta_elbow < 180; theta_elbow++) { 155 | for(var theta_wrist = 0; theta_wrist < 180; theta_wrist++) { 156 | var yhat = WRIST_LENGTH * cos(radians(theta_elbow)) - FINGER_LENGTH * cos(radians(theta_elbow) + radians(theta_wrist)); 157 | var zhat = WRIST_LENGTH * sin(radians(theta_elbow)) - FINGER_LENGTH * sin(radians(theta_elbow) + radians(theta_wrist)); 158 | var diffhat = Math.abs(yp - yhat) + Math.abs(z - zhat); 159 | if (diffhat < diff) { 160 | diff = diffhat; 161 | theta_e = theta_elbow; 162 | theta_w = theta_wrist; 163 | } 164 | } 165 | } 166 | return { 167 | shoulder: theta_s, 168 | elbow: theta_e, 169 | wrist: theta_w 170 | }; 171 | } 172 | 173 | function calculatePosition(key) { 174 | var x = key.x; 175 | var y = key.y; 176 | x = x * X_KEY_DISTANCE; 177 | if (y > 0) { 178 | x += SECOND_ROW_OFFSET; 179 | } 180 | if (y > 1) { 181 | x += THIRD_ROW_OFFSET; 182 | } 183 | return calculateAngles(x, y * Y_KEY_DISTANCE + Y_OFFSET, Z_OFFSET); 184 | } 185 | 186 | var results = {}; 187 | for (var key in KEYS) { 188 | results[key] = calculatePosition(KEYS[key]); 189 | } 190 | console.log(results); 191 | -------------------------------------------------------------------------------- /typebot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TypeBot", 3 | "version": "1.0.1", 4 | "description": "A typing robot", 5 | "dependencies": { 6 | "johnny-five": "^0.8.14" 7 | }, 8 | "author": "Bryan Hughes " 9 | } 10 | -------------------------------------------------------------------------------- /typebot/servocontrol.js: -------------------------------------------------------------------------------- 1 | var five = require('johnny-five'); 2 | var positions = {}; 3 | var servos = {}; 4 | var opts; 5 | 6 | // Initializes the servo control module 7 | function init(board, options, callback) { 8 | 9 | // Store the options for use with move() 10 | opts = options; 11 | 12 | // Initialize the servos 13 | for (var servo in options.servos) { 14 | 15 | // Alias the servo config for easy access 16 | var servoConfig = options.servos[servo]; 17 | 18 | // Store the start position as the current position 19 | positions[servo] = servoConfig.startPosition; 20 | 21 | // Create the servo instance 22 | servos[servo] = new five.Servo({ 23 | pin: servoConfig.pin, 24 | isInverted: servoConfig.isInverted 25 | }); 26 | 27 | // Move to the servo to the starting position 28 | servos[servo].to(positions[servo]); 29 | } 30 | 31 | // Wait for the servos to move to their starting positions 32 | setTimeout(callback, 1000); 33 | } 34 | 35 | // Move the servos 36 | function move(destinations, callback) { 37 | 38 | // Find the largest servo angle change 39 | var largestChange = 0; 40 | for (var servo in destinations) { 41 | var delta = Math.abs(destinations[servo] - positions[servo]); 42 | if (delta > largestChange) { 43 | largestChange = delta; 44 | } 45 | } 46 | 47 | // If none of the servos need to move, short-circuit here 48 | if (largestChange === 0) { 49 | 50 | // We still need to call the callback, but we want the callback to always be 51 | // asynchronous, so we use process.nextTick to call it asynchronously. 52 | // For more information on why this is a good thing, read: 53 | // http://nodejs.org/api/process.html#process_process_nexttick_callback 54 | process.nextTick(callback); 55 | return; 56 | } 57 | 58 | // Calculate how long we should take to move, based on the largest change 59 | // in angle. This means that only this one servo will move at full speed. 60 | // All of the other servos will move at a slower rate so that all servos 61 | // inish at the same time. 62 | var duration = largestChange / opts.rate; 63 | 64 | // Move the servos to their destinations 65 | for (servo in destinations) { 66 | positions[servo] = destinations[servo]; 67 | servos[servo].to(destinations[servo], duration); 68 | } 69 | 70 | // Wait until we are done and call the callback 71 | setTimeout(callback, duration + opts.settleTime); 72 | } 73 | 74 | // Export the methods so that typebot.js can use them 75 | module.exports = { 76 | init: init, 77 | move: move 78 | }; 79 | -------------------------------------------------------------------------------- /typebot/typebot.js: -------------------------------------------------------------------------------- 1 | var five = require('johnny-five'); 2 | var servocontrol = require('./servocontrol'); 3 | 4 | // This is the sequence of keys that are pressed. Each element in the array 5 | // needs to correspond with an entry in the KEYS object. 6 | var SEQUENCE = ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']; 7 | 8 | // This is the rate that the servos should rotate at, measured in degrees per 9 | // millisecond. 0.05 is a good value to start with. 10 | var SERVO_RATE = 0.05; 11 | 12 | // This is the delay between movement steps. A delay gives everythign a chance 13 | // to settle and helps prevent overdriving the arm. 14 | var STEP_SETTLE_TIME = 250; 15 | 16 | // This is the basic servo config. Each key-value pair names and defines a 17 | // servo. The port is the pin number on the Arduino header, and the 18 | // defaultPosition is the position that the servo will start at when the app 19 | // boots up. 20 | var SERVO_CONFIG = { 21 | servos: { 22 | shoulder: { 23 | pin: 3, 24 | startPosition: 90, 25 | }, 26 | elbow: { 27 | pin: 6, 28 | startPosition: 60, 29 | }, 30 | wrist: { 31 | pin: 5, 32 | startPosition: 30, 33 | invert: true 34 | } 35 | }, 36 | rate: SERVO_RATE, 37 | settleTime: STEP_SETTLE_TIME 38 | }; 39 | 40 | // These are the keys on the keyboard, and the position of each 41 | // servo when they are *pressing* the key 42 | var KEYS = { 43 | a: { shoulder: 125, elbow: 19, wrist: 87 }, 44 | b: { shoulder: 88, elbow: 21, wrist: 62 }, 45 | c: { shoulder: 105, elbow: 21, wrist: 65 }, 46 | d: { shoulder: 114, elbow: 21, wrist: 77 }, 47 | e: { shoulder: 114, elbow: 19, wrist: 87 }, 48 | f: { shoulder: 107, elbow: 21, wrist: 74 }, 49 | g: { shoulder: 100, elbow: 21, wrist: 72 }, 50 | h: { shoulder: 92, elbow: 21, wrist: 70 }, 51 | i: { shoulder: 81, elbow: 20, wrist: 79 }, 52 | j: { shoulder: 84, elbow: 21, wrist: 70 }, 53 | k: { shoulder: 77, elbow: 21, wrist: 71 }, 54 | l: { shoulder: 69, elbow: 21, wrist: 73 }, 55 | m: { shoulder: 70, elbow: 21, wrist: 65 }, 56 | n: { shoulder: 78, elbow: 21, wrist: 63 }, 57 | o: { shoulder: 75, elbow: 20, wrist: 81 }, 58 | p: { shoulder: 68, elbow: 20, wrist: 83 }, 59 | q: { shoulder: 124, elbow: 16, wrist: 98 }, 60 | r: { shoulder: 108, elbow: 20, wrist: 83 }, 61 | s: { shoulder: 120, elbow: 20, wrist: 82 }, 62 | t: { shoulder: 102, elbow: 20, wrist: 81 }, 63 | u: { shoulder: 88, elbow: 21, wrist: 78 }, 64 | v: { shoulder: 97, elbow: 21, wrist: 63 }, 65 | w: { shoulder: 119, elbow: 18, wrist: 92 }, 66 | x: { shoulder: 113, elbow: 21, wrist: 68 }, 67 | y: { shoulder: 95, elbow: 20, wrist: 79 }, 68 | z: { shoulder: 120, elbow: 21, wrist: 72 } 69 | }; 70 | 71 | function run() { 72 | 73 | // Define the states 74 | var STATE_IDLE = 0; 75 | var STATE_MOVING = 1; 76 | var STATE_PRESSING = 2; 77 | var STATE_RELEASING = 3; 78 | 79 | // State machine information 80 | var sequencePosition = -1; 81 | var state = STATE_IDLE; 82 | var key; 83 | 84 | function tick() { 85 | switch(state) { 86 | 87 | case STATE_IDLE: 88 | 89 | // Get the next key 90 | sequencePosition++; 91 | key = KEYS[SEQUENCE[sequencePosition]]; 92 | if (!key) { 93 | process.exit(); 94 | } 95 | console.log('Typing key ' + SEQUENCE[sequencePosition]); 96 | 97 | // Move the arm to resting above the key 98 | state = STATE_MOVING; 99 | servocontrol.move({ 100 | 'shoulder': key.shoulder, 101 | 'elbow': key.elbow + 10, 102 | 'wrist': key.wrist - 5 103 | }, tick); 104 | break; 105 | 106 | // Press the key 107 | case STATE_MOVING: 108 | state = STATE_PRESSING; 109 | servocontrol.move({ 110 | 'elbow': key.elbow, 111 | 'wrist': key.wrist 112 | }, tick); 113 | break; 114 | 115 | // Release the key 116 | case STATE_PRESSING: 117 | state = STATE_RELEASING; 118 | servocontrol.move({ 119 | elbow: key.elbow + 10, 120 | wrist: key.wrist - 5 121 | }, tick); 122 | break; 123 | 124 | // Change to the idle state and pump the event loop 125 | case STATE_RELEASING: 126 | state = STATE_IDLE; 127 | tick(); 128 | break; 129 | } 130 | } 131 | 132 | // Kickstart the event loop 133 | tick(); 134 | } 135 | 136 | // Initialize the hardware 137 | var board = new five.Board(); 138 | board.on('ready', function() { 139 | servocontrol.init(board, SERVO_CONFIG, run); 140 | }); 141 | --------------------------------------------------------------------------------