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.
<?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.
<?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.
Até breve!
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
Postar um comentário