Módulo de 16 relés com Raspberry Pi 3 utilizando Socket


Hoje vamos falar do Raspberry Pi 3, um microcomputador que, na verdade, é enorme porque seu processador é Quad core, com bastante memória e sistema operacional Linux. Neste projeto, ainda constituído por um módulo de 16 relés e utilizando Socket, o meu objetivo é iniciar a automação com o Raspberry PI, introduzir conceitos de Socket e Layer, além da programação em C Unix / Linux, mostrar um cliente-servidor e utilizar funções da Lib WiringPI.


Recursos usados

Neste tutorial nós vamos abordar coisas importantes de rede e de protocolo de comunicação, o que resume bem a IOT (Internet Of Things). Para aqueles que querem fazer o projeto de hoje, mas não têm a placa de 16 relés, não há problema algum. É possível utilizar leds conectados às portas do Raspberry Pi utilizando um resistor de 330 Ohms.

Objetivos

- Iniciar automação com Raspberry Pi,
- Introduzir conceitos de socket e Layer;
- Mostrar um cliente-servidor;
- Introduzir programação em C Unix / Linux;
- Utilizar funções da Lib WiringPI.


Configuração Geany

Após a instalação do Raspberry Pi 3, é necessário configurar a IDE Geany antes de compilar os códigos. Dentro do Raspberry Pi nós vamos programar em linguagem C usando compilador GCC.

1. Clique no menu 
Build -> Set Build Commands
(Construir -> Definir Comandos de Construção);

2. Adicione os seguintes comandos:
Em “Compile” adicione: “-lwiringPi” e “-lpthread”
Em “Build” adicione: “-lwiringPi” e “-lpthread”
Em “Execute” adicione: “sudo” como na imagem


Programas Cliente.c e Server.c

No nosso exemplo de hoje nós vamos fazer um programa Cliente.c e Server.c. Ambos vão rodar dentro do sistema operacional Linux do Raspberry Pi 3 usando o IP 127.0.0.1 que é o IP interno LoopBack. Então, são dois processos da memória, sendo que um Client/Server vai ficar conversando com o outro. Mas, é óbvio que você pode mudar o IP e colocar ele rodando no seu notebook, por exemplo, desde que você tenha um GCC lá para compilação necessária e trate as bibliotecas de acordo com seu S.O.

Ligação dos jumpers

Na montagem que fizemos temos os jumpers ligando a placa de relés aos GPIOs do Raspberry.

Socket ou http?

Esse tipo de programação que estamos falando hoje é um tipo de comunicação que chamamos de Socket. Mas, qual a melhor? Socket ou HTTP? Tem situações que são críticas. Ou porque precisa ser muito rápido ou porque o procedimento precisa de segurança, ou seja, você tem um projeto mais complicado e, geralmente, essas situações envolvem aplicações militares ou até aplicações financeiras e, para esses casos, o Socket é bastante indicado. O mesmo ocorre com a robótica.
O Socket para os americanos é como se fosse um fio com plugues em ambas as pontas, que possibilita a ligação entre dois dispositivos, bem como a troca de informações entre tais componentes.
Então, imagine uma situação: você tem um programa executável e ele tem que passar por várias camadas do sistema operacional até chegar à camada física e atravessá-la. Passa novamente por todo o sistema operacional até chegar ao outro executável. Este segundo executável responde e prossegue o mesmo caminho. Neste processo temos uma menor quantidade de Layers e temos dados binários trafegando, tudo de forma rápida e confiável.
Em outra situação, desta vez em HTTP, temos, por exemplo, uma aplicação que está em PHP ou Java Script e esta vai ter que passar pelo browser, pelo apache, pelo Java script, ou seja, vai ser interpretada várias vezes até chegar ao sistema operacional. Só aí já são umas oito camadas até chegar à camada física da rede que a levará até a segunda aplicação, onde passa pelas camadas do sistema operacional. Aqui temos, então, uma maior quantidade de layers e dados em modo texto.
O que quero dizer é que o excesso no número de camadas aumenta a insegurança, pois consequentemente você aumenta o número de pontos de entrada que possibilitam que seu sistema seja invadido, monitorado, entre outras situações. Tudo isso você pode entender melhor assistindo o filme “Snowden”.
No entanto, quero deixar claro que não tenho preferência por Socket ou por HTTP, já que cada um tem sua aplicação.
Há algum tempo, a IBM fez um estudo de comunicação HTTP comparado com o TCP binário com Socket. O resultado do Socket foi quase 100% comparado ao HTTP. No entanto, já vi casos que a performance do TCP binário chega a ser dez vezes maior. 



Diagrama de estado do modelo Client / Server

