Rise of the Machines: PHP and IoT - php[world] 2016

Preview:

Citation preview

Rise of the Machines:PHP & IoTphp[world] 2016

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Colin O’Dell• Lead Web Developer at Unleashed Technologies

• PHP League Member league/commonmark league/html-to-markdown

• PHP 7 Upgrade Guide e-book

• Arduino / RasPi / 3D printing Enthusiast

@colinodell - joind.in/talk/18676

Internet of ThingsThe Internet of Things is the internetworking of physical devices, vehicles, buildings and other items—embedded with electronics, software, sensors, actuators, and network connectivity that enable these objects to collect and exchange data.

- https://en.wikipedia.org/wiki/Internet_of_things

@colinodell - joind.in/talk/18676

Internet of ThingsThe Internet of Things is the internetworking of physical devices, vehicles, buildings and other items—embedded with electronics, software, sensors, actuators, and network connectivity that enable these objects to collect and exchange data.

- https://en.wikipedia.org/wiki/Internet_of_things

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Connectivity• Ethernet / WiFi

• NFC / RFID

• Bluetooth LE

• Zigbee / Z-Wave

• Cellular (LTE / CDMA / GSM)

• And many others

@colinodell - joind.in/talk/18676

2003 2010 2015 20200

10

20

30

40

50

60

Population vs Connected Devices

People Connected Devices

(Bill

ions

)

Source: https://www.cisco.com/c/dam/en_us/about/ac79/docs/innov/IoT_IBSG_0411FINAL.pdf

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Today’s Agenda• Device overview

• Four examples of combining IoT with PHP Project Goals Hardware & Platform Choice Building the device Demo

• Q&A

@colinodell - joind.in/talk/18676

DIY IoT DevicesFor the software or hardware enthusiast

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

How to choose?• Identify required features

• Ideal programming language

• Consider power requirements

• Size / footprint

@colinodell - joind.in/talk/18676

Four Examples Using PHP

Uptime MonitorUse Raspberry Pi to monitor if site is up; change LED color accordingly

@colinodell - joind.in/talk/18676

Why Raspberry Pi?• Runs Linux (Raspian – a derivative of Debian)

• Has GPIO pins

• PHP easily installed

• Raspberry Pi Zero only $5

• Handles HTTPs out-of-the-box

• Why not?

@colinodell - joind.in/talk/18676

Hardware• Raspberry Pi (any variant)• Micro USB cable (power)• USB WiFi dongle

• RGB LED• Two resistors• Wires

@colinodell - joind.in/talk/18676

RGB LEDs

@colinodell - joind.in/talk/18676

Wiring

@colinodell - joind.in/talk/18676

$ composer require piphp/gpio

@colinodell - joind.in/talk/18676

<?php

const URL = 'https://www.colinodell.com';const GPIO_PIN_GREEN = 12;const GPIO_PIN_RED = 16;

require_once 'vendor/autoload.php';

$gpio = new PiPHP\GPIO\GPIO();$greenLed = $gpio->getOutputPin(GPIO_PIN_GREEN);$redLed = $gpio->getOutputPin(GPIO_PIN_RED);

$httpClient = new GuzzleHttp\Client();

while (true) { try { $response = $httpClient->head(URL, [ 'connect_timeout' => 3, 'timeout' => 3, ]);

echo URL . " is online\n"; $greenLed->setValue(1); $redLed->setValue(0); } catch (\RuntimeException $ex) { echo URL . " is OFFLINE!\n"; $greenLed->setValue(0); $redLed->setValue(1); }

sleep(3);}

@colinodell - joind.in/talk/18676

Recap• PHP on a Raspberry Pi

• PHP checks if site is up

• PiPHP/GPIO library used to control output pins (LEDs)

Other uses for output pins:

• Control motors/servos

• Control relays (turn higher-voltage things on/off)

• Drive digital displays (LCD screens, LED number displays, etc.)

