diff --git a/.gitignore b/.gitignore index b512c09..9daa824 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +.DS_Store +node_modules diff --git a/README.md b/README.md index f3d84d2..b0a7ec6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,16 @@ The way this works is simple (in theory, not in practice). The Arduino listens f ##board -Right now, the board library will attempt to autodiscover the Arduino. I'm going to make it configurable, don't worry. +````javascript +var board = new arduino.Board({ + device: "ACM" +}); +```` +The board library will attempt to autodiscover the Arduino. +The `device` option can be used to set a regex filter that will help the library when scanning for matching devices. +**Note**: the value of this parameter will be used as argument of the grep command + +If this parameter is not provided the board library will attempt to autodiscover the Arduino by quering every device containing 'usb' in its name. ````javascript var board = new arduino.Board({ @@ -119,6 +128,67 @@ Fade the to full brightness then back to minimal brightness in `interval` ms. De Current brightness of the LED +##lcd + +This is a port of the [LiquidCrystal library](http://arduino.cc/en/Reference/LiquidCrystal) into JavaScript. Note that communicating with the LCD requires use of the synchronous `board.delay()` busy loop which will block other node.js events from being processed for several milliseconds at a time. (This could be converted to pause a board-level buffered message queue instead.) + +````javascript +var lcd = new d.LCD({ + board: board, + pins: {rs:12, rw:11, e:10, data:[5, 4, 3, 2]} +}); +lcd.begin(16, 2); +lcd.print("Hello Internet."); +```` + +In `options`, the "pins" field can either be an array matching a call to any of the [LiquidCrystal constructors](http://arduino.cc/en/Reference/LiquidCrystalConstructor) or an object with "rs", "rw" (optional), "e" and a 4- or 8-long array of "data" pins. Pins will default to `[12, 11, 5, 4, 3, 2]` if not provided. + +###lcd.begin(), lcd.clear(), lcd.home(), lcd.setCursor(), lcd.scrollDisplayLeft(), lcd.scrollDisplayRight() + +These should behave the same as their counterparts in the [LiquidCrystal library](http://arduino.cc/en/Reference/LiquidCrystal). + +###lcd.display(on), lcd.cursor(on), lcd.blink(on), lcd.autoscroll(on) + +These are similar to the methods in the [LiquidCrystal library](http://arduino.cc/en/Reference/LiquidCrystal), however they can take an optional boolean parameter. If true or not provided, the setting is enabled. If false, the setting is disabled. For compatibility `.noDisplay()`, `.noCursor()`, `.noBlink()` and `.noAutoscroll()` methods are provided as well. + +###lcd.write(val), lcd.print(val) + +These take a buffer, string or integer and send it to the display. The `.write` and `print` methods are equivalent, aliases to the same function. + +###lcd.createChar(location, charmap) + +Configures a custom character for code `location` (numbers 0–7). `charmap` can be a 40-byte buffer as in [the C++ method](http://arduino.cc/en/Reference/LiquidCrystalCreateChar), or an array of 5-bit binary strings, or a 40-character string with pixels denoted by any non-space (`' '`) character. These bits determine the 5x8 pixel pattern of the custom character. + +````javascript +var square = new Buffer("1f1f1f1f1f1f1f1f", 'hex'); + +var smiley = [ + '00000', + '10001', + '00000', + '00000', + '10001', + '01110', + '00000' +]; + +var random = + ". .." + + " . . " + + ". . ." + + " . . " + + " .. " + + ". . " + + " . ." + + ".. .." ; + +lcd.createChar(0, square); +lcd.createChar(1, smiley); +lcd.createChar(2, random); +lcd.setCursor(5,2); +lcd.print(new Buffer("\0\1\2\1\0")); // NOTE: when `.print`ing a string, 'ascii' turns \0 into a space +```` + ##piezo ````javascript @@ -164,6 +234,20 @@ setInterval(function(){ }, 1000); ```` +##ping + +See: + +````javascript +var range = new arduino.Ping({ + board: board +}); + +range.on('read', function () { + console.log("Distance to target (cm)", range.centimeters); +}); +```` + ##servo ````javascript @@ -213,6 +297,8 @@ What is implemented right now: * `02` digitalRead * `03` analogWrite * `04` analogRead +* `97` ping +* `98` servo * `99` debug ##pin diff --git a/examples/adjustable-baud.js b/examples/adjustable-baud.js new file mode 100644 index 0000000..f897045 --- /dev/null +++ b/examples/adjustable-baud.js @@ -0,0 +1,15 @@ +var arduino = require('../'); + +var board = new arduino.Board({ + debug: true, + baudrate: 9600 +}); + +var led = new arduino.Led({ + board: board, + pin: 9 +}); + +board.on('ready', function(){ + led.blink(); +}); diff --git a/examples/adjustable-port.js b/examples/adjustable-port.js new file mode 100644 index 0000000..3815493 --- /dev/null +++ b/examples/adjustable-port.js @@ -0,0 +1,15 @@ +var arduino = require('../'); + +var board = new arduino.Board({ + debug: true, + device: "ACM" +}); + +var led = new arduino.Led({ + board: board, + pin: "13" +}); + +board.on('ready', function(){ + led.blink(); +}); \ No newline at end of file diff --git a/examples/piezo.js b/examples/piezo.js index 67ab39a..dd59278 100644 --- a/examples/piezo.js +++ b/examples/piezo.js @@ -15,3 +15,7 @@ board.on('ready', function(){ piezo.note('b', 1100); }, 1000); }); + + +// Resources +// http://arduino.cc/en/Tutorial/Tone4 diff --git a/examples/ping.js b/examples/ping.js new file mode 100644 index 0000000..c50499c --- /dev/null +++ b/examples/ping.js @@ -0,0 +1,31 @@ +var arduino = require('../'), + board, ping; + +board = new arduino.Board({ + debug: false +}); + +ping = new arduino.Ping({ + board: board, + pin: 7 +}); + +// 'read' events fire approx ~50ms +ping.on('read', function(err) { + + // Current sensor data stored in properties + // of this Ping instance: + // + // this.microseconds - time lapse from fire to read + // this.inches - calculated distance to object in inches + // this.centimeters - calculated distance to object in centimeters + + console.log('Object is ~' + Math.round(this.inches) + ' inches from sensor'); + +}); + +// To test, use the following: +// http://arduino.cc/en/uploads/Tutorial/ping_bb.png +// +// More information: +// http://arduino.cc/en/Tutorial/Ping diff --git a/examples/pir.js b/examples/pir.js new file mode 100644 index 0000000..fd3e788 --- /dev/null +++ b/examples/pir.js @@ -0,0 +1,52 @@ +var arduino = require('../'), + board, pir; + +board = new arduino.Board({ + debug: false +}); + +pir = new arduino.PIR({ + board: board, + pin: 7 +}); + +// 'calibrated' event fired when PIR sensor is +// ready to detect movement/motion in observable range +// +// All events receive error and date arguments +pir.on('calibrated', function(err, date) { + + console.log('calibrated'); + + // Current sensor data stored in properties + // of this PIR instance: + // + // this.state - current state of motion + // 0 No motion currently detected + // 1 Motion currently detected + + // 'motionstart' event fired when motion occurs + // within the observable range of the PIR sensor + this.on('motionstart', function(err, date) { + + console.log('motionstart', this.state); + console.log( date ); + + }); + + // 'motionend' event fired when motion has ceased + // within the observable range of the PIR sensor + this.on('motionend', function(err, date) { + + console.log('motionend', this.state); + + }); +}); + + +// To test, use the following: +// http://www.ladyada.net/images/sensors/pirardbb.gif +// http://bildr.org/blog/wp-content/uploads/2011/06/PIR-Arduino_hookup.png +// +// More information: +// http://www.ladyada.net/learn/sensors/pir.html diff --git a/examples/sensor-throttled.js b/examples/sensor-throttled.js new file mode 100644 index 0000000..1e4ed85 --- /dev/null +++ b/examples/sensor-throttled.js @@ -0,0 +1,36 @@ +var arduino = require('../'), + board, sensor, piezo; + +board = new arduino.Board({ + debug: false +}); + +sensor = new arduino.Sensor({ + board: board, + pin: 'A0', + throttle: 100 +}); + +piezo = new arduino.Piezo({ + board: board, + pin: 11 +}); + +sensor.on('read', function(err, value) { + value = +value; + + // |value| is the raw sensor output + console.log( value ); + + if ( value > 0 ) { + piezo.note('b', 100); + } +}); + +// Tested with: +// SoftPot +// http://www.spectrasymbol.com/how-it-works-softpot +// http://www.sparkfun.com/datasheets/Sensors/Flex/SoftPot-Datasheet.pdf +// +// sensor +// http://www.ladyada.net/learn/sensors/cds.html diff --git a/examples/sensor.js b/examples/sensor.js new file mode 100644 index 0000000..b1a105a --- /dev/null +++ b/examples/sensor.js @@ -0,0 +1,25 @@ +var arduino = require('../'), + board, sensor; + +board = new arduino.Board({ + debug: true +}); + +sensor = new arduino.Sensor({ + board: board, + pin: 'A0' +}); + +sensor.on('read', function(err, value) { + value = +value; + // |value| is the raw sensor output + console.log( value ); +}); + +// Tested with: +// SoftPot +// http://www.spectrasymbol.com/how-it-works-softpot +// http://www.sparkfun.com/datasheets/Sensors/Flex/SoftPot-Datasheet.pdf +// +// sensor +// http://www.ladyada.net/learn/sensors/cds.html diff --git a/examples/servo.js b/examples/servo.js index 517ae78..eb732ab 100644 --- a/examples/servo.js +++ b/examples/servo.js @@ -1,13 +1,56 @@ -var arduino = require('../'); +var arduino = require('../'), + board, led, servo; -var board = new arduino.Board({ +// Construct instances +board = new arduino.Board({ debug: true }); -var servo = new arduino.Servo({ - board: board +led = new arduino.Led({ + board: board, + pin: 13 }); -board.on('ready', function(){ - servo.sweep(); +servo = new arduino.Servo({ + board: board, + pin: 9 }); + +// Once servo is attached: +// - "read" +// - log position +// - "aftersweep" +// - blink the led +// - read the position +// - detach the servo +// - "detached" +// - log detach message +// +// - execute full sweep + +servo.on('attached', function(err) { + console.log('attached'); + + this.on('read', function(err, pos) { + console.log(pos); + }); + + this.on('detached', function(err) { + console.log('detached'); + }); + + this.on('aftersweep', function(err) { + led.blink(); + + this.read(); + this.detach(); + }); + + this.sweep(); +}); + +// To test, use the following: +// http://arduino.cc/en/uploads/Tutorial/sweep_BB.png +// +// More information: +// http://www.ladyada.net/learn/sensors/pir.html diff --git a/index.js b/index.js index 360176f..3c55f8c 100644 --- a/index.js +++ b/index.js @@ -4,5 +4,9 @@ module.exports = { Led: require('./lib/led'), Piezo: require('./lib/piezo'), Button: require('./lib/button'), - Servo: require('./lib/servo') -} + Servo: require('./lib/servo'), + Sensor: require('./lib/sensor'), + Ping: require('./lib/ping'), + PIR: require('./lib/pir'), + LCD: require('./lib/lcd') +}; diff --git a/lib/board.js b/lib/board.js index 789e71a..0901827 100644 --- a/lib/board.js +++ b/lib/board.js @@ -12,43 +12,51 @@ var events = require('events'), var Board = function (options) { this.log('info', 'initializing'); this.debug = options && options.debug || false; + this.device = options && options.device || 'usb|ttyACM*|ttyS0'; + this.baudrate = options && options.baudrate || 115200; this.writeBuffer = []; var self = this; this.detect(function (err, serial) { - if (err) throw err; - self.serial = serial; - self.emit('connected'); - - self.log('info', 'binding serial events'); - self.serial.on('data', function(data){ - self.log('receive', data.toString().red); - self.emit('data', data); - }); - - setTimeout(function(){ - self.log('info', 'board ready'); - self.sendClearingBytes(); - - if (self.debug) { - self.log('info', 'sending debug mode toggle on to board'); - self.write('99' + self.normalizePin(0) + self.normalizeVal(1)); - process.on('SIGINT', function(){ - self.log('info', 'sending debug mode toggle off to board'); - self.write('99' + self.normalizePin(0) + self.normalizeVal(0)); - delete self.serial; - setTimeout(function(){ - process.exit(); - }, 100); - }); - } - - if (self.writeBuffer.length > 0) { - self.processWriteBuffer(); - } - - self.emit('ready'); - }, 500); + if (err) { + if(self.listeners('error').length) + self.emit('error', err); + else + throw new Error(err); + }else{ + self.serial = serial; + self.emit('connected'); + + self.log('info', 'binding serial events'); + self.serial.on('data', function(data){ + self.log('receive', data.toString().red); + self.emit('data', data); + }); + + setTimeout(function(){ + self.log('info', 'board ready'); + self.sendClearingBytes(); + + if (self.debug) { + self.log('info', 'sending debug mode toggle on to board'); + self.write('99' + self.normalizePin(0) + self.normalizeVal(1)); + process.on('SIGINT', function(){ + self.log('info', 'sending debug mode toggle off to board'); + self.write('99' + self.normalizePin(0) + self.normalizeVal(0)); + delete self.serial; + setTimeout(function(){ + process.exit(); + }, 100); + }); + } + + if (self.writeBuffer.length > 0) { + self.processWriteBuffer(); + } + + self.emit('ready'); + }, 500); + } }); } @@ -62,30 +70,38 @@ util.inherits(Board, events.EventEmitter); * Loop through all USB devices and try to connect * This should really message the device and wait for a correct response */ -Board.prototype.detect = function (cb) { +Board.prototype.detect = function (callback) { this.log('info', 'attempting to find Arduino board'); var self = this; - child.exec('ls /dev | grep usb', function(err, stdout, stderr){ - var possible = stdout.slice(0, -1).split('\n'), - found = false; - for (var i in possible) { - var tempSerial, err; - try { - tempSerial = new serial.SerialPort('/dev/' + possible[i], { - baudrate: 115200, - parser: serial.parsers.readline("\n") - }); - } catch (e) { - err = e; - } - if (!err) { - found = tempSerial; - self.log('info', 'found board at ' + tempSerial.port); - break; + child.exec('ls /dev | grep -E "'+ self.device +'"', function(err, stdout, stderr){ + var usb = stdout.slice(0, -1).split('\n'), + found = false, + err = null, + possible, temp; + + while ( usb.length ) { + possible = usb.pop(); + + if (possible.slice(0, 2) !== 'cu') { + try { + temp = new serial.SerialPort('/dev/' + possible, { + baudrate: self.baudrate, + parser: serial.parsers.readline('\n') + }); + } catch (e) { + err = e; + } + if (!err) { + found = temp; + self.log('info', 'found board at ' + temp.port); + break; + } else { + err = new Error('Could not find Arduino'); + } } } - if (found) cb(null, found); - else cb(new Error('Could not find Arduino')); + + callback(err, found); }); } @@ -125,20 +141,18 @@ Board.prototype.write = function (m) { * Add a 0 to the front of a single-digit pin number */ Board.prototype.normalizePin = function (pin) { - return ("00" + pin).substr(-2); - /*pin = String(pin).split(''); - if (pin.length > 1) { - return pin.join(''); - } else { - pin.unshift('0'); - return pin.join(''); - }*/ + return this.lpad( 2, '0', pin ); } Board.prototype.normalizeVal = function(val) { - return ("000" + val).substr(-3); + return this.lpad( 3, '0', val ); } +// +Board.prototype.lpad = function(len, chr, str) { + return (Array(len + 1).join(chr || ' ') + str).substr(-len); +}; + /* * Define constants */ @@ -203,9 +217,10 @@ Board.prototype.delay = function (ms) { /* * Logger utility function */ -Board.prototype.log = function (level, message) { +Board.prototype.log = function (/*level, message*/) { + var args = [].slice.call(arguments); if (this.debug) { - console.log(String(+new Date()).grey + ' duino '.blue + level.magenta + ' ' + message); + console.log(String(+new Date()).grey + ' duino '.blue + args.shift().magenta + ' ' + args.join(', ')); } } diff --git a/lib/button.js b/lib/button.js index eb886ae..636a132 100644 --- a/lib/button.js +++ b/lib/button.js @@ -18,16 +18,19 @@ var Button = function (options) { }, 50); this.board.on('data', function (m) { m = m.slice(0, -1).split('::'); + + var err = null; + if (m.length > 1 && m[0] == self.pin) { // 0 is up // 1 is down if (m[1] == 0 && self.down) { self.down = false; - self.emit('up'); + self.emit('up', err); } if (m[1] == 1 && !self.down) { self.down = true; - self.emit('down'); + self.emit('down', err); } } }); diff --git a/lib/lcd.js b/lib/lcd.js new file mode 100644 index 0000000..d9692ce --- /dev/null +++ b/lib/lcd.js @@ -0,0 +1,254 @@ +// port of LiquidCrystal.cpp by natevw + + +// commands +LCD_CLEARDISPLAY = 0x01; +LCD_RETURNHOME = 0x02; +LCD_ENTRYMODESET = 0x04; +LCD_DISPLAYCONTROL = 0x08; +LCD_CURSORSHIFT = 0x10; +LCD_FUNCTIONSET = 0x20; +LCD_SETCGRAMADDR = 0x40; +LCD_SETDDRAMADDR = 0x80; + +// flags for display entry mode +LCD_ENTRYRIGHT = 0x00; +LCD_ENTRYLEFT = 0x02; +LCD_ENTRYSHIFTINCREMENT = 0x01; +LCD_ENTRYSHIFTDECREMENT = 0x00; + +// flags for display on/off control +LCD_DISPLAYON = 0x04; +LCD_DISPLAYOFF = 0x00; +LCD_CURSORON = 0x02; +LCD_CURSOROFF = 0x00; +LCD_BLINKON = 0x01; +LCD_BLINKOFF = 0x00; + +// flags for display/cursor shift +LCD_DISPLAYMOVE = 0x08; +LCD_CURSORMOVE = 0x00; +LCD_MOVERIGHT = 0x04; +LCD_MOVELEFT = 0x00; + +// flags for function set +LCD_8BITMODE = 0x10; +LCD_4BITMODE = 0x00; +LCD_2LINE = 0x08; +LCD_1LINE = 0x00; +LCD_5x10DOTS = 0x04; +LCD_5x8DOTS = 0x00; + + + +var LCD = function (options) { + if (!options || !options.board) throw new Error('Must supply required options'); + this.board = options.board; + + var pins = options.pins || [12, 11, 5, 4, 3, 2]; + if (!Array.isArray(pins)) + this.pins = pins; + else if (pins.length % 2) + this.pins = {rs:pins[0], rw:pins[1], e:pins[2], data:pins.slice(3)}; + else + this.pins = {rs:pins[0], e:pins[1], data:pins.slice(2)}; + if (!('rw' in this.pins)) this.pins.rw = 255; + + this.board.pinMode(this.pins.rs, 'out'); + if (this.pins.rw !== 255) { + this.board.pinMode(this.pins.rw, 'out'); + } + this.board.pinMode(this.pins.e, 'out'); + + this.begin(16, 1); +} + +LCD.prototype.begin = function (cols, lines, dotsize) { + this._numlines = lines; + + var displayfunction = 0; + displayfunction |= (lines > 1) ? LCD_2LINE : LCD_1LINE; + displayfunction |= (dotsize && lines === 1) ? LCD_5x10DOTS : LCD_5x8DOTS; + + this._delayMicroseconds(50000); + this.board.digitalWrite(this.pins.rs, this.board.LOW); + this.board.digitalWrite(this.pins.e, this.board.LOW); + if (this.pins.rw !== 255) + this.board.digitalWrite(this.pins.rw, this.board.LOW); + + // put the LCD into 4 bit or 8 bit mode + if (this.pins.data.length === 4) { + displayfunction |= LCD_4BITMODE; + this._writeNbits(4, 0x03); + this._delayMicroseconds(4500); + this._writeNbits(4, 0x03); + this._delayMicroseconds(4500); + this._writeNbits(4, 0x03); + this._delayMicroseconds(150); + this._writeNbits(4, 0x02); + } else { + displayfunction |= LCD_8BITMODE; + this.command(LCD_FUNCTIONSET | displayfunction); + this._delayMicroseconds(4500); + this.command(LCD_FUNCTIONSET | displayfunction); + this._delayMicroseconds(150); + this.command(LCD_FUNCTIONSET | displayfunction); + } + + this.command(LCD_FUNCTIONSET | displayfunction); + + this._displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; + this.display(); + + this.clear(); + + this._displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; + this.leftToRight(); +} + +LCD.prototype.clear = function () { + this.command(LCD_CLEARDISPLAY); + this._delayMicroseconds(2000); // this command takes a long time! +} + +LCD.prototype.home = function () { + this.command(LCD_RETURNHOME); + this._delayMicroseconds(2000); +} + +LCD.prototype.setCursor = function (col, row) { + if (row >= this._numlines) { + row = this._numlines - 1; + } + + var row_offsets = [0x00, 0x40, 0x14, 0x54]; + this.command(LCD_SETDDRAMADDR | (col + row_offsets[row])); +} + +LCD.prototype.display = function (on) { + on = (arguments.length) ? on : true; + if (on) this._displaycontrol |= LCD_DISPLAYON + else this._displaycontrol &= ~LCD_DISPLAYON; + this.command(LCD_DISPLAYCONTROL | this._displaycontrol); +} + +LCD.prototype.noDisplay = function () { this.display(false); } + +LCD.prototype.cursor = function (on) { + on = (arguments.length) ? on : true; + if (on) this._displaycontrol |= LCD_CURSORON + else this._displaycontrol &= ~LCD_CURSORON; + this.command(LCD_DISPLAYCONTROL | this._displaycontrol); +} + +LCD.prototype.noCursor = function () { this.cursor(false); } + +LCD.prototype.blink = function (on) { + on = (arguments.length) ? on : true; + if (on) this._displaycontrol |= LCD_BLINKON + else this._displaycontrol &= ~LCD_BLINKON; + this.command(LCD_DISPLAYCONTROL | this._displaycontrol); +} + +LCD.prototype.noBlink = function () { this.blink(false); } + +LCD.prototype.scrollDisplayLeft = function () { + this.command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); +} + +LCD.prototype.scrollDisplayRight = function () { + this.command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); +} + + +LCD.prototype.leftToRight = function () { + this._displaymode |= LCD_ENTRYLEFT; + this.command(LCD_ENTRYMODESET | this._displaymode); +} + +LCD.prototype.rightToLeft = function () { + this._displaymode &= ~LCD_ENTRYLEFT; + this.command(LCD_ENTRYMODESET | this._displaymode); +} + +LCD.prototype.autoscroll = function (on) { + on = (arguments.length) ? on : true; + if (on) this._displaymode |= LCD_ENTRYSHIFTINCREMENT + else this._displaymode &= ~LCD_ENTRYSHIFTINCREMENT; + this.command(LCD_ENTRYMODESET | this._displaymode); +} + +LCD.prototype.noAutoscroll = function () { this.autoscroll(false); } + +LCD.prototype.createChar = function (location, charmap) { + location &= 0x7; + this.command(LCD_SETCGRAMADDR | (location << 3)); + + var buffer = new Buffer(8); + if (Array.isArray(charmap)) for (var i = 0; i < 8; i++) { + buffer[i] = parseInt(charmap[i], 2); + } else if (typeof charmap === 'string') for (var i = 0; i < 8; i++) { + var byte = 0; + if (charmap[5*i + 0] !== ' ') byte |= 0x10; + if (charmap[5*i + 1] !== ' ') byte |= 0x08; + if (charmap[5*i + 2] !== ' ') byte |= 0x04; + if (charmap[5*i + 3] !== ' ') byte |= 0x02; + if (charmap[5*i + 4] !== ' ') byte |= 0x01; + buffer[i] = byte; + } else buffer = charmap; + this.write(buffer); +} + +LCD.prototype.write = LCD.prototype.print = function (str) { + // TODO: map misc Unicode chars to typical LCD extended charset? + var bytes = (typeof str === 'string') ? new Buffer(str, 'ascii') : + (typeof str === 'object') ? str : new Buffer([str]); + for (var i = 0, len = bytes.length; i < len; i++) + this.send(bytes[i], this.board.HIGH); +} + + +/* + * mid/low level stuff + */ + +LCD.prototype.command = function (value) { + this.send(value, this.board.LOW); +} + +LCD.prototype.send = function (value, mode) { + this.board.digitalWrite(this.pins.rs, mode); + if (this.pins.rw !== 255) { + this.board.digitalWrite(this.pins.rw, this.board.LOW); + } + if (this.pins.data.length === 8) { + this._writeNbits(8, value); + } else { + this._writeNbits(4, value >> 4); + this._writeNbits(4, value & 0xF); + } +} + +LCD.prototype._writeNbits = function (n, value) { + for (var i = 0; i < n; i++) { + this.board.pinMode(this.pins.data[i], 'out'); + var bit = (value >> i) & 0x01; + this.board.digitalWrite(this.pins.data[i], (bit) ? this.board.HIGH : this.board.LOW); + } + this._pulseEnable(); +} + +LCD.prototype._pulseEnable = function () { + this.board.digitalWrite(this.pins.e, this.board.LOW); + this._delayMicroseconds(1); + this.board.digitalWrite(this.pins.e, this.board.HIGH); + this._delayMicroseconds(1); // enable pulse must be >450ns + this.board.digitalWrite(this.pins.e, this.board.LOW); + this._delayMicroseconds(100); // commands need > 37us to settle +} + +LCD.prototype._delayMicroseconds = function (us) { + this.board.delay(us/1000); +} + +module.exports = LCD; \ No newline at end of file diff --git a/lib/ping.js b/lib/ping.js new file mode 100644 index 0000000..9eac71b --- /dev/null +++ b/lib/ping.js @@ -0,0 +1,71 @@ +var events = require('events'), + util = require('util'); + +/* + * Main Ping constructor + * Process options + * Tell the board to set it up + */ +var Ping = function (options) { + if (!options || !options.board) throw new Error('Must supply required options to Ping'); + this.board = options.board; + this.pin = this.board.normalizePin(options.pin || 9); + + // Data response is in microseconds + this.microseconds = 0; + this.inches = 0; + this.centimeters = 0; + + var types = { + read: true + }; + + // Loop and trigger fire-read sequences + setInterval(function () { + this.fire(); + }.bind(this), 50); + + this.board.on('data', function (message) { + var m = message.slice(0, -1).split('::'), + err = null, + pin, type, data; + + if (!m.length) { + return; + } + + pin = m[0]; + type = m[1]; + data = m.length === 3 ? m[2] : null; + + if (pin === this.pin && types[type]) { + // See: http://arduino.cc/en/Tutorial/Ping + // According to Parallax's datasheet for the PING))), there are + // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per + // second). This gives the distance travelled by the ping, outbound + // and return, so we divide by 2 to get the distance of the obstacle. + // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf + this.inches = data / 74 / 2; + // The speed of sound is 340 m/s or 29 microseconds per centimeter. + // The ping travels out and back, so to find the distance of the + // object we take half of the distance travelled. + this.centimeters = data / 29 / 2; + + this.emit(type, err, data); + } + }.bind(this)); +}; + +util.inherits(Ping, events.EventEmitter); + +Ping.prototype.command = function () { + var msg = '97' + this.pin + ([].slice.call(arguments).join('')); + + this.board.write(msg); +}; + +Ping.prototype.fire = function () { + this.command('01'); +}; + +module.exports = Ping; diff --git a/lib/pir.js b/lib/pir.js new file mode 100644 index 0000000..cef77dc --- /dev/null +++ b/lib/pir.js @@ -0,0 +1,69 @@ +var events = require('events'), + util = require('util'); + +/* + * Main PIR constructor + * Process options + * Tell the board to set it up + */ +var PIR = function (options) { + if (!options || !options.board) { + throw new Error('Must supply required options to PIR'); + } + this.board = options.board; + this.pin = this.board.normalizePin(options.pin || 9); + this.state = null; + this.calibrated = false; + + setInterval(function () { + this.board.digitalRead(this.pin); + }.bind(this), 50); + + this.board.on('data', function (message) { + var m = message.slice(0, -1).split('::'), + timestamp = new Date(), + err = null, + pin, data; + + if (!m.length) { + return; + } + + pin = m[0]; + data = m[1]; + + if (pin === this.pin) { + + // If this is not a calibration event + if (this.state != null && this.state != +data) { + + // Update current state of PIR instance + this.state = +data; + + // 'motionstart' event fired when motion occurs + // within the observable range of the PIR sensor + if (data === '01') { + this.emit('motionstart', err, timestamp); + } + + // 'motionend' event fired when motion has ceased + // within the observable range of the PIR sensor + if (data === '00') { + this.emit('motionend', err, timestamp); + } + } + + // 'calibrated' event fired when PIR sensor is + // ready to detect movement/motion in observable range + if (!this.calibrated) { + this.calibrated = true; + this.state = +data; + this.emit('calibrated', err, timestamp); + } + } + }.bind(this)); +}; + +util.inherits(PIR, events.EventEmitter); + +module.exports = PIR; diff --git a/lib/sensor.js b/lib/sensor.js new file mode 100644 index 0000000..a1883fc --- /dev/null +++ b/lib/sensor.js @@ -0,0 +1,45 @@ +var events = require('events'), + util = require('util'); + +/* + * Main Sensor constructor + * Process options + * Tell the board to set it up + */ +var Sensor = function (options) { + if (!options || !options.board) throw new Error('Must supply required options to Sensor'); + this.board = options.board; + this.pin = options.pin || 'A0'; + this.board.pinMode(this.pin, 'in'); + + // Poll for sensor readings + setInterval(function () { + this.board.analogRead(this.pin); + }.bind(this), options.throttle || 50); + + // When data is received, parse inbound message + // match pin to instance pin value + this.board.on('data', function (message) { + var m = message.slice(0, -1).split('::'), + err = null, + pin, data; + + if (!m.length) { + return; + } + + pin = m[0] + data = m.length === 2 ? m[1] : null; + + if (pin === this.pin) { + this.emit('read', err, data); + } + }.bind(this)); +}; + +/* + * EventEmitter, I choose you! + */ +util.inherits(Sensor, events.EventEmitter); + +module.exports = Sensor; diff --git a/lib/servo.js b/lib/servo.js index c609411..de61334 100644 --- a/lib/servo.js +++ b/lib/servo.js @@ -1,38 +1,142 @@ +var events = require('events'), + util = require('util'); + /* * Main Servo constructor * Process options * Tell the board to set it up */ var Servo = function (options) { - if (!options || !options.board) throw new Error('Must supply required options to Sutton'); + if (!options || !options.board) throw new Error('Must supply required options to Servo'); this.board = options.board; this.pin = this.board.normalizePin(options.pin || 9); - var self = this; + + var types = { + attached: true, + detached: true, + read: true, + moved: true + }; + this.board.on('ready', function () { - self.attach(); - }); -} + console.log('board ready, attaching servo', this); + this.attach(); + }.bind(this)); -Servo.prototype.write = function (pos) { - this.board.write('98' + this.pin + '02' + pos); -} + this.board.on('data', function (message) { + var m = message.slice(0, -1).split('::'), + err = null, + pin, type, data; + + if (!m.length) { + return; + } + + pin = m[0] + type = m[1]; + data = m.length === 3 ? m[2] : null; + + if (pin === this.pin && types[type]) { + this.emit(type, err, data); + } + }.bind(this)); +}; + +util.inherits(Servo, events.EventEmitter); + +Servo.prototype.command = function () { + var msg = '98' + this.pin + ([].slice.call(arguments).join('')); + + // this.board.log( 'info', 'command', msg ); + this.board.write(msg); +}; + +Servo.prototype.detach = function () { + this.command('00'); +}; Servo.prototype.attach = function () { - this.board.write('98' + this.pin + '01'); -} - -Servo.prototype.read = function () {} -Servo.prototype.detach = function () {} -Servo.prototype.writeMilliseconds = function () {} -Servo.prototype.attached = function () {} - -Servo.prototype.sweep = function () { - var self = this; - var pos = 0; - setInterval(function() { - self.write(pos++); - if (pos == 180) clearInterval(this); - }, 25); -} + this.command('01'); +}; + +Servo.prototype.write = function (pos) { + pos = this.board.lpad(3, '0', pos); + this.board.log('info', 'moving to: ' + pos); + this.command('02' + pos); +}; + +Servo.prototype.read = function () { + this.command('03'); +}; + +// Servo.prototype.writeMilliseconds = function () {}; +// Servo.prototype.attached = function (callback) { +// this.callbacks.attached.push(callback); +// }; + +Servo.prototype.sweep = function (options) { + // Ensure no missing options object + options = options || {}; + + var timeout = { + inner: null, + outer: null + }, + moves = 0, + // sweep settings + lapse = options.lapse || 2000, + to = options.to || 180, + from = options.from || 1, + // sweep handlers + doSweep = function doSweep(pos) { + // this.board.log('info', 'current position: ', pos); + var moveTo, + posint = +pos; + + // this.board.log('info', 'current pos int: ', posint); + + if (posint === 93) { + moveTo = 1; + } else { + + // this.board.log('info', 'posint not 93.....'); + + if (posint < to) { + moveTo = to; + } else if (posint == to) { + moveTo = 90; + } else { + moveTo = from; + } + } + + this.write(moveTo); + + moves++; + }; + + this.on('read', doSweep); + + // Initialize sweep; wait for for stack unwind. + timeout.outer = setTimeout(function loop() { + // Read the current position, will trigger + // 'read' event with position data; + if (moves < 2) { + this.read(); + + timeout.inner = setTimeout(loop.bind(this), lapse); + } else { + // this.board.log('info', 'info', 'clearing'); + + clearTimeout(timeout.inner); + clearTimeout(timeout.outer); + + loop = null; + + this.removeListener('read', doSweep); + this.emit.call(this, 'aftersweep'); + } + }.bind(this), 0); +}; module.exports = Servo; diff --git a/package.json b/package.json index 0f606f2..546ec97 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,21 @@ { "author": "Cam Pedersen (http://campedersen.com/)", + "contributors": [ + { "name": "Rick Waldron", "email": "waldron.rick@gmail.com" }, + { "name": "Leonhardt Wille", "email": "wille@riverlabs.de" }, + { "name": "Seiya Konno", "email": "nulltask@gmail.com" }, + { "name": "Nathan Vander Wilt", "email": "nate@calftrail.com" }, + { "name": "Adam Brault (&yet)", "email": "contact@andyet.com" }, + { "name": "Emanuele Tessore", "email": "setola@gmail.com" } + ], "name": "duino", "description": "Arduino framework for mad scientists", - "version": "0.0.5", + "version": "0.0.9", + "keywords": [ + "arduino", + "serial", + "framework" + ], "repository": { "type": "git", "url": "git://github.com/ecto/duino.git" diff --git a/src/du.ino b/src/du.ino index ba26406..728da1e 100644 --- a/src/du.ino +++ b/src/du.ino @@ -1,12 +1,16 @@ #include -char messageBuffer[12]; + +bool debug = false; + int index = 0; + +char messageBuffer[12]; char cmd[3]; char pin[3]; char val[4]; char aux[4]; -bool debug = false; + Servo servo; void setup() { @@ -27,27 +31,41 @@ void loop() { */ void process() { index = 0; - + strncpy(cmd, messageBuffer, 2); cmd[2] = '\0'; strncpy(pin, messageBuffer + 2, 2); pin[2] = '\0'; - strncpy(val, messageBuffer + 4, 3); - val[3] = '\0'; - strncpy(aux, messageBuffer + 7, 3); - aux[3] = '\0'; - + + if (atoi(cmd) > 90) { + strncpy(val, messageBuffer + 4, 2); + val[2] = '\0'; + strncpy(aux, messageBuffer + 6, 3); + aux[3] = '\0'; + } else { + strncpy(val, messageBuffer + 4, 3); + val[3] = '\0'; + strncpy(aux, messageBuffer + 7, 3); + aux[3] = '\0'; + } + if (debug) { Serial.println(messageBuffer); } int cmdid = atoi(cmd); - + + // Serial.println(cmd); + // Serial.println(pin); + // Serial.println(val); + // Serial.println(aux); + switch(cmdid) { case 0: sm(pin,val); break; case 1: dw(pin,val); break; case 2: dr(pin,val); break; case 3: aw(pin,val); break; case 4: ar(pin,val); break; + case 97: handlePing(pin,val,aux); break; case 98: handleServo(pin,val,aux); break; case 99: toggleDebug(val); break; default: break; @@ -122,7 +140,7 @@ void ar(char *pin, char *val) { char m[8]; sprintf(m, "%s::%03d", pin, rval); Serial.println(m); -} +} void aw(char *pin, char *val) { if(debug) Serial.println("aw"); @@ -134,10 +152,8 @@ void aw(char *pin, char *val) { int getPin(char *pin) { //Converts to A0-A5, and returns -1 on error int ret = -1; - if(pin[0] == 'A' || pin[0] == 'a') - { - switch(pin[1]) - { + if(pin[0] == 'A' || pin[0] == 'a') { + switch(pin[1]) { case '0': ret = A0; break; case '1': ret = A1; break; case '2': ret = A2; break; @@ -148,14 +164,44 @@ int getPin(char *pin) { //Converts to A0-A5, and returns -1 on error } } else { ret = atoi(pin); - if(ret == 0 && (pin[0] != '0' || pin[1] != '0')) - { + if(ret == 0 && (pin[0] != '0' || pin[1] != '0')) { ret = -1; } } return ret; } - + +/* + * Handle Ping commands + * fire, read + */ +void handlePing(char *pin, char *val, char *aux) { + if (debug) Serial.println("ss"); + int p = getPin(pin); + + if(p == -1) { if(debug) Serial.println("badpin"); return; } + Serial.println("got signal"); + + // 01(1) Fire and Read + if (atoi(val) == 1) { + char m[16]; + + pinMode(p, OUTPUT); + digitalWrite(p, LOW); + delayMicroseconds(2); + digitalWrite(p, HIGH); + delayMicroseconds(5); + digitalWrite(p, LOW); + + Serial.println("ping fired"); + + pinMode(p, INPUT); + sprintf(m, "%s::read::%08d", pin, pulseIn(p, HIGH)); + Serial.println(m); + + delay(50); + } +} /* * Handle Servo commands @@ -165,15 +211,42 @@ void handleServo(char *pin, char *val, char *aux) { if (debug) Serial.println("ss"); int p = getPin(pin); if(p == -1) { if(debug) Serial.println("badpin"); return; } - Serial.println("got signal"); + Serial.println("signal: servo"); + + // 00(0) Detach if (atoi(val) == 0) { servo.detach(); + char m[12]; + sprintf(m, "%s::detached", pin); + Serial.println(m); + + // 01(1) Attach } else if (atoi(val) == 1) { + // servo.attach(p, 750, 2250); servo.attach(p); - Serial.println("attached"); + char m[12]; + sprintf(m, "%s::attached", pin); + Serial.println(m); + + // 02(2) Write } else if (atoi(val) == 2) { Serial.println("writing to servo"); Serial.println(atoi(aux)); + // Write to servo servo.write(atoi(aux)); + delay(15); + + // TODO: Experiment with microsecond pulses + // digitalWrite(pin, HIGH); // start the pulse + // delayMicroseconds(pulseWidth); // pulse width + // digitalWrite(pin, LOW); // stop the pulse + + // 03(3) Read + } else if (atoi(val) == 3) { + Serial.println("reading servo"); + int sval = servo.read(); + char m[13]; + sprintf(m, "%s::read::%03d", pin, sval); + Serial.println(m); } -} \ No newline at end of file +}