Nós vamos ter dois programas que nós vamos compilar e rodar no Raspberry Pi. Montei, então, este diagrama para facilitar sua visualização.
À direita está um exemplo com um smartphone, um computador, um ESP8266 e um ESP32 como Client do Raspberry Pi, os quais também podem servir de Server, diferenciação feita através do programa que estiver rodando dentro destes equipamentos ou dispositivos.

Código Servidor main()

int main(int argc , char *argv[])
{
  int socket_servidor;
  
  setaPinos(); //seta os pinos dos reles
  
  if(!preparaSocket(&socket_servidor)) //cria e prepara o socket
   return 0;
 
  iniciaServidor(socket_servidor); //inicia o servidor e executa um loop de recepção de mensagens
}
Ainda criando o Socket:
 
bool preparaSocket(int *conexao)
{
  struct sockaddr_in servidor;
  
  //cria conexão socket
  *conexao = socket(AF_INET, SOCK_STREAM , 0); 
  
  if (*conexao == -1)
  {
    puts("Nao foi possivel criar o socket");
    return false;
  }
  puts("Socket criado");
  
  //Prepara a estrutura sockaddr_in
  servidor.sin_family = AF_INET;
  servidor.sin_addr.s_addr = INADDR_ANY;
  servidor.sin_port = htons(PORTA);
* domínio: inteiro, domínio de comunicação, por exemplo, protocolo AF_INET (protocolo IPv4), AF_INET6 (protocolo IPv6)
Quando INADDR_ANY é especificado na chamada de ligação, o soquete será vinculado a todas as interfaces locais.

Ainda no Código Servidor, seguimos para a função Bind:
  //efetua ligação
  if(bind((*conexao),(struct sockaddr *)&(servidor) , sizeof(servidor)) < 0)
  {
    puts("Falha na ligacao");
    return false;
  }
  puts("Ligacao efetuada");
  return true;
}

Função iniciaServidor(int socket_servidor), que envolve o Listen e o Accept:

