diff --git a/.gitignore b/.gitignore index 30bc162..729745d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/node_modules \ No newline at end of file +# Node Stuff /node_modules # Vim Stuff [._]*.s[a-w][a-z] [._]s[a-w][a-z] *.un~ session.vim .netrwhist *~ \ No newline at end of file diff --git a/README.md b/README.md index f17c7e7..8faf58a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,89 @@ -We are building a robot from scratch using node.js +Node Rover - Makers Academy Final Project +======================== -Using Node, Http server, TCP server, socket io. -Arduino. +## Node Rover + +### Introduction + +Node Rover is a group final project from Makers Academy. The original task was to build some form of robot that could be controlled wirelessely. Our team decided to make a robot based on the Mars rover (4 engines with continuous tracks). We decided that we would buidl it from scratch, and control it via a Node.js server. The goal was to have a mobile first web app which could control several aspects of the rover (motor movement, claw, camera pan tilt bracket) over wifi. + +### Languages/Platforms/Tools + +* Node.js +* C +* Mocha +* Zombie +* MongoDB +* Mongoose +* Twit +* WiFlyHQ +* Arduino (microcontroller) +* Johnny-Five +* jQuery +* JavaScript + +### Learning Outcomes + +Node.js, JavaScript and Mocha were all somewhat familar to the team members. The true challenges were with the hardware (electronic wiring and related hardware knowledge), as well as programming directly to the Arduino with C. We had to take a ground up approach to physically configuring the rover, starting with simple lighting of LED lights via usb, and progressing to wireless single character commands over telnet. Eventually we had a fully fledged wifi rover that was controllable via a desktop browser, smartphone, smartphone gyrometer, and even with Twitter commands. + + +### To-do List +- [ ] Refactor the code. Can definitely remove a lot of duplication. +- [ ] Fix bugs - mainly the occasional server crash due to lack of TCP connection error handling. +- [ ] Add an IP camera in order to use the video stream code that was incorporated. +- [ ] Write code the the built in engine Encoders on the rover (for precision control). + +### Collaborators +Andrew Snead - (http://www.github.com/snozza) +Hadi Chalabi - (http://www.github.com/Schlap) +Zeeshan Rasool - (http://www.github.com/zrasool88) +Colin S - (http://www.github.com/mala23) +Andrew Harrison - (http://www.github.com/AndrewHarrison) + +### Instructions + +The live version of the site will be launched shortly. + +Clone the repository: + +``` +$ git clone git@github.com:snozza/project-noderover.git +``` + +Change into the directory and npm install the modules: + +``` +$ cd project-noderover +$ npm install +``` + +Run the tests: + +``` +$ mocha +``` + +Start the node server and visit http://localhost:3000/ + +``` +$ node server.js + +``` + +### Special Hardware + +Please note that the app is written for the Arduino Uno combined with a RN-XV Wifly Module, Wireless SD Shield, and a 4 motor controller shield. The code will likely need to be modified to accomodate other products. + +### Additional Info + +You will also need to sign up for a Twitter Dev account in order to use the Twitter control functionality. Add a file called config.js to the root of /node_modules/twit and add it to your .gitignore file. Then add the following (replaced with your details): + +``` +module.exports = { + consumer_key: '...' + , consumer_secret: '...' + , access_token: '...' + , access_token_secret: '...' +} + +``` diff --git a/cfiles/motor.ino b/cfiles/motor.ino index 363f794..4419d27 100644 --- a/cfiles/motor.ino +++ b/cfiles/motor.ino @@ -7,18 +7,20 @@ WiFly wifly; byte server[] = { 127, 0, 0, 1}; -const char mySSID[] = "ZyXEL1374utj"; -const char myPassword[] = "yaahctjwae"; +//const char mySSID[] = "Makers$Academy"; +//const char myPassword[] = "makersWelcome"; -const char site[] = "192.168.1.34"; +const char site[] = "192.168.50.34"; //Servos Servo arm; Servo claw; +Servo pan; int aPos = 90; int cPos = 90; +int pPos = 90; //Motors @@ -47,6 +49,7 @@ void setup() claw.attach(10); arm.attach(11); + pan.attach(12); pinMode(pwm_a,OUTPUT); pinMode(pwm_b,OUTPUT); @@ -62,21 +65,21 @@ void setup() Serial.println("Failed to start wifly"); } - if (!wifly.isAssociated()) { - /* Setup the WiFly to connect to a wifi network */ - Serial.println("Joining network"); - wifly.setSSID(mySSID); - wifly.setPassphrase(myPassword); - wifly.enableDHCP(); - - if (wifly.join()) { - Serial.println("Joined wifi network"); - } else { - Serial.println("Failed to join wifi network"); - } - } else { - Serial.println("Already joined network"); - } +// if (!wifly.isAssociated()) { +// /* Setup the WiFly to connect to a wifi network */ +// Serial.println("Joining network"); +// wifly.setSSID(mySSID); +// wifly.setPassphrase(myPassword); +// wifly.enableDHCP(); +// +// if (wifly.join()) { +// Serial.println("Joined wifi network"); +// } else { +// Serial.println("Failed to join wifi network"); +// } +// } else { +// Serial.println("Already joined network"); +// } Serial.println("WiFly ready"); Serial.println(wifly.getIP(buf, sizeof(buf))); @@ -174,7 +177,12 @@ void loop() } if(b == 'o'){ - for(aPos; aPos < 180; aPos++) { + for(aPos; aPos < 100; aPos++) { + b = Serial.read(); + if (b == 's' || b == 'l') { + arm.write(aPos); + break; + } arm.write(aPos); delay(15); }; @@ -182,7 +190,12 @@ void loop() if(b == 'p'){ Serial.println(aPos); - for(aPos; aPos > 0; aPos--) { + for(aPos; aPos > 0; aPos--) { + b = Serial.read(); + if (b == 's' || b == 'l') { + arm.write(aPos); + break; + } arm.write(aPos); delay(15); }; @@ -190,22 +203,57 @@ void loop() if(b == '0'){ Serial.println(cPos); - for(cPos; cPos < 160; cPos++) { + for(cPos; cPos < 160; cPos++) { + b = Serial.read(); + if (b == 's') { + claw.write(cPos); + break; + } claw.write(cPos); delay(5); }; }; if(b == '9'){ - Serial.println(cPos); - for(cPos; cPos > 0; cPos--) { + Serial.println(cPos); + for(cPos; cPos > 0; cPos--) { + b = Serial.read(); + if (b == 's') { + claw.write(cPos); + break; + } claw.write(cPos); delay(5); }; }; + + if(b == 'n'){ + Serial.println(pPos); + for(pPos; pPos < 180; pPos++) { + b = Serial.read(); + if (b == 's') { + pan.write(pPos); + break; + } + pan.write(pPos); + delay(5); + }; + };; + + if(b == 'm'){ + Serial.println(pPos); + for(cPos; cPos > 0; pPos--) { + b = Serial.read(); + if (b == 's') { + pan.write(pPos); + break; + } + pan.write(pPos); + delay(5); + }; + }; + } } } - - - + \ No newline at end of file diff --git a/lib/controller.js b/lib/controller.js index 89e35d9..33d4168 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -11,6 +11,7 @@ Controller.prototype.addNewPerson = function(socket) { Controller.prototype.listenOnDirections = function(socket) { this.listenOnMotors(socket); this.listenOnClaw(socket); + this.listenOnVision(socket); } Controller.prototype.listenOnMotors = function(socket) { @@ -32,6 +33,20 @@ Controller.prototype.listenOnClaw = function(socket) { socket.on('claw-stop', function() {return _this.onClawStop(this, _this)}); }; +Controller.prototype.listenOnVision = function(socket) { + _this = this + socket.on('look-right', function() {return _this.onVisionRight(this, _this)}); + socket.on('look-left', function() {return _this.onVisionLeft(this, _this)}); +} + +Controller.prototype.onVisionLeft = function(socket, _this) { + _this.transmitToArduino('n'); +} + +Controller.prototype.onVisionRight = function(socket, _this) { + _this.transmitToArduino('m'); +} + Controller.prototype.onRight = function(socket, _this) { _this.transmitToArduino('d'); socket.emit('right', 'turning right'); @@ -91,6 +106,7 @@ Controller.prototype.onKeyPress = function(socket, _this, key) { }; Controller.prototype.transmitToArduino = function(char) { + console.log(char); if (this.arduino) this.arduino.write(char); }; diff --git a/lib/routes.js b/lib/routes.js index de7c520..4f975ff 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -5,22 +5,22 @@ module.exports = function(app) { app.post('/sessions', function(req, res, next) { if (!req.body.email || !req.body.password) { - return res.end('fail'); + return res.end('Please enter both a password and an email'); } else { req.models.User.findOne({ email: req.body.email}, function(error, user) { if(error) { console.log('got here'); - return res.end('fail'); + return res.end("error"); } else if(user){ user.comparePassword(req.body.password, function(error, isMatch) { if(error) return next(error); else if(!isMatch) { - console.log('shit'); - return res.end('wrong password'); + console.log('uh oh'); + return res.end('Password is Incorrect'); } else { console.log('yayyy') @@ -30,7 +30,7 @@ module.exports = function(app) { }); } else - res.end('no user'); + res.end("User Doesn't Exist"); }); } }); diff --git a/lib/tweets.js b/lib/tweets.js index 2f1a3f3..290d361 100644 --- a/lib/tweets.js +++ b/lib/tweets.js @@ -1,14 +1,15 @@ var Twit = require('twit'); var config1 = require('../node_modules/twit/config1'); -function TwitterControl(arduino) { - this.arduino = arduino; - this.controls = controls = ['left', 'right', 'forward', 'reverse', 'brake']; +function TwitterControl() { + this.arduino = null; + this.controls = ['left', 'right', 'forward', 'reverse', 'brake']; this.twit = new Twit(config1); this.stream = this.twit.stream('statuses/filter', { track: '@TarsNode' }); }; TwitterControl.prototype.init = function() { + console.log(this.arduino) this.listenOnStreams(); }; diff --git a/public/js/controller.js b/public/js/controller.js index ab71a29..9b7c525 100644 --- a/public/js/controller.js +++ b/public/js/controller.js @@ -1,15 +1,17 @@ -function Controller() +function Controller(socket) { this.keys = [65, 68, 82, 83, 87]; + this.socket = socket; + this.gyroToggle = [this.gyroControlOn, this.gyroControlOff]; } Controller.prototype.init = function(socket) { this.listenOnMotors(socket); this.listenOnClaw(socket); - // this.listenOnVision(socket); + this.listenOnVision(socket); this.onKeyPress(socket); this.onKeyUp(socket); - this.gyroControl(socket); + this.listenOnGyro(socket); }; Controller.prototype.listenOnMotors = function(socket) { @@ -27,15 +29,23 @@ Controller.prototype.listenOnClaw = function(socket) { this.onClawRelease(socket); }; -// Controller.prototype.listenOnVision = function(socket) { -// this.onLookRight(socket); -// this.onLookLeft(socket); -// this.onLookUp(socket); -// this.onLookDown(socket); -// }; +Controller.prototype.listenOnGyro = function(socket) { + _this = this; + $(document).ready(function() { + $(document).on('click', '#gyroText, #gyro-toggle', function () { + _this.gyroToggle.reverse()[1](_this); + console.log($(document).find('#gyroText').text()) + }); + }) +} + + +Controller.prototype.listenOnVision = function(socket) { + this.onLookRight(socket); + this.onLookLeft(socket); +}; Controller.prototype.onRightClick = function(socket) { - console.log(socket) $(document).on('mousedown touchstart', '#move-right', function(){ socket.emit('right'); }); @@ -89,17 +99,17 @@ Controller.prototype.onClawRelease = function(socket) { }); }; -// Controller.prototype.onLookRight = function(socket) { -// $(document).on('mousedown touchstart', '#look-right', function() { -// socket.emit('look-right'); -// }); -// }; +Controller.prototype.onLookRight = function(socket) { + $(document).on('mousedown touchstart', '#look-up', function() { + socket.emit('look-right'); + }); +}; -// Controller.prototype.onLookLeft = function(socket) { -// $(document).on('mousedown touchstart', '#look-left', function() { -// socket.emit('look-left'); -// }); -// }; +Controller.prototype.onLookLeft = function(socket) { + $(document).on('mousedown touchstart', '#look-down', function() { + socket.emit('look-left'); + }); +}; // Controller.prototype.onLookLeft = function(socket) { // $(document).on('mousedown touchstart', '#look-up', function() { @@ -134,37 +144,33 @@ Controller.prototype.onKeyUp = function(socket) { }); } -Controller.prototype.gyroControl = function(socket) { +Controller.prototype.gyroControlOn = function(_this) { + console.log('yes') + $(document).find('#gyroText').text('Gyro Off'); var status = 0; var x; var z; gyro.startTracking(function(o) { x = o.x * 5; z = o.z * 4; - socket.emit('accel', o); + _this.socket.emit('accel', o); if (z > -15 && z < 15) { - socket.emit('claw-stop'); + _this.socket.emit('claw-stop'); } - // else if(x > 14) { - // status = 2; - // socket.emit('left') - // } - // else if(x < -14) { - // status = 1; - // socket.emit('right'); - // } else if (z > 15) { - socket.emit('claw-down'); + _this.socket.emit('claw-down'); } else if (z < 15) { - socket.emit('claw-up'); + _this.socket.emit('claw-up'); } - - // else { - // status = 3; - // socket.emit('brake') - // } }) } + +Controller.prototype.gyroControlOff = function(_this) { + console.log('no') + $(document).find('#gyroText').text('Gyro On'); + gyro.stopTracking() +} + diff --git a/public/js/interface.js b/public/js/interface.js index a8e4252..8351676 100644 --- a/public/js/interface.js +++ b/public/js/interface.js @@ -12,7 +12,6 @@ function validLogin(){ $("#popupLogin").fadeOut('slow', function() { $(document.body).load(page).fadeIn('slow'); }); - init(); }else{ console.log('fail'); $("#loginerror").html('

