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

June 09, 2016
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
    developer at teamleader

    View Slide

  3. Stijn Vannieuwenhuyse
    track and field timer

    View Slide

  4. Track & Field
    its 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
    its all about precise numbers

    View Slide

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

    View Slide

  8. Problem 1
    How to integrate?

    View Slide

  9. FT01
    Long jump men



    Lambert Romain
    DAMP


    A6

    Rank: 1

    View Slide

  10. RS232

    View Slide

  11. Problem 2
    How to connect?

    View Slide

  12. PHP
    developer

    View Slide

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

    View Slide

  14. 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);
    }
    }
    ^ infinite loop
    for as long as their is stuff to do
    ^ callbacks
    queued
    ^ ticks
    - next : execute in same loop
    - future execute in next loop

    View Slide

  15. composer require react/socket

    View Slide

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

    View Slide

  17. composer require react/socket-client

    View Slide

  18. Problem 3
    How to communicate?

    View Slide

  19. C\x0d

    View Slide

  20. TFF000000a2000700100030a012345678910\x0d

    View Slide

  21. TRRGGBBXXXXYYYYLLLLHHHHFDDDDDDDDDD\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

  22. Domain Driven Design
    make the implicit explicit

    View Slide

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

    View Slide

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

    View Slide

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

  26. B00100028004700af0f0f0f0fff0fff0f\x0d

    View Slide

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

  28. Domain Specific Language

    View Slide

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

  30. Value Objects

    View Slide

  31. new Bitmap()
    new BitmapBlock()
    new BitmapLine()

    View Slide

  32. Dot Matrix
    Fonts

    View Slide

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

  34. Problem 4
    How to get updates?

    View Slide

  35. inotify

    View Slide

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

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

  38. Problem 5
    How to combine I/O?

    View Slide

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

    View Slide

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

  41. Problem 6
    How to send own layouts?

    View Slide

  42. PHP TCP
    server

    View Slide

  43. $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

  44. Command
    pattern

    View Slide

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

    View Slide

  46. $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

  47. Problem 7
    How to open up control?

    View Slide

  48. $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

  49. Problem 8
    How to get current state?

    View Slide

  50. websockets

    View Slide

  51. composer require cboden/ratchet

    View Slide

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

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

    View Slide

  54. var connection = null;
    var connect = function() {
    connection = new WebSocket('ws://192.168.6.10: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

  55. Problem 9
    How to automate?

    View Slide

  56. {
    "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

  57. timers

    View Slide

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

    View Slide

  59. it works
    for now

    View Slide

  60. my experiences

    View Slide

  61. async programming in PHP
    is possible

    View Slide

  62. php is stable enough
    for long running processes

    View Slide

  63. reactphp is a
    mature and rich library

    View Slide

  64. I use reactphp
    in production

    View Slide

  65. I shouldn't
    use php for this

    View Slide

  66. Questions?

    View Slide

  67. Stijn Vannieuwenhuyse
    @stijnvnh

    View Slide