Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How I (ab)used PHP with the help of ReactPHP

How I (ab)used PHP with the help of ReactPHP

PHP is one of the most accessible and commonly used languages in computer programming. It’s used for building websites, web applications, micro services and loads of other stuff. Normally we would not use it to control hardware. But let that be the showcase of this talk. I’ll show you how I (ab)used PHP to control hardware used during track and field competitions. It involves LED-panels, serial-to-ethernet-­convertors and the use of reactphp, an event-driven non-blocking I/O library.

Stijn Vannieuwenhuyse

January 27, 2017
Tweet

More Decks by Stijn Vannieuwenhuyse

Other Decks in Programming

Transcript

  1. how I (ab)used PHP
    with the help of reactphp

    View Slide

  2. Stijn Vannieuwenhuyse
    head of engineering at teamleader

    View Slide

  3. Stijn Vannieuwenhuyse
    track and field timer

    View Slide

  4. Track & Field
    it's all about precise numbers

    View Slide

  5. Team Arctic v.z.w.
    team for athletics' results communication, timing, information and coordination

    View Slide

  6. Track & Field
    it's all about precise numbers

    View Slide

  7. View Slide

  8. What's wrong with it?
    Why change?

    View Slide

  9. Problem 1
    How to integrate?

    View Slide

  10. FT01
    Long jump men



    Lambert Romain
    DAMP


    A6

    Rank: 1

    View Slide

  11. RS232

    View Slide

  12. View Slide

  13. Problem 2
    How to connect?

    View Slide

  14. PHP
    developer

    View Slide

  15. reactphp
    event-driven, non-blocking I/O with PHP

    View Slide

  16. public function run()
    {
    $this->running = true;
    while ($this->running) {
    $this->nextTickQueue->tick();
    $this->futureTickQueue->tick();
    $this->timers->tick();
    if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
    $timeout = 0;
    } elseif ($scheduledAt = $this->timers->getFirst()) {
    $timeout = $scheduledAt - $this->timers->getTime();
    if ($timeout < 0) {
    $timeout = 0;
    } else {
    $timeout *= self::MICROSECONDS_PER_SECOND;
    }
    } elseif ($this->readStreams || $this->writeStreams) {
    $timeout = null;
    } else {
    break;
    }
    $this->waitForStreamActivity($timeout);
    }
    }

    View Slide

  17. composer require react/react

    View Slide

  18. Problem 2
    How to connect?

    View Slide

  19. $loop = LoopFactory::create();
    $connector = new TcpConnector($loop);
    $connector->create('192.168.1.93', 6000)->then(
    function(Stream $stream) {
    echo "connected", PHP_EOL;
    }
    );
    $loop->run();

    View Slide

  20. composer require react/socket-client

    View Slide

  21. Problem 3
    How to communicate?

    View Slide

  22. C\x0d

    View Slide

  23. TFF000000a2000700100030a012345678910\x0d

    View Slide

  24. TFF000000a2000700100030a012345678910\x0d
    TRRGGBBXXXXYYYYLLLLHHHHFDDDDDDDDDDDD\x0d
    T: command name
    RRGGBB: hex color value
    XXXX: x-position in hex, starting from left
    YYYY: y-position in hex, starting from top
    LLLL: length of the text in hex
    HHHH: heigth of the text in hex
    F: font-type
    D: data
    \x0d: end marker

    View Slide

  25. Domain Driven Design
    make the implicit explicit

    View Slide

  26. interface Instruction
    {
    /**
    * @return string
    */
    public function toData();
    }

    View Slide

  27. class Clear implements Instruction
    {
    public function toData()
    {
    return "C\x0d";
    }
    }

    View Slide

  28. class Text implements Instruction
    {
    private $data;
    private $x;
    private $y;
    private $height;
    private $length;
    private $colorCode;
    private $font;
    public function __construct($data, $x, $y, $height, $length, $colorCode, $font)
    {
    $this->data = $data;
    $this->x = $x;
    $this->y = $y;
    $this->height = $height;
    $this->length = $length;
    $this->colorCode = $colorCode;
    $this->font = $font;
    }
    public function toData()
    {
    return sprintf(
    "T%s%04X%04X%04X%04X%s%s\x0d",
    $this->getColor(), //returns hex string of red, green or yellow
    $this->x,
    $this->y,
    $this->length,
    $this->height,
    $this->font,
    $this->data
    );
    }
    }

    View Slide

  29. B00100028004700af0f0f0f0fff0fff0f\x0d

    View Slide

  30. BXXXXYYYYLLLLHHHHDDDDDDDDDDD\x0d
    B: command name
    XXXX: x-position in hex, starting from left
    YYYY: y-position in hex, starting from top
    LLLL: length of the text in hex
    HHHH: heigth of the text in hex
    D: data
    \x0d: end marker

    View Slide

  31. Domain Specific Language

    View Slide

  32. . . . . . . . . . . . Y Y Y Y Y Y Y . . . . . . . . . . . . . . . . . . . Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
    R R R R R R R R R R R Y . . . . . Y G G G G G G G G G G G G G G G G G G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    . . . . . R . . . . . Y . Y Y Y . Y . . . . . G . . . . . G . . . . . G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    . . R . . R . R R R . Y . . . Y . Y . G . G . G . G G G . G . G G G . G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    . . R . . R . . . R . Y . Y Y Y . Y . G . G . G . G . . . G . G . . . G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    . . R . . R . R R R . Y . . . Y . Y . G G G . G . G G G . G . G G G . G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    . . R . . R . R . . . Y . Y Y Y . Y . . . G . G . . . G . G . G . G . G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    . . R . . R . R R R . Y . . . . . Y . . . G . G . G G G . G . G G G . G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    . . . . . R . . . . . Y . . . . . Y . . . . . G . . . . . G . . . . . G . Y . . . . . . . . . . . . . . . . . . . . . . . . . .
    Y Y Y Y Y Y Y Y Y Y Y Y . . . . . Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y . . . . . . . . . . . . . . . . . . . . . . . . . .

    View Slide

  33. Value Objects

    View Slide

  34. new Bitmap()
    new BitmapBlock()
    new BitmapLine()

    View Slide

  35. Dot Matrix
    Fonts

    View Slide

  36. final class AdvancedDotDigital extends Font
    {
    protected static $chars = [
    "0" => "01111110\n11111111\n11000011\n11000011\n11000011\n11000011\n11000011\n11000011\n11000011\n11000011\n11111111\n01111110",
    "1" => "00011000\n00111000\n01111000\n11011000\n00011000\n00011000\n00011000\n00011000\n00011000\n00011000\n11111111\n11111111",
    "2" => "01111110\n11111111\n11000011\n00000011\n00000011\n01111111\n11111110\n11000000\n11000000\n11000000\n11111111\n11111111",
    "3" => "01111110\n11111111\n11000011\n00000011\n00000011\n00111110\n00111110\n00000011\n00000011\n11000011\n11111111\n01111110",
    "4" => "11000011\n11000011\n11000011\n11000011\n11000011\n11000011\n11111111\n11111111\n00000011\n00000011\n00000011\n00000011",
    "5" => "11111111\n11111111\n11000000\n11000000\n11111110\n11111111\n00000011\n00000011\n00000011\n11000011\n11111111\n01111110",
    "6" => "01111110\n11111111\n11000011\n11000000\n11111110\n11111111\n11000011\n11000011\n11000011\n11000011\n11111111\n01111110",
    "7" => "11111111\n11111111\n11000110\n00000110\n00001100\n00001100\n00011000\n00011000\n00110000\n00110000\n00110000\n00110000",
    "8" => "01111110\n11111111\n11000011\n11000011\n11000011\n01111110\n01111110\n11000011\n11000011\n11000011\n11111111\n01111110",
    "9" => "01111110\n11111111\n11000011\n11000011\n11000011\n11000011\n11111111\n01111111\n00000011\n11000011\n11111111\n01111110",
    "m" => "0000000000\n0000000000\n0000000000\n0000000000\n0000000000\n1111101110\n1111111111\n1100110011\n1100110011\n1100110011\n1100110011\n1100110011\n1100110011",
    "X" => "1100000000011\n0110000000110\n0011000001100\n0001100011000\n0000110110000\n0000011100000\n0000011100000\n0000110110000\n0001100011000\n0011000001100\n0110000000110\n1100000000011",
    "O" => "0000111110000\n0011111111100\n0111000001110\n0110000000110\n1100000000011\n1100000000011\n1100000000011\n1100000000011\n0110000000110\n0111000001110\n0011111111100\n0000111110000",
    ":" => "0000\n0000\n0110\n0110\n0000\n0000\n0000\n0000\n0110\n0110\n0000\n0000",
    "-" => "0000000000000\n0000000000000\n0000000000000\n0000000000000\n0000000000000\n0011111111100\n0011111111100\n0000000000000\n0000000000000\n0000000000000\n0000000000000\n0000000000000",
    ];
    }

    View Slide

  37. Problem 4
    How to get updates?

    View Slide

  38. inotify

    View Slide

  39. inotify_init() : filedescriptor
    inotify_add_watch($fd, $path, $mask) : watchDescriptorId
    inotify_rm_watch($fd, $wdId)
    inotify_read($fd) : event[]
    IN_MODIFY
    IN_CREATE
    IN_ALL_EVENTS
    IN_OPEN
    ...

    View Slide

  40. class Watcher
    {
    public function __construct($dir, LoopInterface $loop)
    {
    $fd = inotify_init();
    stream_set_blocking(fd, 0);
    inotify_add_watch($fd, $dir, IN_MODIFY);
    $loop->addReadStream($fd,
    function($fd) {
    foreach(inotify_read($fd) as $events) {
    //process change
    }
    }
    )
    }
    }

    View Slide

  41. Problem 5
    How to combine I/O?

    View Slide

  42. interface Layout
    {
    /**
    * @return Instruction[]
    */
    public function toInstructions();
    }

    View Slide

  43. class NextAthlete implements Layout
    {
    private $name;
    private $team;
    private $attempt;
    private $place = null;
    private $height = null;
    public function __construct($name, $team, $attempt, $place = null, $height = null)
    {
    $this->name = $name;
    $this->team = $team;
    $this->attempt = $attempt;
    $this->place = $place;
    $this->height = $height;
    }
    /**
    * @return Instruction[]
    */
    public function toInstructions()
    {
    $instructions = [];
    $name = (new Text(strtoupper($this->name), RingMatrix::class, "Y"))->toBitmap();
    $instructions[] = new Bitmap(0, 0, $name);
    $team = (new Text(strtoupper($this->team), SquareDotDigital::class, "R"))->toBitmap();
    $instructions[] = new Bitmap(64 - $team->getLength(), 11, $team);
    //some other conversion from data to instructions
    return $instructions;
    }
    }

    View Slide

  44. Problem 6
    How to send own layouts?

    View Slide

  45. PHP TCP
    server

    View Slide

  46. $loop = Factory::create();
    $server = new Server($loop);
    $server->on('connection', function (Connection $connection) {
    echo sprintf("%s came online\n", $connection->getRemoteAddress());
    $connection->on('close', function(Stream $stream) use ($connection) {
    echo sprintf("%s went offline\n", $connection->getRemoteAddress());
    });
    });
    $server->listen('6000', '0.0.0.0');
    $loop->run();

    View Slide

  47. Command
    pattern

    View Slide

  48. {
    "commandName":"Nortuni.FieldScoreboardManager.Commands.ShowLayout",
    "payload": {
    "scoreBoardId": "LED01",
    "layoutName": "TwoLines",
    "data": {
    "line1": "TEAM",
    "line2": "ARCTIC"
    }
    }
    }

    View Slide

  49. $loop = Factory::create();
    $deviceServer = new Server($loop);
    $commandServer = new Server($loop);
    $deviceServer->on('connection', function (Connection $connection) {
    //do some stuff
    });
    $commandServer->on('connection', function (Connection $connection) {
    $connection->on('data', function() {
    //parse json to command
    //dispatch command
    })
    });
    $deviceServer->listen('6000', '0.0.0.0');
    $commandServer->listen('6001', '0.0.0.0');
    $loop->run();

    View Slide

  50. Problem 7
    How to open up control?

    View Slide

  51. $socketServer = new Server($loop);
    $webserver = new React\Http\Server($socketServer);
    $webserver->on('request', function ($request, $response) {
    //just like a normal controller method
    //$response is a writable stream
    });
    $socket->listen(8080);

    View Slide

  52. Problem 8
    How to get current state?

    View Slide

  53. websockets

    View Slide

  54. composer require cboden/ratchet

    View Slide

  55. class WebSocketConnector implements MessageComponentInterface
    {
    private $clients;
    public function __construct() {
    $this->clients = new SplObjectStorage;
    }
    public function onOpen(ConnectionInterface $connection)
    {
    $this->clients->attach($conn);
    }
    public function onClose(ConnectionInterface $connection)
    {
    $this->clients->detach($connection);
    }
    public function onError(ConnectionInterface $connection, \Exception $e)
    {
    $connection->close();
    }
    public function onMessage(ConnectionInterface $from, $msg)
    {
    //process data
    }
    public function sendToClients($data)
    {
    foreach($this->clients as $client) {
    $client->send($data);
    }
    }
    }

    View Slide

  56. $socketServer = new Ratchet\App('192.168.15.100', 8080, '0.0.0.0', $loop);
    $socketServer->route('/field', new WebSocketConnector(), ['*']);

    View Slide

  57. var connection = null;
    var connect = function() {
    connection = new WebSocket('ws://192.168.15.100:8080/field');
    connection.onopen = function() {
    //log or ...
    };
    connection.onmessage = function(message) {
    //process message.data
    }
    connection.onclose = function() {
    connection = null;
    setTimeout(connect, 1000);
    };
    }
    var dispatch = function(data) {
    if(connection && connection.readyState == WebSocket.OPEN) {
    connection.send(data);
    }
    };

    View Slide

  58. Problem 9
    How to automate?

    View Slide

  59. {
    "commandName":"Nortuni.FieldScoreboardManager.Commands.ShowSlideshow",
    "payload": {
    "scoreBoardId":"LED01",
    "interval":"20",
    "screens":[
    {
    "layoutName":"MeetingName",
    "data": {
    "line1":"PHPGENT",
    "line2":"MEETUP",
    "line3":"",
    "line4":"WELCOME"
    }
    },
    {
    "layoutName":"MeetingName",
    "data": {
    "line1":"",
    "line2":"TEAMLEADER",
    "line3":"CRM",
    "line4":""
    }
    }
    ]
    }
    }

    View Slide

  60. timers

    View Slide

  61. $this->timer = $this->loop->addPeriodicTimer($interval, function() {
    $this->showNextSlide();
    });

    View Slide

  62. it works
    for now

    View Slide

  63. my experiences

    View Slide

  64. async programming in PHP
    is possible

    View Slide

  65. php is stable enough
    for long running processes

    View Slide

  66. reactphp is a
    mature and rich library

    View Slide

  67. I use reactphp
    in production

    View Slide

  68. I shouldn't
    use php for this

    View Slide

  69. Questions?

    View Slide

  70. sponsorship
    [email protected]

    View Slide

  71. Stijn Vannieuwenhuyse
    @stijnvnh
    https://joind.in/talk/822f4

    View Slide