void iniciaServidor(int socket_servidor)
{
  int socket_cliente, *novo_socket; 
  
  //inicia a recepção de mensagens
  listen(socket_servidor , 3); 
  
  puts("Aguardando novas conexoes...");
  
  //loop infinito, para cada conexão criada uma thread que recebe as mensagens
  while((socket_cliente = accept(socket_servidor, (struct sockaddr *)0, (socklen_t*)0)))
  {
    puts("Conexao aceita");
    
    //cria thread
    pthread_t thread;
    novo_socket = malloc(sizeof(*novo_socket));
    *novo_socket = socket_cliente;

    //caso ocorra algum erro neste momento o loop é abortado
    if(pthread_create(&thread,NULL , comunicacaoSocket, (void*) novo_socket) < 0)
    {
      puts("Nao foi possivel criar a thread.");
      close(*novo_socket);
      free(novo_socket);
      return;
    }
    puts("Thread criada");
  }
void *comunicacaoSocket(void *conexao)
{
  puts("Thread de conexao iniciada");
  int tamanhoMensagemRecebida, i;
  char mensagem[TF_MENSAGEM];
  mensagem[0] = '\0';
  
  //enquanto não for recebida nenhuma mensagem, permanece em loop
  while(1) 
  {
   //recebe mensagem
   tamanhoMensagemRecebida = recv(*(int*)conexao ,&mensagem, sizeof(mensagem), MSG_PEEK);
   
   //define o final da mensagem (este comando impede que o 'lixo' de memória fique junto à  mensagem)
   mensagem[tamanhoMensagemRecebida] = '\0';
   
   //imprime mensagem recebida
   printf("\nRecebido: %s\n",mensagem);
   
   //deixa a mensagem em maiusculo, assim tanto faz "a1" ou "A1"
   for(i=0; i<tamanhoMensagemRecebida; i++)
   {
      mensagem[i] = toupper(mensagem[i]);
   }
 
//acende ou apaga reles de acordo com a mensagem recebida
   if(strcmp(mensagem,"A1")==0)
    ativaRele(PINORELE_1);
   else
   if(strcmp(mensagem,"D1")==0)
    desativaRele(PINORELE_1);
   else
   if(strcmp(mensagem,"A2")==0)
    ativaRele(PINORELE_2);  
   else
   if(strcmp(mensagem,"D2")==0)
    desativaRele(PINORELE_2);
   else
   if(strcmp(mensagem,"A3")==0)
    ativaRele(PINORELE_3);
   else
   if(strcmp(mensagem,"D3")==0)
    desativaRele(PINORELE_3);
// continua assim até o A16 e D16

 puts("Thread finalizada e socket fechado.");
  
  //libera memória desta conexão
  free(conexao);
  
  return 0;
}

Essa função ativa em sequência:
 
void ativaEmSequencia()
{
 int vet[16], i;
 vet[0] = PINORELE_1;
 vet[1] = PINORELE_2;
 vet[2] = PINORELE_3;
 vet[3] = PINORELE_4;
 vet[4] = PINORELE_5;
 vet[5] = PINORELE_6;
 vet[6] = PINORELE_7;
 vet[7] = PINORELE_8;
 vet[8] = PINORELE_9;
 vet[9] = PINORELE_10;
 vet[10] = PINORELE_11;
 vet[11] = PINORELE_12;
 vet[12] = PINORELE_13;
 vet[13] = PINORELE_14;
 vet[14] = PINORELE_15;
 vet[15] = PINORELE_16;
 
        for(int i=0; i<16; i++)
        {
        digitalWrite(vet[i],LOW);
        delay(200);
        digitalWrite(vet[i],HIGH);
        delay(200);
   }
}

Código do Client

conecta() – criação do Socket
//cria socket e conecta,
bool conecta(int *conexao)
{
 struct sockaddr_in client;
 
    //cria conexão socket
 *conexao = socket(AF_INET , SOCK_STREAM , 0);
 
 //caso ocorra um erro na criação, aborta a conexão
 if (*conexao == -1)
   return false;
   
 //imprime mensagem   
 puts("Socket criado");
Connect
//prepara estrutura do cliente para conexão
 client.sin_addr.s_addr = inet_addr("127.0.0.1");
 client.sin_family = AF_INET;
 client.sin_port = htons(PORTA);

 //conecta o cliente
 if (connect(*conexao , (struct sockaddr *)&client, sizeof(client)) < 0)
   return false;

 //imprime mensagem   
 puts("Conectado\n");
 
 return true;
}

Função Send e Close
int main()
{
 int conexao;
 char msg[TF_MENSAGEM];
 
 //recebe mensagem
 puts("Digite a mensagem:\n[1] - Para sair\n");
 fflush(stdin);
 gets(msg);
 
 //envia mensagens até que "1" seja recebido
 while(strcmp(msg,"1")!=0)
 {
  //conecta cliente
  if(!conecta(&conexao))
  {
   puts("Erro de conexão");
   break;
  }
  
  //envia mensagem
  if(send(conexao ,&msg, TF_MENSAGEM, 0) < 0) 
   puts("Falha no envio\n");
  else
   puts("Enviada!\n");
  
  puts("Digite a mensagem:\n[1] - Para sair\n");
  fflush(stdin);
  gets(msg);
 }
   
 //fecha o socket
 close(conexao);
 return 0;
}

Tratamento de pinos (biblioteca wiringPi.h)

A sequência dos relés usada neste exemplo se dá a partir do pino 8 e até o pino 10, respeitando a seguinte sequência:
#define PINORELE_1   8  
#define PINORELE_2   9  
#define PINORELE_3   7  
#define PINORELE_4   15  
#define PINORELE_5   16  
#define PINORELE_6   0   
#define PINORELE_7   1   
#define PINORELE_8   2  
#define PINORELE_9   3   
#define PINORELE_10  4   
#define PINORELE_11  5   
#define PINORELE_12  12  
#define PINORELE_13  13  
#define PINORELE_14  6   
#define PINORELE_15  14  
#define PINORELE_16  10 

Qualquer dúvida sobre as saídas do Raspberry Pi é só consultar esta tabela:

Ligação dos jumpers

Aqui eu mostro para vocês a ligação: a saída do Raspberry  3 ligada no relé 1, a saída 5 ligada no relé 2, e assim por diante, conforme mostrado na tabela abaixo.

Iniciando o Servidor

Após a compilação é preciso executar o programa com o comando “./servidor”. Assim, o servidor já será iniciado e todas as mensagens recebidas serão informadas no console.

Enviando comandos pelo putty

Eu sempre recomendo que vocês tenham algum programa do tipo deste putty, que funciona como um cliente para protocolos de rede SSH, Telnet e Rlogin.

Digite o IP do Raspberry pi (para descobrir este IP digite ‘ifconfig’ no console)
e a mesma porta utilizada no algoritmo.




Faça o download dos arquivos:

3 comentários:

  1. Ola professor Fernando, da continuidade as aulas de raspberry pi com socket e multiplos clientes, vai ajudar muita gente com seus projetos.

    Att.

    Marcos

    ResponderExcluir
  2. Faz um video ensinando interagir socket com pagina Web.

    ResponderExcluir
  3. Oi Fernando
    posso fazer com Raspi 2 ?

    ResponderExcluir

Tecnologia do Blogger.