Super Simple Raspberry Pi Video Kiosk

How many times have you gone into a museum and had a supposed interactive exhibit not work? It's pretty difficult to expose computers to the general public and expect them to keep working through hell and high water. Here's a really simple recipe for creating an HD capable video kiosk from just an Arduino and a Raspberry Pi.

Physical Interface

To get over issues of broken keyboards and mice, I'm going to a use a simple capacitive sensor to trigger playing videos. Unless Joe Public is walking around with a Taser, this should be pretty resistant to most kinds of physical abuse. For this prototype, the sensor pads are just circles of aluminium foil stuck to a case made of Corex plastic sheet, but in a production machine, I'd probably use stainless steel and an MDF case.



Touchsensor
Touch Sensors


Rather than fiddle around with the GPIO pins on the Raspberry Pi, and because there aren't very many of them, I'm using an Arduino, (in this case a homebrewed one), to tell the Pi, via a serial USB port, which pad has been touched. So now we can have up to 11 touch pads, which should be enough for most installations.



Document_2013-04-02_16-38-26
Kiosk Connections


Here is the code for the Arduino, you can also get it on GitHub https://gist.github.com/mkarliner/5318550. The great thing about this capacitive sensor implementation is that is requires absolutely no extra hardware at all. All you do is hook up the touch pad to the Arduino's digital I/O pins and you're done.

Arduino Code

This code is based on Capacitive Sensors with No Additional Hardware

// readCapacitivePin
//  Input: Arduino pin number
//  Output: A number, from 0 to 17 expressing
//  how much capacitance is on the pin
//  When you touch the pin, or whatever you have
//  attached to it, the number will get higher
#include "pins_arduino.h" // Arduino pre-1.0 needs this
uint8_t readCapacitivePin(int pinToMeasure) {
  // Variables used to translate from Arduino to AVR pin naming
  volatile uint8_t* port;
  volatile uint8_t* ddr;
  volatile uint8_t* pin;
  // Here we translate the input pin number from
  //  Arduino pin number to the AVR PORT, PIN, DDR,
  //  and which bit of those registers we care about.
  byte bitmask;
  port = portOutputRegister(digitalPinToPort(pinToMeasure));
  ddr = portModeRegister(digitalPinToPort(pinToMeasure));
  bitmask = digitalPinToBitMask(pinToMeasure);
  pin = portInputRegister(digitalPinToPort(pinToMeasure));
  // Discharge the pin first by setting it low and output
  *port &= ~(bitmask);
  *ddr  |= bitmask;
  delay(1);
  // Make the pin an input with the internal pull-up on
  *ddr &= ~(bitmask);
  *port |= bitmask;

  // Now see how long the pin to get pulled up. This manual unrolling of the loop
  // decreases the number of hardware cycles between each read of the pin,
  // thus increasing sensitivity.
  uint8_t cycles = 17;
       if (*pin & bitmask) { cycles =  0;}
  else if (*pin & bitmask) { cycles =  1;}
  else if (*pin & bitmask) { cycles =  2;}
  else if (*pin & bitmask) { cycles =  3;}
  else if (*pin & bitmask) { cycles =  4;}
  else if (*pin & bitmask) { cycles =  5;}
  else if (*pin & bitmask) { cycles =  6;}
  else if (*pin & bitmask) { cycles =  7;}
  else if (*pin & bitmask) { cycles =  8;}
  else if (*pin & bitmask) { cycles =  9;}
  else if (*pin & bitmask) { cycles = 10;}
  else if (*pin & bitmask) { cycles = 11;}
  else if (*pin & bitmask) { cycles = 12;}
  else if (*pin & bitmask) { cycles = 13;}
  else if (*pin & bitmask) { cycles = 14;}
  else if (*pin & bitmask) { cycles = 15;}
  else if (*pin & bitmask) { cycles = 16;}

  // Discharge the pin again by setting it low and output
  //  It's important to leave the pins low if you want to 
  //  be able to touch more than 1 sensor at a time - if
  //  the sensor is left pulled high, when you touch
  //  two sensors, your body will transfer the charge between
  //  sensors.
  *port &= ~(bitmask);
  *ddr  |= bitmask;

  return cycles;
}



void setup()  {
  Serial.begin(9600);
}

  int last_val;

void loop() {
  int x;
  int val;

  for(x=9; x<12; x++) {
   val = readCapacitivePin(x); 
  // Only send a value to the serial port if it's over the threshold and it's a different pin.
   if(val > 5 && last_val !=x) {
     Serial.println(x);
     last_val = x;
     delay(100);
   }
  }

}

OK, so now we have a Arduino that sends the number of the I/O pin that has been triggered down the serial port, but only if the trigger is a new one and is above a threshold value.

Let's turn our attention to the Pi.

At the time of writing, (April 2013), I'm not aware of any X-Window servers for the Pi which have hardware acceleration, but in this case that's just fine, as we just want a full screen display of a video.

Omxplayer is a command line video player that is bundled with the Raspbian distro for the Pi, and will playout H.264 video at full HD resolution. Just the thing.

Here's a simple script, written in Ruby, that waits for the Arduino to send a new touch signal, and then plays an associated video file. It's obviously pretty dumb, but I think that it illustrates the principal concisely. Note the -o option to omxplayer, that means the audio should be sent down the hdmi link to the TV/monitor.

Player script

#!/usr/bin/ruby

require 'rubygems'
require 'serialport'


p = SerialPort.new "/dev/ttyUSB0", 9600

while(true)
        val = p.gets.to_i
        last_val = -1
        puts "VAL: #{val} LV #{last_val}"

        if(val == 10 && last_val != val)
                last_val = 10
                puts "ten"
                %x(omxplayer -o hdmi "sw.mp4")
        elsif(val == 11 && last_val !=val)
                last_val = 11
                puts "eleven"
                %x(omxplayer -o hdmi "titanic.mp4")
        end
        sleep(1)
end

Final touch

If you want this to be started up when the Arduino boots, just add an entry to /etc/rc.local like this:

nohup <name of ruby script> >/dev/null &