@colinodell - joind.in/talk/18676

Conference Room Occupancy SensorRaspberry Pi detects movement in room; sends room status to Slack

@colinodell - joind.in/talk/18676

Why Raspberry Pi?• Runs Linux (Raspian – a derivative of Debian)

• Has GPIO pins

• PHP easily installed

• Raspberry Pi Zero only $5

• Handles HTTPs out-of-the-box

• Why not?

TL;DR: Same reasons as before!

@colinodell - joind.in/talk/18676

Hardware• Raspberry Pi (any variant)• Micro USB cable (power)• USB WiFi dongle

• PIR Sensor• Wires

@colinodell - joind.in/talk/18676

PIR Sensors

Images from https://learn.adafruit.com/pir-passive-infrared-proximity-motion-sensor/overview

@colinodell - joind.in/talk/18676

Wiring

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Signal Motion Detected

Occupied Vacant Occupied

@colinodell - joind.in/talk/18676

<?php

namespace ColinODell\PHPIoTExamples\PHPPIRSensor;

class Room{ /** * @var bool */ private $occupied = false;

/** * @var int */ private $lastMovement;

/** * @var callable */ private $onRoomStateChange;

/** * @var int */ private $roomEmptyTimeout;

/** * Room constructor. */ public function __construct() { $this->lastMovement = time(); }

/** * After $timeout seconds of no movement, the room will be marked as vacant. * * @param int $timeout */ public function setRoomEmptyTimeout($timeout) { $this->roomEmptyTimeout = $timeout; }

/** * Provide a callback to respond to the room becoming vacant or occupied. * * A single boolean parameter will be passed, denoting whether: * - The room is now occupied (true) * - The room is now vacant (false) * * @param callable $callback */ public function setOnRoomChangeCallback(callable $callback) { $this->onRoomStateChange = $callback; }

/** * Call this function whenever there is motion. * * @return void */ public function motionDetected() { $this->setOccupied(true);

$this->lastMovement = time(); }

/** * Call this function regularly to see if enough time has elapsed * to mark the room vacant. * * @return void */ public function tick() { // If there's been no movement lately, set the room to vacant if (time() - $this->lastMovement >= $this->roomEmptyTimeout) { $this->setOccupied(false); } }

/** * @param bool $newState */ private function setOccupied($newState) { if ($newState !== $this->occupied) { $this->occupied = $newState; if (is_callable($this->onRoomStateChange)) { $func = $this->onRoomStateChange; $func($newState); } } }}

@colinodell - joind.in/talk/18676

<?php

// Configuration:

const GPIO_PIN = 17;const DECLARE_ROOM_EMPTY_AFTER = 60*5; // 5 minutesconst SLACK_INCOMING_WEBHOOK_URL = 'https://hooks.slack.com/services/xxxx/xxxx/xxxx';

// End configuration

require_once 'vendor/autoload.php';

use ColinODell\PHPIoTExamples\PHPPIRSensor\Room;use PiPHP\GPIO\GPIO;use PiPHP\GPIO\Pin\InputPinInterface;

// HTTP client for communicating with Slack$http = new \GuzzleHttp\Client();

// Instantiate a new room object to keep track of its state$room = new Room();$room->setRoomEmptyTimeout(DECLARE_ROOM_EMPTY_AFTER);$room->setOnRoomChangeCallback(function($isOccupied) use ($http) { $message = 'The conference room is now ' . ($isOccupied ? 'occupied.' : 'vacant.');

$http->postAsync(SLACK_INCOMING_WEBHOOK_URL, [ 'json' => [ 'text' => $message, ], ]);});

// Create a GPIO object$gpio = new GPIO();

// Set the pin to watch for rising edges$pin = $gpio->getInputPin(GPIO_PIN);$pin->setEdge(InputPinInterface::EDGE_RISING);

