Aquisição de dados via TCP/IP com Genuino (Arduino)

Introdução

Recentemente a tão conhecida marca Arduino , fundada por Massimo Banzi, David Cuartielles, Tom Igoe e David Mellis e toda uma comunidade, sofreu uma mudança de nome, para os produtos destinados a outros mercados fora dos EUA, passando a usar o nome Arduino apenas nos EUA e o nome Genuino, em todos os restantes mercados. Falo em marca, pois não se refere apenas a uma board, mas a toda uma “marca” de circuitos baseados em microcontroladores e projectos com base numa mesma filosofia de open-hardware. Não me alongando mais sobre o tema, esta mudança teve origem numa questão legal, que é muito bem apresentada por Maximo Banzi, no keynote que apresentou na Maker Fair e pode ser visto no youtube. Assim sendo, de agora avante, neste artigo, o circuito anteriormente conhecido por Arduino, será designado por Genuino.

Passemos agora àquilo que é realmente interessante: comunicar com o Genuino, receber dados e transmitir dados! Recentemente estive a trabalhar num projecto, que começou como trabalho e acabou dando origem a um fork para um hobbie, onde quis receber dados, de um amplo conjunto de sensores, ligados ao microcontrolador e transmiti-los de seguida, usando a ligação de rede existente para essa transmissão, usando a pilha TCP/IP v4, para transmitir dados bidireccionalmente, entre o microcontrolador e um dispositivo ligado à rede. Neste caso concreto foi utilizada uma ligação por cabo, apesar de ser relativamente simples fazer o mesmo com um circuito ethernet, por exemplo um ESP8266-01, ou qualquer outro que suporte a norma 802.11b, quer comunique com o Genuino por UART, quer comunique usando um outro protocolo.

O problema

Neste caso concreto, pretendia obter a informação sobre a humidade relativa do ar, a temperatura e a luminosidade num determinado espaço e de seguida transmitir essa mesma informação via rede.

Posto o problema, claramente definidos os objectivos, como seria de esperar, existe uma pesquisa sobre que sen- sores usar paca cada uma das funções e como o por a comunicar com a rede.

O hardware

Neste projecto concreto, os sensores usados foram um sensor DHT-11, conjuntamente com a respectiva resistência pull-up de 4.7k, na linha de dados, ligado ao Genuino. Este sensor oferece a capacidade de ler a temperatura do ar e a humidade relativa, é amplamente suportado pela comunidade e existe uma excelente biblioteca de suporte para o mesmo, a lib dht11.h disponível no github no repositório da Adafruit.

Para obter a informação da luminosidade, existiam duas possibilidades, logo à primeira vista. Uma seria usar um foto-resistor, a outra usar um circuito mais complexo como o TSL2561 da Sparkfun, que comunica por I2C com o micro- controlador e oferece uma série de funcionalidades interessantes que um fotoresistor não disponibiliza, nomeadamente a capacidade de ler tanto a luminosidade visível como a luminosidade infravermelha, aproximando os valores lidos, daqueles que o olho humano percepciona. Tal como o sensor DHT-11, o TSL2561 tem um amplo suporte de toda a comunidade e bibliotecas que facilitam a sua utilização.
Arduino: sensoresEscolhidos os sensores e a placa Genuino, no caso um Genuino Uno, restou a escolha do circuito ethernet a utilizar, neste caso ethernet shield w5100, com leitor de cartões micro-SD e stacking headers, para ficar acoplado mesmo por cima do Genuino.

Arduino: ethernet shield

Ligações

Escolhido o hardware, é necessário proceder às ligações antes de passarmos à escrita do código destinado a ser executado pelo microcontrolador ATMega328. Para este efeito e uma vez que não se trata de um circuito definitivo, mas ape- nas um circuito de teste, as protoboards, como se pode ver na imagem abaixo, são bastante úteis e facilitam as ligações.

Arduino: esquema de ligações

A fim de facilitar as ligações do circuito, na tabela abaixo encontram-se os sensores, indicando o numero de cada pino e o pino a que é ligado no circuito ethernet do Genuino.

DHT11 Genuino
Pino do sensor Componente Pino do Genuino
1   5vdc
2 Resistência 4.7k D2
3    
4   Gnd
TSL2561 Genuino
1-SDA   A4
2-SCL   A5
3-GND   GND
4-3v3   3v3
5-INIT    

As células em branco foram deixadas propositadamente uma vez que nos respectivos locais, não é feita nenhuma ligação, quer seja a um componente intermédio, quer seja ao Genuino.

Programação do Genuino

