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.
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.
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.
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:
6 Comentários
Ola professor Fernando, da continuidade as aulas de raspberry pi com socket e multiplos clientes, vai ajudar muita gente com seus projetos.
ResponderExcluirAtt.
Marcos
Faz um video ensinando interagir socket com pagina Web.
ResponderExcluirOi Fernando
ResponderExcluirposso fazer com Raspi 2 ?
Olá, boa noite.
ResponderExcluirMuito boas suas aulas, conteúdo de primeira.
Para meu sistema de automação, preciso que os relés também sejam acionados via pulsador (interruptor), e desta forma quando acionado via pulsador ele atualize o servidor com a informação do estado da lampada, portanto o controle seja integrado, via wifi ou pulsador. Como poderia fazer esta implementação?
Obrigado
Prof. Fernando, minha dúvida é os pinos gnd vcc, fonte separado dos 2 x 5v e gnd dos pinos 16un reles. Entendo (mas, dúvida) ser para fonte externa e nao usar a do raspberry (tipo a IN arduino).
ResponderExcluirAgradeço...
Comprei uma placa dessa de 16 relés, mais não está acionando. Com ajuda do multímetro medi a saída da raspbarry e a tensão de acionamento 3.5v funciona perfeitamente. Acho que o problema é porque a placa é de 5v pois o rele fica sempre atracado. Tem alguma maneira de contornar isso? Ou vou poder usar ele só com o arduino onde a tensão de acionamento é 5v?
ResponderExcluir