Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Stijn Vannieuwenhuyse head of engineering at teamleader

Slide 3

Slide 3 text

Stijn Vannieuwenhuyse track and field timer

Slide 4

Slide 4 text

Track & Field it's all about precise numbers

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Track & Field it's all about precise numbers

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

What's wrong with it? Why change?

Slide 9

Slide 9 text

Problem 1 How to integrate?

Slide 10

Slide 10 text

FT01 Long jump men Lambert Romain DAMP A6 Rank: 1

Slide 11

Slide 11 text

RS232

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Problem 2 How to connect?

Slide 14

Slide 14 text

PHP developer

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

composer require react/react

Slide 18

Slide 18 text

Problem 2 How to connect?

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

composer require react/socket-client

Slide 21

Slide 21 text

Problem 3 How to communicate?

Slide 22

Slide 22 text

C\x0d

Slide 23

Slide 23 text

TFF000000a2000700100030a012345678910\x0d

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Domain Driven Design make the implicit explicit

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

B00100028004700af0f0f0f0fff0fff0f\x0d

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Domain Specific Language

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Value Objects

Slide 34

Slide 34 text

new Bitmap() new BitmapBlock() new BitmapLine()

Slide 35

Slide 35 text

Dot Matrix Fonts

Slide 36

Slide 36 text

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", ]; }

Slide 37

Slide 37 text

Problem 4 How to get updates?

Slide 38

Slide 38 text

inotify

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Problem 5 How to combine I/O?

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Problem 6 How to send own layouts?

Slide 45

Slide 45 text

PHP TCP server

Slide 46

Slide 46 text

$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();

Slide 47

Slide 47 text

Command pattern

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

$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();

Slide 50

Slide 50 text

Problem 7 How to open up control?

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Problem 8 How to get current state?

Slide 53

Slide 53 text

websockets

Slide 54

Slide 54 text

composer require cboden/ratchet

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Problem 9 How to automate?

Slide 59

Slide 59 text

{ "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":"" } } ] } }

Slide 60

Slide 60 text

timers

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

it works for now

Slide 63

Slide 63 text

my experiences

Slide 64

Slide 64 text

async programming in PHP is possible

Slide 65

Slide 65 text

php is stable enough for long running processes

Slide 66

Slide 66 text

reactphp is a mature and rich library

Slide 67

Slide 67 text

I use reactphp in production

Slide 68

Slide 68 text

I shouldn't use php for this

Slide 69

Slide 69 text

Questions?

Slide 70

Slide 70 text

sponsorship [email protected]

Slide 71

Slide 71 text

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