Wednesday, October 22, 2014

Tessel.io voting / rating system

First tessel.io experiment

As a lecturer I can imaging that sometimes you go to fast for the students, sometimes it's too slow, etc... During conferences the audiance wants to give feedback... and so on... so why not build a rating display with a tessel.io and 2 servo's?

The hardware part


1) more info on the microcontroller
- Tessel.io
- Runs JavaScript on the bare hardware with wifi onboard
- Has many plug-in IO boards (relay, gps, servo, ambient, cellular data, ...)

2) Save a power supply / need for usb cable:
- The servo board needs its own power supply anyway (adaptor included)
- I soldered a 2 pin header onto the tessel board
- Connected the power for a servo to this Vin tessel (black&red wire)

3) The dials:
- I've made 2 holes in a cardboard
- put in the servo's
- made 2 dials from hard white plastic


The software part




1) The voting system - pure prototype object - nothing to do with the tessel.

// Param prototype object with a Question, a Yes and No option
function Param(Q, Y, N, servo) {
  this.Q = Q; this.Y = Y; this.N = N;

  // clear Y and N counters
  this.YCnt = 0this.NCnt = 0;

  // remember my servo for rendering the score
  this.servo = servo;
}

Param.prototype.reset = function() {
  // clear Y and N counters
  this.YCnt = 0this.NCnt = 0;
  return this;
}

Param.prototype.read = function(obj, name) {
  // read ourselfs from a http query object
  this.Q = obj[name]; this.Y = obj[name+"Y"]; this.N = obj[name+"N"];
  return this;
}

Param.prototype.add = function(choice) {
  if (choice == "Y"this.YCnt++;
  if (choice == "N"this.NCnt++;
  return this;
};

Param.prototype.render = function() {
  // set the servo to a position 0..1
  //   0.5 if there are no votes received yet, avoid div0
  var total = this.YCnt+this.NCnt;


  // move the servo's see (2)
  servoSystem.move(this.servo, ((total == 0) ? 0.5 : this.YCnt / total));
  return this;
};

// make 2 param objects
var A = new Param("Question 1""Yes""No"1);
var B = new Param("Question 2""Good""Bad"2);


Example usage from the http server using our objects

// user entered a vote
if (cmd == 'Vote') {
  A.add(urlObj.query.A).render();
  B.add(urlObj.query.B).render();
  res.write(homepage('Thanks for voting'), 'utf8');        
}



2) Controlling the dials / servo's:

var tessel = require('tessel');

var servolib = require('servo-pca9685');
var servoSystem = servolib.use(tessel.port['A']);

...

// method from our Param object
Param.prototype.render = function() {
  // set the servo to a position 0..1 
  // set to 0.5 if there are no votes received yet, prevent div0
  var total = this.YCnt+this.NCnt;
  servoSystem.move(this.servo, ((total == 0) ? 0.5 : this.YCnt / total));
  return this;
};

..

servoSystem.on('ready', function () {
  
  // configure servo 1 & 2 - max and min
  servoSystem.configure(A.servo, 0.04, 0.13, function () {
  servoSystem.configure(B.servo, 0.04, 0.13, function () {

    
  // setup voting system [ see above in (1) ]
  ...
    
  // setup http server [ see below in (3) ]
  ...

  });
  });
});


3) The http server

This is quick and dirty stuff, because in the end, we don't want this on the tessel.

The http stuff will go to a bigger webserver
- this will keep the score and handle the http pages
- the tessel will send http json requests to get the current score (or we could keep a websocket open between the tessel and the webserver)  

    var server = http.createServer(function (req, res) {
  
      // parse url into and object,
      // parse also query string (true as 2nd param)
      var urlObj = url.parse(req.url, true);
      var pathname = urlObj.pathname;
      
      // serving a home page
      if (pathname == '/') {
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(homepage(), 'utf8');
        res.end();
        return;
        
        
      // serving an admin page
      } else if (pathname == '/admin') {

        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(adminpage(), 'utf8');
        res.end();
        return;
        
      // accepting commands
      } else if (typeof urlObj.query != "undefined") {
        var cmd = urlObj.query.request;
        res.writeHead(200, {'Content-Type': 'text/html'});
        
        // user entered a vote
        if (cmd == 'Vote') {
          A.add(urlObj.query.A).render();
          B.add(urlObj.query.B).render();
          res.write(homepage('Thanks for voting'), 'utf8');
        
        // administrator changed the question + reset the counters
        } else if (cmd == 'Save') {
          A.read(urlObj.query, "A").reset().render();
          B.read(urlObj.query, "B").reset().render();
          res.write(homepage('Parameters saved'), 'utf8');

          
        } else {
          res.write('<!DOCTYPE html><html><h1>Illegal command</h1>' +
                    ' -- Go play somewhere else!</html>', 'utf8');
        }
        res.end();
        return;
      }
    });

    // have the server listen for incoming requests
    server.listen(80);