Traditional conference rooms often come with frustrating user experiences where many callers can’t seem to get in, missed the invitation, or can’t seem to find the e-mail where the call details were provided. With Rehuddle, users are given several super simple ways to share: 1) click to automatically copy the call details to your clipboard 2) click to auto generate an e-mail with the call details 3) text message someone to join the room 4) share the room via facebook and 5) generate a call that places a caller directly in the room (no extension needed).
The site is built with Ruby, Asterisk, Javascript, Node.js, and MySQL. Each user that joins a “huddle” is provided an avatar based on the last four digits of their phone-number. This allows each user to be uniquely identified and understand who is on the call. The avatars are currently being generated using Node.js and Underscore.js. Node is being used to ping our mySQL server every second and pipe the results into a socket that the client can read by parsing JSON. Underscore.js is being used to compare the current list of callers with a previous list of callers. This allows the client to know which callers to add for each browser and which callers to remove. Each conference room also gets a unique URL. If a user creates a room called “Sony Meeting” – Rehuddle will auto generate a URL Rehuddle.com/sony-meeting where the call can live. This url generation is being done with Ruby utilizing Sinatra and Datamapper/MySQL. Within the room our copy to clipboard share feature is using a JQuery library called zClip. The SMS messages are being routed through a Twilio’s Ruby API, the Facebook share call-to-action is using the standard Facebook Developers share button, and the call generator is happening in Asterisk, but is routed through Flowroute through a SIP registry.
In future iterations Phil and I are looking to add document sharing features, editable name tags for each avatar, and potentially unique phone numbers for each room. We both feel that there’s a huge gap in the market for conference systems that are simple, easy to use, easy to share, and provide an informative and enjoyable web interface. We both look forward to more user testing to see where this product takes us. A big thank you goes to Chris Kairalla for teaching us Asterisk and helping us debug throughout the build!
var net = require('net') //TCP Server
var sockets = [] //empty array for ppl who connect to server
var server = net.createServer(function(socket){
sockets.push(socket); //add person to socket
socket.on('data', function(data){
for(var i = 0; i<sockets.length; i++){
if(sockets[i] == socket) continue; //dont broadcast your own echo
sockets[i].write(data); //when data is received write
}
});
//remove socket from arary
socket.on('end', function(){
var i = sockets.indexOf(socket);
sockets.splice(i,1); //remove person from array
//could also do-- delete socket.[0]
});
});
server.listen(8000);The Arduino code is as follows:
#define FORWARD 10 #define REVERSE 20 #define STOPPED 30 #define forwardPin 6 #define reversePin 7 #define leftPin 8 #define rightPin 9 #define pulseDuration 500 #include <SPI.h> #include <Ethernet.h> //setup 10 A5 DA 00 85 70 byte mac[] = { 0x10, 0xA5, 0xDA, 0x00, 0x85, 0x70 }; IPAddress server (XXX,XXX,XXX,XX); // my server int port = 12002; EthernetClient client; unsigned long startPulseTime = 0; int drivingState = STOPPED; int inByte = 0; // incoming serial byte // The setup() method runs once, when the sketch starts void setup() { // initialize the digital pin as an output: pinMode(forwardPin, OUTPUT); pinMode(reversePin, OUTPUT); pinMode(leftPin, OUTPUT); pinMode(rightPin, OUTPUT); Ethernet.begin(mac); Serial.begin(9600); delay(1000); if (Ethernet.begin(mac) == 0) { Serial.println("Failed to configure Ethernet using DHCP"); // no point in carrying on, so do nothing forevermore: for(;;) ; } Serial.println("Ethernet reday"); serverConnect(); } void serverConnect(){ Serial.println("connecting to remote server..."); if (client.connect(server, port)) { Serial.println("connected to remote server"); client.println('A'); } else { Serial.println("remote connection failed"); } } // the loop() method runs over and over again, // as long as the Arduino has power void loop() { // if we get a valid byte, read analog ins: if (client.available()) { // get incoming byte: inByte = client.read(); //Serial.print(inByte); switch(inByte){ case '2': Serial.println("forward"); forward(); break; case '8': Serial.println("reverse"); reverse(); break; case '4': Serial.println("left"); turnLeft(); break; case '6': Serial.println("right"); turnRight(); break; } } else { if (drivingState != STOPPED){ if ((millis() - startPulseTime) > pulseDuration){ Serial.println("pulse ended"); powerStop(); } } } } void forward(){ drivingState = FORWARD; startPulseTime = millis(); digitalWrite(reversePin, LOW); digitalWrite(forwardPin,HIGH); } void reverse(){ drivingState = REVERSE; startPulseTime = millis(); digitalWrite(forwardPin, LOW); digitalWrite(reversePin, HIGH); } void turnLeft(){ digitalWrite(rightPin,LOW); digitalWrite(leftPin, HIGH); } void turnRight(){ digitalWrite(leftPin, LOW); digitalWrite(rightPin,HIGH); }
The node code is as follows:
var agi_net = require('net'); var remote_net = require('net'); var REMOTE_PORT=12002; var AGI_HOST = '127.0.0.1'; var AGI_PORT = 12001; /** * AGI will only send key presses, and that's it. * There's no identifying characteristics, just raw press events. */ /** Remote clients send no info to this server. * all they do is receive byte representations of digits. * (0-9, #, *) on the phone's keypad as ascii bytes*/ //remote screens or physical objects var remoteClients = []; remote_net.createServer(function(sock){ console.log('CONNECTED REMOTE CLIENT: ' + sock.remoteAddress +':'+ sock.remotePort); remoteClients.push(sock); //add client // Add a 'close' event handler to this instance of socket sock.on('close', function() { for(var i = 0; i < remoteClients.length; i++) { if(remoteClients[i] == sock) { //remove client remoteClients.splice(i,1); break; } } console.log('DISCONNECTED REMOTE CLIENT: ' + sock.remoteAddress +':'+ sock.remotePort); }); // Add a 'data' event handler to this instance of socket sock.on('data', function(data) { for ( var i = 0; i < data.length; i++){ handleByte(data[i]); } function handleByte(buf){ console.log(buf); } }); }).listen(REMOTE_PORT); console.log('Server listening for remote connections on ' + REMOTE_PORT); agi_net.createServer(function(sock){ console.log('CONNECTED AGI CLIENT: ' + sock.remoteAddress +':'+ sock.remotePort); // Add a 'close' event handler to this instance of socket sock.on('close', function(data) { console.log('CLOSED AGI CLIENT: ' + sock.remoteAddress +':'+ sock.remotePort); }); // Add a 'data' event handler to this instance of socket sock.on('data', function(data) { console.log(data); //broadcast data to all remote clients for (var i = 0; i < remoteClients.length; i++){ var client = remoteClients[i]; client.write(data); } }); }).listen(AGI_PORT, AGI_HOST); console.log('Server listening for AGI connections on ' + AGI_HOST +':'+ AGI_PORT);
The AGI script is as follows:
#!/usr/bin/ruby #very simple implementation of asterisk -> server -> client stack #only sends the digit and the caller id. #designed for very simple control, like arduino or web page require 'rubygems' require 'ruby-agi' require 'socket' agi = AGI.new host = 'localhost' port = 12001 looping = true s = TCPSocket.open(host, port) agi.stream_file("vm-extension") while looping result = agi.wait_for_digit(-1) # wait forever if result.digit s.write "#{result.digit}" else #hangup broke the pending AGI request looping = false end end
The asterisk dialplan is as follows:
[rtt233_socketIO_arduino] exten => 1,1,Answer(); exten => 1,n,AGI(/home/rtilton1/asterisk_agi/socketIO_arduino.rb); exten => 1,n,Hangup();
All code examples and knowledge are curtesy to our awesome professor Chris Kairalla! I’m excited to continue on node and download the free book/pdf found here: http://visionmedia.github.com/masteringnode/