// Call $room->motionDetected() whenever the pin goes HIGH$interruptWatcher = $gpio->createWatcher();$interruptWatcher->register($pin, function($pin, $value) use ($room) { $room->motionDetected();});

// Run foreverwhile (true) { $interruptWatcher->watch(DECLARE_ROOM_EMPTY_AFTER * 1000); $room->tick();}

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Recap• PHP on a Raspberry Pi

• PiPHP/GPIO library monitors GPIO pin for signal changes

• PHP tracks duration between rising edges (low-to-high signal changes)

• Notifications pushed to Slack via webhook

Other uses for input pins:

• Switches & buttons

• Sensors (temperature, humidity, motion, accelerometer, GPS)

• Receiving data from other devices

Alexa Custom SkillUse PHP + Laravel to handle request for php[world] session information

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Why Alexa / Amazon Echo?• Amazon handles voice recognition

• Parsed request passed from Amazon to our API endpoint

• We handle accordingly; send formatted response for Alexa to read aloud

• Published skills easily installable

@colinodell - joind.in/talk/18676

User Interaction Flow

Diagram from https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/overviews/understanding-custom-skills

@colinodell - joind.in/talk/18676

Invoking Custom Skills

• Ask [skill name] [action] Ask PHP World which sessions are next

• Tell [skill name] [action] Tell PHP World this talk is great

• [action] using [skill name] Rate this talk 5 stars using PHP World

@colinodell - joind.in/talk/18676

Actions• What sessions are next?

• What session is next in {room}?

• What sessions are at {time} in {room}?

• Who is speaking in {room} {day} at {time}?

@colinodell - joind.in/talk/18676

Defining ActionsGetSessions what sessions are nextGetSessions what talks are nextGetSessions who is talking nextGetSessions who is speaking nextGetSessions which sessions are nextGetSessions which talks are next

GetSessions what session is next in {room}GetSessions what talk is next in {room}GetSessions who is talking next in {room}GetSessions who is speaking next in {room}GetSessions which session is next in {room}GetSessions which talk is next in {room}

GetSessions what sessions are at {time}GetSessions what talks are at {time}GetSessions who is talking at {time}GetSessions who is speaking at {time}GetSessions which sessions are at {time}GetSessions which talks are at {time}

GetSessions what sessions are at {time} {day}GetSessions what talks are at {time} {day}GetSessions who is talking at {time} {day}GetSessions who is speaking at {time} {day}

GetSessions which sessions are at {time} {day}GetSessions which talks are at {time} {day}

GetSessions what session is in {room} at {time}GetSessions what talk is in {room} at {time}GetSessions who is talking in {room} at {time}GetSessions who is speaking in {room} at {time}GetSessions which session is in {room} at {time}GetSessions which talk is in {room} at {time}

GetSessions what session is in {room} at {time} {day}GetSessions what talk is in {room} at {time} {day}GetSessions who is talking in {room} at {time} {day}GetSessions who is speaking in {room} at {time} {day}GetSessions which session is in {room} at {time} {day}GetSessions which talk is in {room} at {time} {day}

GetSessions what session is {day} in {room} at {time}

GetSessions what talk is {day} in {room} at {time}GetSessions who is talking {day} in {room} at {time}GetSessions who is speaking {day} in {room} at {time}GetSessions which session {day} is in {room} at {time}GetSessions which talk {day} is in {room} at {time}

@colinodell - joind.in/talk/18676

Intent Schema{ "intents": [ { "intent": "GetSessions", "slots": [ { "name": "day", "type": "AMAZON.DATE" }, { "name": "time", "type": "AMAZON.TIME" }, { "name": "room", "type": "ROOM" } ] }, { "intent": "AMAZON.HelpIntent" } ]}

FairfaxGreat FallsPotomacAsh Grove AAsh Grove BAsh Grove C

@colinodell - joind.in/talk/18676

$ composer require develpr/alexa-app

