Книга: Laravel 4 Cookbook
Назад: E-Commerce
Дальше: File-Based CMS

Embedded Systems

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.

The code for this chapter can be found at: https://github.com/formativ/tutorial-laravel-4-embedded-systems.

Gathering Parts

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.

Installing Dependencies

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 }, 

This was extracted from composer.json.

Follow that up with:

1 composer update 

Ratchet isn’t built specifically for Laravel 4, so there are no service providers for us to add.

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.

Note About Errata

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.

Creating An Interface

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 ]); 

This file should be saved as app/routes.php.

 1 <?php  2   3 class IndexController  4 extends BaseController  5 {  6   public function indexAction()  7   {  8     return View::make("index/index");  9   } 10 } 

This file should be saved as app/controllers/IndexController.php.

 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> 

This file should be saved as app/views/index/index.blade.php.

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….

Creating A Socket Server

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 } 

This file should be saved as public/js/shared.js.

 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 } 

This file should be saved as workbench/formativ/embedded/src/Formativ/Embedded/EmbeddedServiceController.php.

 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 } 

This file should be saved as workbench/formativ/embedded/src/Formativ/Embedded/SocketInterface.php.

 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 } 

This file should be saved as workbench/formativ/embedded/src/Formativ/Embedded/Socket.php.

The Socket class only has room for a single connection. Feel free to change this so that it can handle any number of controlling connections.

 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 } 

This file should be saved as workbench/formativ/embedded/src/Formativ/Embedded/Command/Serve.php.

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!

Connection To Arduino

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 } 

This code needs to be uploaded to the Arduino you’re working with for any communication between our server and the Arduino to take place.

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 } 

This file should be saved as workbench/formativ/embedded/src/Formativ/Embedded/Command/Serve.php.

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 was extracted from public/js/shared.js.

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.

Spinning Up

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.

You may be wondering why my device starts with cu and not tty. I first tried the tty version of the Arduino and it would cause the server to hang. I experimented and found that the cu version let me connect and communicate with the Arduino. Go figure…

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.

Adding A Webcam

Streaming video from a web cam can be tricky, so we’re going to take time-lapse photos instead…

Installing ImageSnap On OSX

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.

Installing Streamer On Ubuntu/Debian

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 

Displaying Photos In The Interface

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" /> 

This was extracted from app/views/index/index.blade.php.

1 setInterval(function() { 2   $("[name='photo']").attr("src", "[path to photo]"); 3 }, 1000); 

This was extracted from public/js/shared.js.

Назад: E-Commerce
Дальше: File-Based CMS