' + result + '

'); @@ -23,12 +22,11 @@ function validLogin(){ } function init() { - console.log('shit') - var controller = new Controller(); - var socket = io.connect(); + var socket = io(); + console.log(socket.io.subs); + var controller = new Controller(socket); controller.init(socket); socket.emit('start'); } -$(document).ready(function(){ -}); + diff --git a/server.js b/server.js index aea0281..428adb7 100644 --- a/server.js +++ b/server.js @@ -46,7 +46,7 @@ function Server() { this.sockets = []; this.app = app; this.controller = null - this.twitterControl = null; + this.twitterControl = new TwitterControl(); this.server = server this.arduinoTcp = null; this.tcpServer = null; @@ -121,7 +121,8 @@ Server.prototype.tcpServerListen = function() { if (_this.tcpServer) { console.log('num of connections on port 1337: ' + _this.tcpServer.getConnections); _this.arduinoTcp = socket; - _this.twitterControl = new TwitterControl(_this.arduinoTcp).init(); + _this.twitterControl.arduino = _this.arduinoTcp + _this.twitterControl.init(); if(_this.controller) _this.controller.arduino = _this.arduinoTcp socket.on('data', function (mydata) { diff --git a/test/features/movementGui.feature b/test/features/movementGui.feature new file mode 100644 index 0000000..b98995d --- /dev/null +++ b/test/features/movementGui.feature @@ -0,0 +1,24 @@ +Feature: User moves the robot using the GUI + In order to have a robo-gasm + As a proper geek + I want to control the movement of my node-rover remotely + + Scenario: User moves node-rover forward + Given I am on '/remotecontrol' + When I click on 'up' + Then 'move-forward' gets sent + + Scenario: User moves node-rover backward + Given I am on '/remotecontrol' + When I click on 'down' + Then 'move-backward' gets sent + + Scenario: User moves node-rover right + Given I am on '/remotecontrol' + When I click on 'right' + Then 'move-right' gets sent + + Scenario: User moves node-rover left + Given I am on '/remotecontrol' + When I click on 'left' + Then 'move-left' gets sent diff --git a/views/index.ejs b/views/index.ejs index 8702640..9cab784 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -20,20 +20,21 @@ - +

<% }else{ %> - +
+ - Turn gyro on + Gyro On @@ -133,7 +134,7 @@
- <% } %> + <% } %> @@ -143,6 +144,11 @@ + <% if(login) { %> + + <% } %>