AlexaRoute::intent('/alexa-end-point', 'GetDeveloperJoke', function(){ Alexa::say("A SQL query goes into a bar, walks up to two tables and asks, \"Can I join you?\"");});

@colinodell - joind.in/talk/18676

<?php

namespace App\Console\Commands;

use Carbon\Carbon;use Illuminate\Support\Facades\DB;use GuzzleHttp\Client;use Illuminate\Console\Command;

class ImportScheduleCommand extends Command { protected $signature = 'import:schedule'; protected $description = 'Imports the php[world] schedule from the conference website.';

public function handle() { // Manually scraped from the site by viewing the AJAX requests for each day $timestamps = [1479081600, 1479168000, 1479254400, 1479340800, 1479427200];

foreach ($timestamps as $timestamp) { $this->importSchedule($timestamp); } }

private function importSchedule($timestamp) { $client = new Client([ 'base_uri' => '', 'timeout' => 10, ]);

$response = $client->post('https://world.phparch.com/wp-admin/admin-ajax.php', [ 'form_params' => [ 'action' => 'get_schedule', 'data-timestamp' => $timestamp, 'data-location' => 0, 'data-track' => 0, 'data-page' => 1, 'data-max-items' => 100, ], 'headers' => [ 'Accept' => 'application/javascript', // Pretend the request from the website - sorry Eli! 'X-Requested-With' => 'XMLHttpRequest', 'Origin' => 'https://world.phparch.com', 'Referer' => 'https://world.phparch.com/schedule/', ], ]);

$data = json_decode($response->getBody(), true);

foreach ($data['sessions'] as $session) { $speakers = implode('|', array_map(function($speaker){ return $speaker['post_title']; }, $session['speakers']));

$tracks = implode('|', array_map(function($track){ return $track['name']; }, $session['tracks']));

$start = Carbon::parse($session['date'].' '.$session['time'], 'America/New_York'); $end = Carbon::parse($session['date'].' '.$session['end_time'], 'America/New_York');

DB::insert('insert into sessions (start, `end`, room, title, abstract, speakers, tracks) values (?, ?, ?, ?, ?, ?, ?)', [ $start->getTimestamp(), $end->getTimestamp(), $session['location'], $session['post_title'], $session['post_excerpt'], $speakers, $tracks, ]); } }}

@colinodell - joind.in/talk/18676

<?php

namespace App\Http\Controllers;

use Carbon\Carbon;use Develpr\AlexaApp\Facades\Alexa;use Develpr\AlexaApp\Request\AlexaRequest;use Illuminate\Database\Query\Builder;use Illuminate\Support\Facades\DB;

class SessionController extends Controller{ public function getSessions(AlexaRequest $request) { $request->getIntent(); $time = $request->slot('time') ?: Carbon::now()->format('G:i'); $day = $request->slot('day') ?: Carbon::now()->format('Y-m-d'); $room = $request->slot('room');

try { $sessions = $this->findSessions($time, $day, $room);

if (count($sessions) === 0) { // No sessions found return Alexa::say('Sorry, I couldn\'t find any sessions on ' . $day . ' at ' . $time)->endSession(); } elseif (count($sessions) === 1) { return Alexa::say($this->getSessionText(reset($sessions)))->endSession();; } else { $text = 'I found ' . count($sessions) . ' sessions. '; foreach ($sessions as $session) { $text .= $this->getSessionText($session) . ' '; }

return Alexa::say($text)->endSession(); } } catch (\Exception $ex) { return Alexa::say('Sorry, I\'m having trouble searching for sessions right now.')->endSession();; } }

/** * @param string|null $time * @param string|null $day * @param string|null $room * * @return array * * @todo: Refactor into a service */ private function findSessions($time, $day, $room) { if (empty($day)) { $day = Carbon::now()->format('Y-m-d'); }

if (empty($time)) { $time = Carbon::now()->format('H:i'); } elseif (strlen($time) === 2) { // We can get a time indicator for certain utterances like "morning", // so we'll need to convert those to a certain time for our purposes // switch ($time) { case 'MO': $time = '08:00'; break; case 'AF': $time = '12:30'; break; case 'EV': $time = ''; break; case 'NI': $time = ''; break; } }

$timestamp = Carbon::parse($day.' '.$time, 'America/New_York')->getTimestamp();

/** @var Builder $query */ $query = DB::table('sessions'); $query->where('start', '<=', $timestamp); $query->where('end', '>', $timestamp);

if (!empty($room)) { $query->where('room', '=', $room); }

$query->orderBy('start');

return $query->get(); }

/** * @param \stdClass $session * * @return string */ private function getSessionText($session) { $speakers = implode(' and ', explode('|', $session->speakers));

$time = Carbon::createFromTimestamp($session->start, 'America/New_York')->format('g:i');

if (empty($speakers)) { return sprintf('%s will be in %s at %s', $session->title, $session->room, $time); }

return sprintf('%s will be giving a talk entitled %s in %s at %s.', $speakers, $session->title, $session->room, $time); }}

@colinodell - joind.in/talk/18676

<?php

// app/Http/routes.php

AlexaRoute::intent('/alexa', 'GetSessions', 'App\Http\Controllers\SessionController@getSessions');

@colinodell - joind.in/talk/18676

Recap• Alexa parses voice request; passes to PHP

• Laravel application handles requests; returns response

• Alexa renders responses as voice and/or cards

Other uses for Alexa:

• Querying for information

• Interactive sessions (online ordering, games, etc.)

• Home automation

Packagist Download CounterDisplaying downloads counts from the Packagist API with Particle Photon

@colinodell - joind.in/talk/18676

Particle Photon• WiFi built in• Small footprint• Can be powered via MicroUSB• Enough digital GPIO pins

• Programmable via web-based IDE• OTA flashing• Built-in webhook support

@colinodell - joind.in/talk/18676

Hardware• Particle Photon• MAX7219 8-Digit Red LED Display Module• Micro USB cable (power)• Wires

@colinodell - joind.in/talk/18676

MAX7219 8-Digit Red LED Display Module

Images from https://learn.adafruit.com/pir-passive-infrared-proximity-motion-sensor/overview

@colinodell - joind.in/talk/18676

Wiring

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Problem!• JSON document is 32.5 KB

• Photon webhook responses are 512-byte chunks spaced 250ms apart

@colinodell - joind.in/talk/18676

Problem!• JSON document is 32.5 KB

• Photon webhook responses are 512-byte chunks spaced 250ms apart

Solution!• Use Yahoo YQL to reduce response size

@colinodell - joind.in/talk/18676

Problem!• JSON document is 32.5 KB

• Photon webhook responses are 512-byte chunks spaced 250ms apart

Solution!• Use Yahoo YQL to reduce response size

• Use PHP to parse JSON, return just one number

@colinodell - joind.in/talk/18676

packagist-counter.php<?php

const URL = 'https://packagist.org/packages/league/commonmark.json';

$json = json_decode(file_get_contents(URL), true);header('Content-Type: text/plain');echo $json['package']['downloads']['total'];

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

@colinodell - joind.in/talk/18676

Recap• Particle Photon + 8 digit LED display

• Particle webhook fetches data from packagist-counter.php

• packagist-counter.php fetches from packagist.org; trims out the fat

@colinodell - joind.in/talk/18676

Summary

@colinodell - joind.in/talk/18676

Summary• IoT is everywhere!

• PHP can bridge the gap

• Run PHP: On the device (RasPi examples) In “the cloud” (Alexa; Particle Photon) Or both!

• Anyone can build web-connected devices

@colinodell - joind.in/talk/18676

Questions?

@colinodell - joind.in/talk/18676

Thanks!

Slides/Feedback:http://joind.in/talk/18676

Project Source Code / Documentation:https://github.com/colinodell/php-iot-examples

Recommended