What do you think of when you hear the name Laravel? Laravel 4 powers an increasing number of websites, but that’s not the only place it can be used. In this tutorial we’re going to use it to control embedded systems.
This tutorial demonstrates the use of an Arduino and a web cam. There are other smaller parts, but these are the main ones. The Arduino needs firmata installed (explained later) and the webcam needs to be compatible with Linux and Motion (installed later).
We will go over using things like LEDs and resisters, but none of those are particularly important. We will also go over using servos and these are important. You can find the bracket I use at: https://www.sparkfun.com/products/10335 and the servos I use at: https://www.sparkfun.com/products/9065. You can get these parts, and an Arduino Uno, for less than $55.
We’re developing a Laravel 4 application which has lots of server-side aspects; but there’s also an interactive interface. There be scripts!
For this; we’re using Bootstrap and jQuery. Download Bootstrap at: http://getbootstrap.com/ and unpack it into your public folder. Where you put the individual files makes little difference, but I have put the scripts in public/js, the stylesheets in public/css and the fonts in public/fonts. Where you see those paths in my source code; you should substitute them with your own.
Next up, download jQuery at: http://jquery.com/download/ and unpack it into your public folder.
For the server-side portion of dependencies, we need to download a library called Ratchet. I’ll explain it shortly, but in the meantime we need to add it to our composer.json file:
1 "require" : { 2 "laravel/framework" : "4.0.*", 3 "cboden/Ratchet" : "0.3.*" 4 }, Follow that up with:
1 composer update We’ll now have access to the Ratchet library for client-server communication, Bootstrap for styling the interface and jQuery for connecting these two things together.
I love all things Arduino and Raspberry Pi. When I’m not programming; I’m trying to build things (in the real world) which can interact with my programming. Don’t let that fool you though: I am not an expert in electronics. There are bound to be better ways to do all of this stuff.
If you can’t get a specific program installed because apt or permissions or network settings or aliens; I cannot help you. I have neither the time nor the experience to guide you through the many potential problems. Hit me up on Twitter and, if I have not yet already done so, I will connect you to a forum where you can discuss this with other (smarter) people.
The first step to creating an interface is returning static HTML for a web request. We handle this by creating a route, a controller and a view:
1 <?php 2 3 Route::get("/", [ 4 "as" => "index/index", 5 "uses" => "IndexController@indexAction" 6 ]); 1 <?php 2 3 class IndexController 4 extends BaseController 5 { 6 public function indexAction() 7 { 8 return View::make("index/index"); 9 } 10 } 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <title>Laravel 4 Embedded Systems</title> 6 <link rel="stylesheet" href="/css/bootstrap.3.0.0.css" /> 7 <link rel="stylesheet" href="/css/bootstrap.theme.3.0.0.css" /> 8 <link rel="stylesheet" href="/css/shared.css" /> 9 </head> 10 <body> 11 <div class="container"> 12 <div class="row"> 13 <div class="col-md-12"> 14 <h1>Laravel 4 Embedded Systems</h1> 15 <input type="number" max="255" min="0" step="5" name="led" /> 16 <input type="number" max="255" min="0" readonly name="sensor" /> 17 <input type="number" max="180" min="0" step="10" name="x" /> 18 <input type="number" max="180" min="0" step="10" name="y" /> 19 </div> 20 </div> 21 </div> 22 <script src="/js/jquery.1.9.1.js"></script> 23 <script src="/js/shared.js"></script> 24 </body> 25 </html> If you’ve created all of these files (and configured your web server to serve the Laravel application) then you should see a set of controls in your browser, when you visit /. Before we move on; let’s just quickly set up a socket server for this interface to talk to the PHP server with….
We’ve covered this topic before; so I won’t go into too much detail. We’re going to set up a Ratchet socket server, to pass messages back and forth between the interface and the PHP server.
1 try { 2 if (!WebSocket) { 3 console.log("no websocket support"); 4 } else { 5 6 var socket = new WebSocket("ws://127.0.0.1:8081/"); 7 8 socket.addEventListener("open", function (e) { 9 console.log("open: ", e); 10 }); 11 12 socket.addEventListener("error", function (e) { 13 // console.log("error: ", e); 14 }); 15 16 socket.addEventListener("message", function (e) { 17 var data = JSON.parse(e.data); 18 console.log("message: ", data); 19 }); 20 21 // console.log("socket:", socket); 22 23 window.socket = socket; 24 25 } 26 } catch (e) { 27 // console.log("exception: " + e); 28 } 1 <?php 2 3 namespace Formativ\Embedded; 4 5 use Evenement\EventEmitter; 6 use Illuminate\Support\ServiceProvider; 7 8 class EmbeddedServiceProvider 9 extends ServiceProvider 10 { 11 protected $defer = true; 12 13 public function register() 14 { 15 $this->app->bind("formativ.embedded.emitter", function() 16 { 17 return new EventEmitter(); 18 }); 19 20 $this->app->bind("formativ.embedded.command.serve", function() 21 { 22 return new Command\Serve( 23 $this->app->make("formativ.embedded.socket") 24 ); 25 }); 26 27 $this->app->bind("formativ.embedded.socket", function() 28 { 29 return new Socket( 30 $this->app->make("formativ.embedded.emitter") 31 ); 32 }); 33 34 $this->commands( 35 "formativ.embedded.command.serve" 36 ); 37 } 38 39 public function provides() 40 { 41 return [ 42 "formativ.embedded.emitter", 43 "formativ.embedded.command.serve", 44 "formativ.embedded.socket" 45 ]; 46 } 47 } 1 <?php 2 3 namespace Formativ\Embedded; 4 5 use Evenement\EventEmitterInterface; 6 use Ratchet\MessageComponentInterface; 7 8 interface SocketInterface 9 extends MessageComponentInterface 10 { 11 public function getEmitter(); 12 public function setEmitter(EventEmitterInterface $emitter); 13 } 1 <?php 2 3 namespace Formativ\Embedded; 4 5 use Evenement\EventEmitterInterface as Emitter; 6 use Exception; 7 use Ratchet\ConnectionInterface as Connection; 8 9 class Socket 10 implements SocketInterface 11 { 12 protected $emitter; 13 14 protected $connection; 15 16 public function getEmitter() 17 { 18 return $this->emitter; 19 } 20 21 public function setEmitter(Emitter $emitter) 22 { 23 $this->emitter = $emitter; 24 } 25 26 public function __construct(Emitter $emitter) 27 { 28 $this->emitter = $emitter; 29 } 30 31 public function onOpen(Connection $connection) 32 { 33 $this->connection = $connection; 34 $this->emitter->emit("open"); 35 } 36 37 public function onMessage(Connection $connection, $message) 38 { 39 $this->emitter->emit("message", [$message]); 40 } 41 42 public function onClose(Connection $connection) 43 { 44 $this->connection = null; 45 } 46 47 public function onError(Connection $connection, Exception $e) 48 { 49 $this->emitter->emit("error", [$e]); 50 } 51 52 public function send($message) 53 { 54 if ($this->connection) 55 { 56 $this->connection->send($message); 57 } 58 } 59 } 1 <?php 2 3 namespace Formativ\Embedded\Command; 4 5 use Formativ\Embedded\SocketInterface; 6 use Illuminate\Console\Command; 7 use Ratchet\ConnectionInterface; 8 use Ratchet\Http\HttpServer; 9 use Ratchet\Server\IoServer; 10 use Ratchet\WebSocket\WsServer; 11 use Symfony\Component\Console\Input\InputOption; 12 use Symfony\Component\Console\Input\InputArgument; 13 14 class Serve 15 extends Command 16 { 17 protected $name = "embedded:serve"; 18 19 protected $description = "Creates a firmata socket server."; 20 21 public function __construct(SocketInterface $socket) 22 { 23 parent::__construct(); 24 25 $this->socket = $socket; 26 27 $socket->getEmitter()->on("message", function($message) { 28 $this->info("Message: " . $message . "."); 29 }); 30 31 $socket->getEmitter()->on("error", function($e) { 32 $this->line("Exception: " . $e->getMessage() . "."); 33 }); 34 } 35 36 public function fire() 37 { 38 $port = (integer) $this->option("port"); 39 40 $server = IoServer::factory( 41 new HttpServer( 42 new WsServer( 43 $this->socket 44 ) 45 ), 46 $port 47 ); 48 49 $this->info("Listening on port " . $port . "."); 50 $server->run(); 51 } 52 53 protected function getOptions() 54 { 55 return [ 56 [ 57 "port", 58 null, 59 InputOption::VALUE_REQUIRED, 60 "Port to listen on.", 61 8081 62 ] 63 ]; 64 } 65 } These four files create a means with which we can start a socket server, listen for messages and respond to them. The shared.js file connects to this server and allows us to send and receive these messages. It’s simpler than last time; but sufficient for our needs!
Real-time communication is typically done through a software layer called Firmata. There are Firmata libraries for many programming languages, though PHP is not one of them. While there is one library listed on the official Firmata website, I could not get it to work.
So we’re going to create our own simple Firmata-like protocol. I managed to put one together that handles analog reads, analog writes and servo control. It does this by parsing strings received over serial connection. This is what it looks like:
1 #include <Servo.h> 2 3 String buffer = ""; 4 String parts[3]; 5 Servo servos[13]; 6 int index = 0; 7 8 void setup() 9 { 10 Serial.begin(9600); 11 buffer.reserve(200); 12 } 13 14 void loop() 15 { 16 17 } 18 19 void handle() 20 { 21 int pin = parts[1].toInt(); 22 int value = parts[2].toInt(); 23 24 if (parts[0] == "pinMode") 25 { 26 if (parts[2] == "output") 27 { 28 pinMode(pin, OUTPUT); 29 } 30 31 if (parts[2] == "servo") 32 { 33 servos[pin].attach(pin); 34 } 35 } 36 37 if (parts[0] == "digitalWrite") 38 { 39 if (parts[2] == "high") 40 { 41 digitalWrite(pin, HIGH); 42 } 43 else 44 { 45 digitalWrite(pin, LOW); 46 } 47 } 48 49 if (parts[0] == "analogWrite") 50 { 51 analogWrite(pin, value); 52 } 53 54 if (parts[0] == "servoWrite") 55 { 56 servos[pin].write(value); 57 } 58 59 if (parts[0] == "analogRead") 60 { 61 value = analogRead(pin); 62 } 63 64 Serial.print(parts[0] + "," + parts[1] + "," + value + ".\n"); 65 } 66 67 void serialEvent() 68 { 69 while (Serial.available()) 70 { 71 char in = (char) Serial.read(); 72 73 if (in == '.' || in == ',') 74 { 75 parts[index] = String(buffer); 76 buffer = ""; 77 78 index++; 79 80 if (index > 2) 81 { 82 index = 0; 83 } 84 } 85 else 86 { 87 buffer += in; 88 } 89 90 if (in == '.') 91 { 92 index = 0; 93 buffer = ""; 94 handle(); 95 } 96 } 97 } The Arduino sketch is only one side of the puzzle. We also need to modify our socket server to communicate with the Arduino:
1 protected $device; 2 3 public function __construct(SocketInterface $socket) 4 { 5 parent::__construct(); 6 7 $this->socket = $socket; 8 9 $socket->getEmitter()->on("message", function($message) 10 { 11 fwrite($this->device, $message); 12 13 $data = trim(stream_get_contents($this->device)); 14 $this->info($data); 15 16 $this->socket->send($data); 17 }); 18 19 $socket->getEmitter()->on("error", function($e) 20 { 21 $this->line("exception: " . $e->getMessage() . "."); 22 }); 23 } 24 25 public function fire() 26 { 27 $this->device = fopen($this->argument("device"), "r+"); 28 stream_set_blocking($this->device, 0); 29 30 $port = (integer) $this->option("port"); 31 32 $server = IoServer::factory( 33 new HttpServer( 34 new WsServer( 35 $this->socket 36 ) 37 ), 38 $port 39 ); 40 41 $this->info("Listening on port " . $port . "."); 42 $server->run(); 43 } 44 45 protected function getArguments() 46 { 47 return [ 48 [ 49 "device", 50 InputArgument::REQUIRED, 51 "Device to use." 52 ] 53 ]; 54 } 55 56 public function __destruct() 57 { 58 if (is_resource($this->device)) { 59 fclose($this->device); 60 } 61 } We’re using PHP file read/write methods to communicate with the Arduino. In our fire() method, we open a connection to the Arduino and store it in $this->device. We also set the stream to non-blocking. This is so that read requests to the Arduino don’t wait until data is returned before allowing the rest of the PHP processing to take place.
In the __construct() method we also modify the message event listener to write the incoming message (from the browser) to the Arduino. We read any info the Arduino has and send it back to the socket. This effectively creates a bridge between the Arduino and the socket.
We’ve also added two additional methods. The getArguments() method stipulates a required device argument. We want the serve command to be able to use any device name, so this argument makes sense. We’ve also added a __destruct() method to close the connection to the Arduino.
Finally, we need to add some JavaScript to read and write from the Arduino:
1 try { 2 if (!WebSocket) { 3 console.log("no websocket support"); 4 } else { 5 6 var socket = new WebSocket("ws://127.0.0.1:8081/"); 7 var sensor = $("[name='sensor']"); 8 var led = $("[name='led']"); 9 var x = $("[name='x']"); 10 var y = $("[name='y']"); 11 12 socket.addEventListener("open", function (e) { 13 socket.send("pinMode,6,output."); 14 socket.send("pinMode,3,servo."); 15 socket.send("pinMode,5,servo."); 16 }); 17 18 socket.addEventListener("error", function (e) { 19 // console.log("error: ", e); 20 }); 21 22 socket.addEventListener("message", function (e) { 23 24 var data = e.data; 25 var parts = data.split(","); 26 27 if (parts[0] == "analogRead") { 28 sensor.val(parseInt(parts[2], 10)); 29 } 30 31 }); 32 33 window.socket = socket; 34 35 led.on("change", function() { 36 socket.send("analogWrite,6," + led.val() + "."); 37 }); 38 39 x.on("change", function() { 40 socket.send("servoWrite,5," + x.val() + "."); 41 }); 42 43 y.on("change", function() { 44 socket.send("analogWrite,3," + y.val() + "."); 45 }); 46 47 setInterval(function() { 48 socket.send("analogRead,0,void."); 49 }, 1000); 50 51 } 52 } catch (e) { 53 // console.log("exception: " + e); 54 } This script does a number of cool things. When it loads, we set the pinMode of the LED pin (6) and the two servo pins (3, 5) to the correct modes. We also attach a message event listener to receive and parse messages from the Arduino.
We then attach some event listeners for the input elements in the interface. These will respond to changes in value by sending signals to the Arduino to adjust its various pin values.
Finally, we set a timer to ping the Arduino with analogRead requests on the sensor pin. Thanks to the message event listener, the return values will be received and reflected in the sensor input field.
To connect to the Arduino, you should run the following command:
1 php artisan embedded:serve /dev/cu.usbmodem1411 On my system, the Arduino is reflected as /dev/cu.usbmodem1411 by this may differ on yours. For instance, my Ubuntu machine shows it as /dev/ttyACM0. The easiest way to find out what it is, is to unplug it, then run the following command:
1 ls /dev/tty* Look over the list to familiarise yourself with the devices you see there. The plug the Arduino in, wait a couple seconds and run that command again. This time you should see a new addition to the list of devices. This is most likely your Arduino.
I usually like to include Vagrant configurations for running these projects. This time I found it confusing to try and communicate with an Arduino through the host machine. So instead I decided to try the server.php script bundled with Laravel applications. Turns out you can run the server with the following command:
1 php -S 127.0.0.1:8080 -t ./public server.php Now you can go to http://127.0.0.1:8080 and see the interface we created earlier. You need to start the serve command successfully before this interface will work. If everything is running correctly, you should immediately start to see the sensor values being updated in the interface.
You can now adjust the LED input to control the brightness of the LED, and the x and y inputs to control the rotation of the servos.
Streaming video from a web cam can be tricky, so we’re going to take time-lapse photos instead…
The utility we’ll be using on OSX is called ImageSnap. We can install it with homebrew:
brew install imagesnap
Once that’s installed; we can periodically take photos with it by using a command resembling:
1 imagesnap -d "Microsoft LifeCam" -w 3 The web cam I am using is Microsoft LifeCam but you can find the appropriate one for you with the command:
1 imagesnap -l I also had to add some warm-up time, with the -w switch.
The utility we’ll be using on Ubuntu/Debian is called Streamer. We can install it with the command:
1 apt-get install -y streamer You can take a photo with the web cam, using the following command:
1 streamer -o snapshot.jpeg Depending on whether you are using OSX or Ubuntu/Debian; you will need to set up a shell script to periodically take photos. Save them to a web-accessible location and display them with the following changes:
1 <img name="photo" /> 1 setInterval(function() { 2 $("[name='photo']").attr("src", "[path to photo]"); 3 }, 1000);