PHP + Raspberry Pi - a hora da verdade

Imagine um mundo onde você pode usar uma linguagem web para manipular os pinos do Raspberry Pi. Não precisa imaginar, esse mundo existe com a recém criada classe Pin.class.php.

E o programador disse: e façamos o código à imagem de nossas ideias!

Como tratei no post anterior, dá pra manipular os arquivos correspondentes ao pinos do RPi usando sysfs. Aquele foi o primeiro passo para migrar do Python para o PHP. Agora, entendendo como funciona essa manipulação, basta criar algumas classes para manipular os pinos e beleza, tudo fica lindo.

Classes de interesse

Como tudo vai rolar num browser, então pode ser interessante criar classes para manipular o HTML, mas isso não é essencial. Para fornecer o acesso básico ao pino, criarei a classe Pin.class.php com o seguinte conteúdo:

<?php
    /**
     * Pin.class.php
     * Manipula um pino no Raspberry Pi
     * by Leonardo Costa
     * address http://electronicxp.blogspot.com

     * versão: 0.1
     */
    class Pin {
        /**
         * Número do pino
         */
        private $number;
       
        /**
         * Modo. Poder ser 'in' ou 'out'. O padrão é 'out'
         */
        private $mode = 'out';
       
        /**
         * Lista com os arquivos providos por sysfs
         */
        protected $pinFiles = array('active', 'direction', 'edge', 'uevent', 'value');
       
        /**
         * Caminho padrão dos pinos
         */
        protected $globalPinAddress = '/sys/class/gpio/';
       
        /**
         * Construtor
         * @param $number Recebe o número do pino no formato GPIO
         * @param $mode Opcional. Determina o modo do pino: 'in' | 'out'. O padrão é 'out'
         * @param $value Opcional. Valor inicial do pino. O padrão é "0" (zero)
         */
        public function __construct($number, $mode = 'out', $value = '0') {
            self::pinStart($number);
            self::pinMode($mode);
            self::pinValue($value);
        }
       
        /**
         * Inicializa o pino
         * @param $number Número do pino
         */
        private function pinStart($number) {
            $this->number = $number;
            if(!self::checkPin()) {
                self::pinOperation('export');
                usleep(145000); //Aguarda até que os arquivos sejam criados
                //Sem usleep, o script corria antes dos arquivos serem todos criados,
                //fazendo com que erros de acesso fossem disparados
            }
        }
       
        /**
         * Termina a manipulação do pino e apaga os arquivos
         */
        public function pinClose() {
            if(self::checkPin()) {
                self::pinOperation('unexport');
            }
        }
       
        /**
         * Trata a criação e extinção do pino
         * @param $operation Informa a operação: export|unexport
         */
        private function pinOperation($operation) {
            if(in_array($operation, array('export', 'unexport'))) {
                $f = fopen("{$this->globalPinAddress}{$operation}", 'a');
                fwrite($f, $this->number);
                fclose($f);
            } else {
                trigger_error("Pin operation \"{$operation}\" is not valid.");
            }
        }
       
        /**
         * Escreve dados em determinaos arquivos
         * @param $file Arquivo a ser alterado
         * @param $value Valor que será gravado
         */
        private function dataWrite($file, $value) {
            if(in_array($file, $this->pinFiles)) {
                $f = fopen("{$this->globalPinAddress}gpio{$this->number}/{$file}", 'a');
                fwrite($f, $value);
                fclose($f);
            } else {
                trigger_error("File \"{$file}\" not a valid file pin");
            }
        }
       
        /**
         * Lê os dados atuais
         */
        public function pinRead() {
            return file_get_contents("{$this->globalPinAddress}gpio{$this->number}/value");
        }
       
        /**
         * Determina o modo do pino (direção).
         * @param $mode Pode ser 'in' ou 'out'
         */
        public function pinMode($mode) {
            if(self::checkPin()) {
                if(in_array($mode, array('in', 'out'))) {
                    self::dataWrite('direction', $mode);
                } else {
                    trigger_error("Mode \"{$mode}\" not allowed.");
                }
            }
        }
       
        /**
         * Escreve um valor no pino
         * @param $value Valor a ser escrito
         */
        public function pinValue($value) {
            self::dataWrite('value', $value);
        }
       
        /**
         * Verifica se o arquivo do valor do pino existe.
         */
        private function checkPin() {
            if(file_exists($this->globalPinAddress."gpio{$this->number}/value")) {
                return true;
            }
            return false;
        }

        /**
         * Retorna informações sobre o pino
         */
        public function __toString() {
            $value = self::pinRead();
            $info = "GPIO{$this->number} {$this->mode} {$value}";
            return $info;
        }
    }


Essa classe ainda ainda não permite a manipulação de outros arquivos que não sejam "direction" ou "value", mas prevê o uso deles. Digamos que essa então é a versão 0.1 dela.

Partindo dessa classe, vou escrever uma outra que vai manipular um LED comum. Ela terá os métodos on() e off() que vai ligar e desligar o LED. Além disso a classe vai ter o método ledType() que vai receber os valores 'cathode' ou 'anode', para que on() e off() funcionem corretamente.

<?php
    /**
     * Led.class.php
     * Manipula um LED comum

     * by Leonardo C. Costa
     * address: http://electronicxp.blogspot.com
     */
    class Led extends Pin {
        /**
         * Determina se o LED é do tipo 'cátodo' ou 'ânodo' comum
         */
        private $cathode;
       
        /**
         * Associa o pino
         */
        public function __construct($pin) {
            parent::__construct($pin);
        }
       
        /**
         * Determina o tipo de terminal
         * @param $type Tipo. Pode ser 'cathode' ou 'anode'
         */
        public function setType($type) {
            if(in_array($type, array('cathode', 'anode'))) {
                $this->cathode = ($type == 'cathode')?true:false;
            }
        }
       
        /**
         * Liga o LED
         */
        public function on() {
            parent::pinValue(self::reverse(1));
        }
       
        /**
         * Desliga o LED
         */
        public function off() {
            parent::pinValue(self::reverse(0));
        }
       
        /**
         * Altera o tipo do on e do off de acordo com o tipo do LED
         * @param $value Valor a ser gravado
         */
        private function reverse($value) {
            if($this->cathode) {
                return $value;
            }
            return (($value - 1) * (-1));
        }
    }