Chegados a esta parte, está na hora de programar o Genuino de forma a comunicar por rede ethernet, usando o conjunto de protocolos TCP/IP. Felizmente uma parte substancial do trabalho, já vem “pré feito” pela biblioteca ethernet.h, que nos disponibiliza uma maior abstracção do hardware propriamente dito, deixando-nos livres para a programação da aplicação que será executada. No entanto continua a ser necessário executar algumas tarefas como a definição de um MAC address, a colocação em modo cliente DHCP, caso tenhamos um servidor DHCP na rede, ou a definição de um endereço IP v4, bem como a respectiva máscara de sub-rede e gateway. Desta biblioteca iremos usar maioritariamente o método write, da classe server, para enviarmos dados para o nosso cliente TCP, bem como o método read, para lermos instruções transmitidas pelo servidor.

O código não é complexo e é quase auto-explicativo, não me alongando na sua explicação. De forma muito resumida, inicia a interface ethernet, fica a aguardar por uma conexão e quando a mesma está aberta, aguarda um comando, que executa e devolve o seu resultado, em texto simples. Recomendo a leitura da documentação oficial da biblioteca, para quem pretenda aprofundar os conceitos e conhecimentos.

//thanks to the sparkfun team, for the li
//braries, all credits for their work due to 
//them.
#include "DHT.h"
#include <SparkFunTSL2561.h>
#include <Wire.h>
#include <SPI.h>
#include <Ethernet.h>

#define DHTPIN1 2     // define o pino de dados 
                      //do sensor, neste caso 2
#define DHTTYPE DHT11   // DHT 11 (basta uma 
                  //constante para dois sensores)
SFE_TSL2561 light;
boolean gain;   //definição do ganho, 0 = X1, 
                //1 = X16;
unsigned int ms;  
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 
                                          0xED };
IPAddress ip(192,168,1, 177);
IPAddress gateway(192,168,1, 1);
IPAddress subnet(255, 255, 255, 0);

DHT dht1(DHTPIN1, DHTTYPE);

EthernetServer server(77);
boolean alreadyConnected = false; 

String commandString;

void setup() {
  dht1.begin();
  light.begin();
unsigned char ID;
  if (light.getID(ID))
  {}
  else
  { byte error = light.getError(); }
  gain = 0;
  unsigned char time = 2;
  light.setTiming(gain,time,ms);
  light.setPowerUp();
  pinMode(ledPin, OUTPUT);
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
  Serial.begin(9600);
   while (!Serial) {;}
  Serial.print("Socket Server:");
  Serial.println(Ethernet.localIP());
}

void loop() {
  EthernetClient client = server.available();
  if (client) {
    if (!alreadyConnected) {
      client.flush();    
      commandString = "";
      server.println("--> insira um dos comandos 
                                    disponiveis!");
      alreadyConnected = true;
    } 
    while (client.available()) {      
     char newChar = client.read();
     if (newChar == 0x0D)  
     { processCommand(commandString);} 
	else { commandString += newChar; }
    }
  }
}

void processCommand(String command)
{
  //dht11-1
    if (command.indexOf("dht1") > -1)
    {
      delay(1000);
      float h1 = dht1.readHumidity();
      float t1 = dht1.readTemperature();
      if (isnan(h1) || isnan(t1)) 
      { return; }    
      server.print("Humidade ");
      server.print(String (h1));
      server.print("; Temperatura ");
      server.println(String (t1));
      commandString = "";
      return;
  } 
  //light
    if (command.indexOf("light") > -1)
    {
      delay(200);
      unsigned int data0, data1;
      if (light.getData(data0,data1))
        {
            double lux;    
            boolean good;  
            //calculo de Lux
            good = light.getLux
                         (gain,ms,data0,data1,lux);
            server.print("luminosidade em lux ");
            server.println(lux);
        }
      commandString = "";
      return;
    }
  commandString = "";
  instructions();
}
void instructions()
   server.println("Comando não reconhecido");
   server.println("Use um dos seguintes 
                                       comantos:");
   server.println("* dht1, para obter a humidade e 
                               temperatura do ar");
   server.println("* light, para obter a informação 
                                 da luminosidade");
}
//to my late night owl angel, thanks for the wisdom 
//inspiration and company,
//Stephan|B|

Conclusão

Uma vez feito o upload do código, para o Genuino, basta ligar um cabo de rede e um cliente simples ou usar a nossa própria aplicação para comunicar por sockets com o Genuino, enviando a instrução que pretendemos e recebendo o resultado da mesma. Neste exemplo usei o Putty, para simplificar, apenas defini a porta como 77, pois era uma das portas disponíveis. O objectivo ao usar sockets neste exemplo, prendeu-se com a simplicidade com que se comunica com o dispositivo, bem como com o leque de possibilidades que deixa em aberto para o desenvolvimento de aplicações que interajam com o circuito, como é o caso de aplicações mobile, feitas por exemplo com C# e Xamarin, usando o componente “sockets-for-pcl”, que é bastante simples de usar e bastante intuitivo.

Publicado na edição 51 (PDF) da Revista PROGRAMAR.