PHP and Zephir and Pi, oh my!

Andrew Collington

http://blog.amnuts.com/
@acollington

The Raspberry Pi

an awesome little computer

The Raspberry Pi board

What is GPIO?

Using GPIO on the RPi

What about a PHP extension?

Sure! But we'll have to make one.

But isn't that hard?

What is Zephir?

Let's make an extension!

Install Zephir

$ sudo apt-get update
$ sudo apt-get install gcc make re2c php5 php5-json php5-dev libpcre3-dev

$ git clone https://github.com/phalcon/zephir
$ cd zephir && ./install -c

Install wiringPi

$ git clone git://git.drogon.net/wiringPi
$ cd wiringPi
$ ./build

Setting up the extension

$ zephir init phpiwire

 

Creates the directory structure

config.json
ext/
phpiwire/

 

Files have .zep extension
Filename typically matches class name

Config example

{
    ...
    "optimizations": {
        "static-type-inference": true,
        "static-type-inference-second-pass": true,
        "local-context-pass": true,
        "constant-folding": true,
        "static-constant-class-folding": true,
        "call-gatherer-pass": true,
        "check-invalid-reads": false
    },
    "namespace": "phpiwire",
    "name": "PHPiWire",
    "description": "Control the RaspberryPi GPIO via PHP (with wiringPi)",
    "author": "Andrew Collington (andy@amnuts.com)",
    "version": "0.2.0",
    "verbose": false,
    "requires": {
        "extensions": []
    },
    "extra-libs": "-lwiringPi -pthreads"
}

A simple Zephir example

namespace App;

class MyClass
{
  protected value;
  
  public function getSomeData(a, const uint! val = 20) -> string | bool
  {
    let this->value = val;
    
    if a == false {
      return false;
    }
    
    return "error";
  }
}

Setting up the classes relies heavily on the C code insertion

namespace Phpiwire;

%{
#include <wiringPi.h>
}%

class Board
{
  public function pwmRange(uint! range = 1024)
  {
    %{ pwmSetRange(range); }%
    return this;
  }
}
#ifdef HAVE_CONFIG_H
#include "../ext_config.h"
#endif
#include 
#include "../php_ext.h"
#include "../ext.h"
#include <Zend/zend_operators.h>
#include <Zend/zend_exceptions.h>
#include <Zend/zend_interfaces.h>
#include "kernel/main.h"
#include "kernel/fcall.h"
#include "kernel/memory.h"
#include "kernel/array.h"
#include "kernel/object.h"
#include "ext/spl/spl_exceptions.h"
#include "kernel/exception.h"
#include "kernel/concat.h"
#include "kernel/operators.h"
#include <wiringPi.h>

ZEPHIR_INIT_CLASS(Phpiwire_Board) {
    ZEPHIR_REGISTER_CLASS(Phpiwire, Board, phpiwire, board, phpiwire_board_method_entry, 0);
    phpiwire_board_ce->create_object = zephir_init_properties_Phpiwire_Board;
    return SUCCESS;
}

PHP_METHOD(Phpiwire_Board, pwmRange) {
    zval *range_param = NULL;
    unsigned int range;
    
    zephir_fetch_params(0, 0, 1, &range_param);
    if (!range_param) {
        range = 1024;
    } else {
        if (unlikely(Z_TYPE_P(range_param) != IS_LONG)) {
            zephir_throw_exception_string(spl_ce_InvalidArgumentException, SL("Parameter 'range' must be a uint") TSRMLS_CC);
            RETURN_NULL();
        }
        range = Z_LVAL_P(range_param);
    }
    pwmSetRange(range); 
    RETURN_THISW();
}

Zephir & Zend Engine language calls

public function read(int! type = self::DIGITAL) -> long
{
  var value = null;
  
  %{
    zval *pinnum;
    zephir_read_property_this(&pinnum, this_ptr, SL("id"), PH_NOISY_CC);
    int pin = Z_LVAL_P(pinnum);
  }%
  
  switch type {
    case self::DIGITAL:
      %{ ZVAL_LONG(value, digitalRead(pin)); }%
      break;
    case self::ANALOG:
      %{ ZVAL_LONG(value, analogRead(pin)); }%
      break;
  }
  
  return value;
}

The easy way

Just download from GitHub!

$ git clone https://github.com/amnuts/phpiwire

Now build

$ cd phpiwire
$ zephir build

Add compiled extension to php.ini

extension=phpiwire.so

Ta da!

All done, but how do we use it?

Get info about the board

<?php

namespace Phpiwire;

$pi = new Board();

echo $pi, "\n";
var_dump($pi->version());

Let's (finally) blink that LED

<?php

namespace Phpiwire;

$p = (new Board())->getPin(0)->mode(Pin::OUTPUT);
while (true) {
    $p->write(Pin::HIGH);
    sleep(1);
    $p->write(Pin::LOW);
    sleep(1);
}

What about an RGB LED?

$sleep = 20000;
$rgb = [100, 0, 0];

while (true) {
    for ($dec = 0; $dec < 3; $dec += 1) {
        $inc = $dec == 2 ? 0 : $dec + 1;
        for ($i = 0; $i < 100; $i += 1) {
            $rgb[$dec] -= 1;
            $rgb[$inc] += 1;
            writePins($rgb);
            usleep($sleep);
        }
    }
}

function writePins(array $rgb)
{
    static $pi, $r, $g, $b;
    if ($pi === null) {
        $pi = new Board();
        $r = $pi->getPin(0)->mode(Pin::SOFT_PWM_OUT)->softPwmWrite(100);
        $g = $pi->getPin(1)->mode(Pin::SOFT_PWM_OUT);
        $b = $pi->getPin(2)->mode(Pin::SOFT_PWM_OUT);
    }
    $r->softPwmWrite($rgb[0]);
    $g->softPwmWrite($rgb[1]);
    $b->softPwmWrite($rgb[2]);
}

More examples in the GitHub repo

Conclusion

Resources and info

Zephir
http://zephir-lang.com/

wiringPi
http://wiringpi.com/

phpiwire
https://github.com/amnuts/phpiwire

Thanks! :-)