Nesse ponto já podemos fazer alguma graça. Vou criar um index.php para fazer o LED acender ou apagar:

<?php
    //Carrega classes na medida em que são necessárias
    function __autoload($class) {
        include_once "{$class}.class.php";
    }
   
    $led = new Led(27);
    $led->on();


Salvando esse arquivo e os outros dois na pasta $ /var/www/ do Raspberry Pi já dá pra fazer alguma coisa. Para isso, depois de salvar, acesse através do seu navegador o endereço do seu RPi e veja o que rolou.


O brilho do LED está pequeno pois usei um resistor de 470ohms. Use um de 300ohms para um brilho mediano e um de 220ohms para um brilho bem intenso. Visite esse post aqui se você tiver dúvidas sobre como montar o circuito do LED.

Para desligar o LED, mude $led->on() para $led->off(). Simples e direto.


LED RGB

Agora vou criar uma última classe que vai manipular diretamente um LED RGB. Ela vai receber os pinos para as cores, criar os arquivos, e permitir a escolha das cores através de comandos simples, como $rgb->color('yellow') e coisas do tipo.

<?php
    /**
     * Rgb.class.php
     * Manipula um LED RGB
     * by Leonardo Costa
     * address http://electronicxp.blogspot.com
     */
    class Rgb {
        private $pins = array();
       
        /**
         * Método construtor. Recebe os pinos correspondentes a cada cor
         * @param $r Pino para a cor vermelha
         * @param $g Pino para a cor verde
         * @param $b Pino para a cor azul
         */
        public function __construct($r, $g, $b) {
            self::setPin('red', $r);
            self::setPin('green', $g);
            self::setPin('blue', $b);
        }
       
        /**
         * Determina o tipo de LED RGB
         * @param $type Pode conter os valores 'anode'|'cathode'
         */
        public function setType($type) {
            foreach($this->pins as $pin) {
                $pin->setType($type);
            }
        }
       
        /**
         * Associa a cor ao pino. Se o pino já estiver em uso, desassocia
         * @param $color Nome da cor
         * @param $pin Pino correspondente
         */
        public function setPin($color, $pin) {
            if(isset($this->pins[$color])) {
                $this->pins[$color]->pinClose();
            }
            $this->pins[$color] = new Led($pin);
        }
       
        /**
         * Liga e/ou desliga as cores
         * @param $r|$g|$b Liga (true) ou desliga (false) as cores
         */
        public function pinsOnOff($r = false, $g = false, $b = false) {
            ($r)?$this->pins['red']->on():$this->pins['red']->off();
            ($g)?$this->pins['green']->on():$this->pins['green']->off();
            ($b)?$this->pins['blue']->on():$this->pins['blue']->off();
        }
       
        /**
         * Seleciona uma cor para o LED
         * @param $color Cor escolhida. Apenas cores primárias, secundárias e branco
         */
        public function color($color) {
            switch($color) {
                case 'red':
                    self::pinsOnOff(true, false, false);
                    break;
                case 'green':
                    self::pinsOnOff(false, true, false);
                    break;
                case 'blue':
                    self::pinsOnOff(false, false, true);
                    break;
                case 'yellow':
                    self::pinsOnOff(true, true, false);
                    break;
                case 'cian':
                    self::pinsOnOff(false, true, true);
                    break;
                case 'magenta':
                    self::pinsOnOff(true, false, true);
                    break;
                case 'white':
                    self::pinsOnOff(true, true, true);
                    break;
                default:
                    self::allOff();
                    throw new Exception("Color {$color} not allowed.");
            }
        }
       
        /**
         * Desliga todas as cores
         */
        public function allOff() {
            foreach($this->pins as $pin) {
                $pin->off();
            }
        }
    }


Para ter certeza de que tudo está funcionando, esse será o novo conteúdo do index.php

function __autoload($class) {
        include_once "widgets/{$class}.class.php";
    }
   
    $colors = array('blue', 'green', 'red', 'yellow', 'cian', 'magenta', 'white');
   
    $rgb = new Rgb(24, 23, 22);
    $rgb->setType('anode');
   
    foreach($colors as $color) {
        $rgb->color($color);
        usleep(500000);
    }
    $rgb->allOff();


O script acima cria uma coleção de cores ($colors). Depois cria o objeto $rgb. Meu LED é do tipo ânodo comum, então tenho que fazer o $rgb->setType('anode'). O foreach percorre toda a coleção de cores e associa uma a uma ao $rgb. Uma pausa de meio segundo entre uma cor e outra nos permite ver o processo acontecendo.

Conclusão

Essas classes provam que é muito fácil usar o PHP para manipular os pinos do Raspberry Pi. Atualmente elas são ativas, ou seja, só executam alterações. É preciso que algum dispositivo torne a comunicação entre o RPi e o browser também passiva, para que qualquer alteração no estado de algum pino possa ser devidamente interpretado e tratado. Mas isso é um assunto para outro tópico.

Até breve!

Comentários

Postagens mais visitadas deste blog

Tá na hora de piscar LEDs de maneira melhorada

LEDs on/off com PHP, Python e RPi

PagSeguro e PHP - Usando a tela do PagSeguro (checkout redirect)