ALIMENTADOR DE ANIMAIS DOMÉSTICOS
AUTOMÁTICO COM COMUNICAÇÃO À DISTÂNCIA
Rafael Cortez Bellotti de Oliveira
Projeto de Graduação apresentado ao Curso de
Engenharia Eletrônica e de Computação da Escola
Politécnica, Universidade Federal do Rio de
Janeiro, como parte dos requisitos necessários à
obtenção do título de Engenheiro.
Orientador: Carlos Fernando Teodósio Soares
Rio de Janeiro
Junho de 2017
iv
UNIVERSIDADE FEDERAL DO RIO DE JANEIRO
Escola Politécnica – Departamento de Eletrônica e de Computação
Centro de Tecnologia, bloco H, sala H-217, Cidade Universitária
Rio de Janeiro – RJ CEP 21949-900
Este exemplar é de propriedade da Universidade Federal do Rio de Janeiro, que
poderá incluí-lo em base de dados, armazenar em computador, microfilmar ou adotar
qualquer forma de arquivamento.
É permitida a menção, reprodução parcial ou integral e a transmissão entre
bibliotecas deste trabalho, sem modificação de seu texto, em qualquer meio que esteja ou
venha a ser fixado, para pesquisa acadêmica, comentários e citações, desde que sem
finalidade comercial e que seja feita a referência bibliográfica completa.
Os conceitos expressos neste trabalho são de responsabilidade do(s) autor(es).
v
DEDICATÓRIA
Dedico este projeto aos meus pais, que sempre estiveram presentes ao meu lado, e
que sempre se preocuparam com minha formação e meu bem-estar. À minha mãe, Márcia
Cortez Bellotti de Oliveira, que sempre me instruiu a pensar fora da caixa, nunca negar
possíveis aventuras e propostas, respirar fundo e levar a vida adiante e focar em nossas
conquistas e não nas dos outros, pois quem faz nosso caminho somos nós. E ao meu pai,
Carlos Alberto Pereira de Oliveira, que sempre insistiu em minha educação e fez disso
uma missão, garantindo que eu sempre tivera acesso a qualquer caminho que posso vir a
desejar em minha vida. A verdade é que sem eles, minha realidade seria apenas um sonho.
vi
AGRADECIMENTO
Agradeço a meu irmão Artur Cortez Bellotti de Oliveira por sua ajuda na
modelação gráfica da peça que seria impressa em 3D, e pelos seus conselhos durante a
execução do projeto. Sou eternamente agradecido a Lara Piloto Braga pelo grande apoio
moral, pelos sábios conselhos e pelo seu carinho, que sempre conseguiu me motivar e me
fazer tentar perseguir o que eu mais gosto de fazer. Agradeço ao meu colega Henrique
Hafner por sua grande dedicação e esforço na impressão 3D do modelo. E finalmente,
agradeço aos meus avós Olivia (in memoriam), Décio, Aldir (in memoriam) e Lúcia pela
inspiração e pelas memórias, tesouro que nunca esquecerei.
vi
i
RESUMO
Este projeto final consiste no estudo e desenvolvimento de um sistema de
alimentação automático para animais domésticos com comunicação à distância. Este foi
desenvolvido através do conceito de sistemas embarcados para programação em Python
e Shell e configuração de uma Raspberry Pi 2 Modelo B. A comunicação via internet
utiliza o protocolo TCP para comunicação entre o sistema e o usuário a longa distância;
a API Weaved e requisições HTTP para criar um proxy de acesso para que o usuário não
tenha que realizar processos de configurações como redirecionamento de portas; e a
linguagem de programação Java para a criação de uma interface gráfica que serve como
portal de acesso do usuário. O sistema poderá alimentar seus animais a cada janela de
horas ou quando o usuário quiser alimentar seus animais imediatamente.
Palavras-Chave: sistemas embarcados, alimentação automática, alimentador de animais
domésticos, Raspberry Pi, redes de computadores.
vi
ii
ABSTRACT
This undergraduate project presents a study and development of an automatic
feeding system for domestic pets with long distance communication through the use of
concepts of embedded systems for programming in Python and Shell. The communication
through the internet uses TCP protocol for long range communication between system
and user; Weaved API and HTTP requests to create an access proxy, so the user doesn’t
have to do configuration processes such as port forwarding; and the Java programming
language used to create a graphic user interface that works as an access portal for the user.
The system will feed the domestic pets periodically or when the user wishes to feed them
immediately.
Key-words: embedded systems, automatic feeding, domestic animal feeder, Raspberry
Pi, computer networks.
ix
SIGLAS
AD – Active Directory
API – Application program interface
CLI – Command Line Interface
DCCP – Datagram Congestion Control Protocol
FTP - File Transfer Protocol
GPIO – General Purpose Input Output
HTTP – Hypertext Transfer Protocol
HTTPS - HTTP Secure
I2C – Inter-Integrated Circuit
IANA – Internet Assigned Numbers Authority
IETF – Internet Engineering Task Force
IMAP – Internet Message Access Protocol
IP – Internet Protocol
IRC – Internet Relay Chat
NNTP – Network News Transfer Protocol
NTP – Network Time Protocol
POP3 – Post Office Protocol
PWM – Pulse Width Modulation
Rpi – Raspberry Pi
Rx – Receiver
SCL – Serial Clock Line
SCTP – Stream Control Transmission Protocol
SDA – Serial Data Line
SMTP – Simple Mail Transfer Protocol
SNMP – Simple Network Message Protocol
SPI – Serial Peripheral Interface
SSH – Secure Shell
TCP – Transmission Control Protocol
Tx – Transmitter
UDP – User Datagram Protocol
UFRJ – Universidade Federal do Rio de Janeiro
x
UI – User Interface
URL – Uniform Resource Locator
UX – User Experience
VNC – Virtual Network Computing
VPN – Virtual Private Network
xi
Sumário
1 Introdução 1
1.1 - Tema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 - Delimitação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 - Justificativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 - Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 - Metodologia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.6 - Materiais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.7 - Descrição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Comunicação entre usuário e sistema 6
2.1 - TCP/IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 - Comunicação TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3 - API Weaved . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3 Configurações do ambiente 26
3.1 - Configuração e instalação da placa Raspberry Pi . . . . . . . . .
26
3.2 - Armazenamento da lista de horários de alimentação . . . . . . . 28
3.3 - Administração de horários de alimentação . . . . . . . . . . . . . . 30
3.4 - Controlador do servomotor . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4 Interfaces física e virtual
38
4.1 - Interface gráfica destinada ao usuário . . . . . . . . . . . . . . . . . .
38
xi
i
4.2 - Construção da estrutura do alimentador . . . . . . . . . . . . . . . . 43
5 Conclusões e pontos de aprimoramento no futuro
47
Bibliografia
49
Apêndice A Fluxograma de operação do projeto
Apêndice B Código fonte do alimentador
Apêndice C Código fonte do cliente
50
51
58
xi
ii
Lista de Figuras
2.1. – Camadas do TCP/IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 – Comunicação envio e resposta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 – Three-way handshake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 – Fluxograma do estabelecimento da comunicação utilizando-se TCP . . . . . . 14
2.5 – Exemplo de redirecionamento de portas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.6 – Exemplo de uso de VPN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.7 – Fluxograma da utilização da API Weaved . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.8 – Fluxograma da comunicação entre o programa cliente e o alimentador. . . . . . . 24
3.1 – Configuração da Raspberry Pi (raspi-config) . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2 – Fluxograma de operação da alimentação programada . . . . . . . . . . . . . . . . . . . . . 31
3.3 – Controlador PWM da Adafruit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.4 – Pinos GPIO da placa Raspberry Pi 2 Modelo B . . . . . . . . . . . . . . . . . . . . . . 34
3.5 – Montagem do circuito do sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.6 – Esquema elétrico do alimentador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.7 – Fluxograma do controle do dispensador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.1 – Login no programa de alimentação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.2 – Usuário não cadastrado/inválido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3 – Menu principal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.4 – Estrutura do alimentador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.5 – Modelo da peça criada no SolidWorks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.6 – Peça rachada na segunda impressão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.7 – Engrenagem na fase final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.8 – Engrenagem acoplada ao servomotor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
xi
v
Lista de Tabelas
2.1 – Comparação entre os protocolos TCP e UDP . . . . . . . . . . . . . . . . . . . . . . 8
2.2 – Portas oficiais mais conhecidas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3 – Comandos e funcionalidades do sistema de alimentação . . . . . . . . . . . . . . . 14
1
Capítulo 1
Introdução
1.1 – Tema
O tema do projeto consiste no estudo e no desenvolvimento de um alimentador de
animais domésticos para facilitar o cuidado necessário ao se ter animais domésticos em
casa. A disponibilidade de tempo dos seres humanos nos tempos atuais se encontra muito
limitada para realizar até tarefas mais simples como alimentar seus animais domésticos
de uma forma saudável, correta, limpa e sem preocupações. O sistema, para atingir tal
meta, permite que o usuário alimente seus animais no momento desejado, inclusive
através da programação dos próximos horários para alimentação. Todas essas
funcionalidades podem ser acessadas independentemente da distância do usuário ao
sistema. Este foi construido usando uma Raspberry Pi 2 Modelo B, uma peça modelada
e impressa de forma 3D, um servomotor e um controlador PWM.
O projeto se encontra dentro de várias áreas do curso, porém as que se destacam
são as áreas de software, sistemas digitais e redes de computadores. Para o
desenvolvimento deste sistema, foram necessários conhecimentos de sistemas
embarcados, protocolo HTTP, uso da API Weaved, Python para programação da
Raspberry Pi 2 Modelo B utilizada e como operar seus pinos, scripts shell, Java e sua
bibilioteca JSwing, conexão TCP via socket e modelagem e impressão 3D.
1.2 – Delimitação
O projeto tem como foco a alimentação de animais domésticos usando
comunicação entre cliente e sistema pela internet. Portanto, não será de muito uso para
quem não tiver um animal ou não tiver acesso à internet. Além disso, devido ao fato de
que o projeto usa TCP para comunicação entre o sistema e o usuário, a porta de internet
5005, que é a porta utilizada para receber as mensagens enviadas pelo cliente, deve estar
sempre aberta quando esta comunicação estiver sendo realizada. Isto pode ser um
problema para locais como ambientes de trabalho e instituições de ensino que, por
2
preacauções de segurança, restringem o acesso a certas portas de internet. Outra
delimitação do sistema é que o projeto está restrito ao fornecimento de comida para os
animais e não de água, embora consiga ser adaptado para isso.
Vale a pena notar que o sistema não possui uma maneira de preencher
automaticamente o reservatório de ração quando este se encontra vazio. Portanto, cabe ao
usuário planejar a quantidade necessária para o seu preenchimento.
1.3 – Justificativa
Sempre me interessei muito por animais e sua saúde física e mental, tanto que
cogitei em uma época cursar medicina veterinária. Além disso, venho não só ajudando
abrigos de animais, mas também ajudando animais de rua diretamente faz alguns anos.
Uma das grandes dificuldades nesse tipo de trabalho voluntário é encontrar um lar para
esses animais, pois os seres humanos andam cada vez com menos disponibilidade de
tempo no seu dia a dia e possuem cada vez mais dificuldade em aproveitar o seu tempo
vago de forma agradável e sem preocupações. Outro fator que dificulta a adoção desses
animais é o fato de que animais domésticos exigem muito tempo de seus donos: passeios,
cuidado com a sua saúde e higiene, atenção psicológica e afetiva, alimentação correta e
nas horas certas, entre outros fatores.
A dificuldade de cuidar de animais domésticos seria certamente reduzida caso sua
alimentação fosse realizada de forma autônoma, mais higiênica e de modo a garantir uma
dieta com porções na medida e hora certa. Com maior facilidade de cuidar de animais
domésticos como cães e gatos, é provável que um número maior de indivíduos considere
adotar um ou mais animais. Além disso, os donos atuais teriam mais tempo livre, e
também poderiam trabalhar e/ou passar tempo fora de casa sem se preocupar muito com
o bem estar do animal quando este fica em casa sozinho.
Neste sentido, o presente projeto utiliza estudos realizados anteriormente para
buscar uma nova forma de, não só adquirir novos conhecimentos, mas também
desenvolver um projeto de interesse pessoal, cujo propósito tem a capacidade de ajudar
uma boa causa e que também tem a capacidade de se tornar um produto para uso
comercial no futuro.
3
1.4 – Objetivos
O projeto propõe um conceito que facilita a vida do dono de pelo menos um
animal doméstico, onde ele não necessitará se preocupar com uma das tarefas necessárias
para cuidar de um animal: a alimentação. Desta forma, tem-se como objetivos específicos:
(1) desenvolver um método computacional que permita que o dispositivo seja acessado
de qualquer localização através do uso da internet; (2) desenvolver um modelo higiênico
e eficiente para a preservação da ração e alimentação programada do animal; (3) elaborar
uma interface fácil de ser utilizada para o usuário do sistema alimentador, onde não há
necessidade de um tutorial complexo e que seja facilmente navegável utilizando
princípios de Design Thinking e UI/UX. UI/UX são conceitos de design que tem como
objetivo proporcionar a interface gráfica mais intuitiva possível (UI ou User Interface) e
a forma mais intuitiva de utilizar o software (UX ou User Experience).
1.5 – Metodologia
Este trabalho irá utilizar aprendizados da área de sistemas embarcados do tipo
Raspberry Pi Modelo B para a programação da placa encarregada de controlar todas as
funcionalidades do alimentador de animais domésticos. Ela será programada usando
scripts em linguagem Python para receber o sinal do usuário e scripts em shell para
configuração de rede, scripts de inicialização e porta de acesso. Como a placa Raspberry
Pi 2 Modelo B só funciona com sinais digitais, o controlador PWM da Adafruit será
utilizado para realizar o acionamento do servomotor. Será realizado um estudo para
descobrir a frequência ideal de operação de servomotores. Este servomotor somente será
utilizado quando a lógica de programação determinar, ou seja, quando o horário atual for
igual ao horário programado para alimentação (horário escolhido pelo usuário), ou
quando o usuário desejar que a ração esteja disponível para o animal naquele instante de
tempo.
Para que o dispositivo esteja acessível de qualquer lugar do mundo pela internet,
a sua porta de entrada deve estar aberta. Como na maioria das casas não será possível
estabelecer conexão com o dispositivo através do seu endereço IP, foram considerados
dois caminhos a serem tomados: fazer port forwarding ou usar um software para
administrar o acesso remoto. Para evitar processos complexos para o usuário e
desenvolvedor, o software Weaved vai ser utilizado, devido também à sua fácil integração
4
com a Raspberry Pi. O software Weaved gera dinamicamente um proxy e uma porta para
o dispositivo, associados ao seu IP privado e ao IP do modem, para poder ser acessado de
qualquer lugar do mundo através da internet.
O protótipo do alimentador vai ser montado em duas etápas: impressão 3D de uma
engrenagem a partir de uma modelagem no software SolidWorks e a construção do resto
do protótipo, utilizando materiais comuns como tubos, potes de plástico e fita adesiva.
Isto garantirá um design simples e de fácil utilização para o usuário do protótipo.
A outra metade do projeto consiste em criar uma interface de uso simples para o
usuário. Este software vai oferecer as seguintes opções para o usuário: disponibilizar
ração para o animal naquele exato momento e adicionar, remover ou substituir um horário
de alimentação, que são marcados para todos os dias da semana. Estes horários serão
checados através do uso do relógio interno da placa do sistema embarcado. Para o
desenvolvimento do software serão usados conceitos de Design Thinking, para criar uma
experiência única e confortável para o usuário, onde o programa será facilmente
navegável e intuitivo, além de utilizar conceitos de engenharia de software através do uso
de padrões de design.
O êxito deste trabalho depende de um estudo profundo sobre o melhor uso do
equipamento a ser utilizado. A placa deve ser otimizada e configurada de forma que possa
estar sempre ligada, independente do horário (embora não seja possível seu uso caso
ocorra uma falta de energia elétrica), a porta de acesso à rede deve ser aberta, de forma
que o usuário não necessite de conhecimentos prévios acerca de redes de computadores
para poder utilizar o produto final, e a interface a ser utilizada pelo o usuário deve ser
simples, intuitiva e eficiente.
1.6 – Materiais
Para o alimentador, será utilizado: uma placa programável Raspberry Pi 2 Modelo
B, que foi responsável por controlar suas funcionalidades através do uso de códigos
escritos em linguagem Python e scripts shell; um controlador PWM de 16 canais da
Adafruit, que foi responsável por gerar saídas PWM; um servomotor que foi controlado
através dessas saídas PWM geradas e uma impressora 3D. Além disso, foram utilizados
materiais comuns de um ambiente residencial para a construção da carcaça do
alimentador. Os materiais utilizados foram: um cano com bifurcação, uma garrafa de
5
plástico, uma tábua de madeira, um pote de plástico, fitas adesivas, parafusos e uma caixa
de madeira.
Para a interface com o usuário, foi utilizado um notebook para o desenvolvimento
do software com interface gráfica, de modo que o usuário possa, com facilidade,
configurar e utilizar o alimentador. Esse software de interface foi escrito em linguagem
Java.
Finalmente, para realizar a administração de acesso à rede remota foi utilizado o
software Weaved.
1.7 – Descrição
No Capítulo 2 será explicado como foi desenvolvida a comunicação entre a placa
Raspberry Pi 2 Modelo B, denominada de sistema, e o usuário. Cada seção explica uma
etapa da construção do método de comunicação.
O Capítulo 3 apresenta as etapas necessárias para a configuração da placa. Este
processo é necessário para que a Raspberry Pi consiga executar todas as funções do
sistema, como armazenamento e manipulação de horários de alimentação e o controle do
servomotor.
As interfaces com o usuário são apresentadas no Capítulo 4. Nele será explicitado
como foi desenvolvida a interface gráfica para o software, que o usuário utilizará para
conseguir realizar a comunicação com a placa, e a modelagem necessária para a
impressão do modelo 3D do projeto.
Ao final, a conclusão cita e desenvolve pontos de melhoria para o projeto no
futuro.
6
Capítulo 2
Comunicação entre usuário e sistema
Este capítulo trata da comunicação entre o programa cliente e o alimentador e se
divide em três partes: explicação de conceitos de TCP/IP e como impactam o
desenvolvimento do projeto (Seção 2.1), elucidação de como foi desenvolvida a
comunicação TCP entre o programa cliente e o alimentador (Seção 2.2) e um
esclarecimento sobre o funcionamento da API Weaved e o porque de sua implementação.
2.1 – TCP/IP
O Internet Protocol Suite, também conhecido como TCP/IP, é um modelo
conceitual e um conjunto de protocolos de comunicações entre computadores em rede.
Seu nome vem de dois protocolos: TCP (Transmission Control Protocol ou Protocolo de
Controle de Transmissão) e IP (Internet Protocol ou Protocolo de Internet). A pilha da
suíte pode ser dividida em camadas: camada de aplicação, camada de transporte, camada
de rede, camada de enlace e camada física [1]. Para a realização deste projeto, foi
realizado um estudo em torno da camada de transporte e a camada de aplicação, com o
objetivo de entender quais protocolos atendem melhor ao projeto. As camadas do
protocolo TCP/IP e as tecnologias mais utilizadas das camadas de aplicação e de
transporte estão representadas na Figura 2.1.
Figura 2.1. – Camadas do TCP/IP.
7
A camada de aplicação tem como objetivo garantir uma forma de comunicação
entre um programa e outro, onde os processos relacionados a esta camada são específicos
da aplicação. Todos os dados são passados pelo programa de rede, utilizando o formato
que foi definido pela aplicação, porém, a codificação necessária deve seguir as regras do
protocolo escolhido para garantir uma operação bem-sucedida. Entre os programas e seus
correpondentes protocolos estão o HTTP (navegação na World Wide Web), e o SSH (login
remoto seguro), ambos utilizados para o desenvolvimento e implementação do projeto.
Uma vez que os dados foram codificados, seguindo o padrão definido pelo
protocolo utilizado, eles serão passados para a próxima camada da pilha IP: a camada de
transporte. Nesta camada, será necessário que aplicações servidoras sejam associadas
com um número de porta, que são geralmente alocadas pela IANA, porém, podem ser
escolhidas pelo desenvolvedor, caso seja necessário, ou até pelo usuário, através de
parâmetros em tempo de execução.
Problemas com conflitos de portas acabam ocorrendo raramente, pois é
extremamente difícil ter programas que utilizam as mesmas portas. Além disso, há uma
grande lista de portas disponíveis para serem utilizadas pela aplicação, ou seja, a
probabilidade de usar uma porta que já está em uso é relativamente baixa.
O objetivo da camada de transporte é determinar o destino dos dados transmitidos
por uma aplicação. A fim de cumprir esse objetivo, existe uma variedade de protocolos
disponíveis ou em desenvolvimento para serem utilizados. Entre eles, podem-se citar os
protocolos TCP, SCTP, UDP e o DCCP.
O TCP ou Transmission Control Protocol é um protocolo considerado
“confiável”, pois garante que os dados que chegam para o receptor estejam íntegros, ou
seja, não chegam corrompidos ou danificados e na ordem correta. Para evitar sobrecarga,
o protocolo TCP mede continuamente o quão carregada se encontra a rede, e desacelera
sua taxa de envio para evitar a sobrecarga. Este protocolo é muito utilizado para
streaming, programas em tempo real e aplicações de routing com altas taxas de perda na
camada física.
O SCTP ou Stream Control Transmission Protocol é um protocolo confiável e
recente que possui suporte multihoming, ou seja, o final de uma conexão pode ser
representado por múltiplas intefarces físicas (múltipos endereços IP). Isto garante que,
caso ocorra alguma falha, a conexão não será interrompida.
O UDP ou User Datagram Protocol é um protocolo sem conexão, ou seja, os
dados são enviados sem haver necessidade de estabelecer um meio entre os dois lados da
8
conexão, o que acaba acelerando a comunicação. Este protocolo permite que a aplicação
envie uma unidade de transferência básica conhecida como datagrama, que está associada
a uma rede de comutação de pacotes, onde a hora de chegada, a ordem e a entrega não
são garantidos. Ele é considerado um protocolo não confiável de menor esforço, pois não
verifica se os pacotes de dados alcançaram o seu destino e não garante que a sequência
de dados chegue na mesma ordem que foi enviada.
O DCCP ou Datagram Congestion Control Protocol está em desenvolvimento
pela IETF. Ele prevê controle de fluxo das semânticas do TCP, e garante que o modelo
de serviços de datagramas do UDP se mantenha visível para o usuário.
Neste trabalho, para o programa cliente poder se comunicar com o sistema
servidor, houve a necessidade de se escolher um protocolo de transporte entre os
estudados. Devido ao fato de que o protocolo DCCP ainda está em fase de
desenvolvimento e, portanto, está em uma fase instável e não pode ser usado, e que o
protocolo SCTP é muito recente e, portanto, não possui muitos casos de uso, pouca
documentação, pouco suporte na Internet e é um protocolo muito mais complexo do que
o projeto demanda, ambos foram desconsiderados. A decisão final ficou entre os
protocolos UDP e TCP. Assim, foi esboçada uma comparação de performance entre os
protocolos como consta na Tabela 2.1. Os critérios determinam as principais diferenças
entre os protocolos TCP e UDP, onde foi necessário um estudo para entender qual seria
o melhor protocolo de transporte a ser utilizado no projeto [2].
Tabela 2.1 – Comparação entre os protocolos TCP e UDP.
Critérios Protocolos
Confiabilidade Ordem Conexão Controle
de congestionamento
Método de
transmissão
TCP Confiável Ordenado Pesado Possui Streaming
UDP Não confiável Não ordenado Leve Não possui Broadcast
A comunicação entre a interface utilizada pelo usuário e o sistema deveria ser
considerada confiável, pois deve se garantir que o ponto de destino receba todos os dados
enviados. Isto garante que sempre haverá uma resposta para cada mensagem enviada.
Assim, o usuário sempre terá certeza se a operação requisitada foi realizada com sucesso
ou não, o que garante uma boa interação humano-computador. O protocolo TCP
administra o envio de mensagens e consegue determinar se ocorreu um recebimento dos
dados ou se ocorreu uma falha no envio, como o caso de um timeout. Se isto ocorrer, o
9
protocolo TCP realiza várias tentativas de envio da mensagem. Caso a mensagem se perca
durante a transmissão, o servidor requisitará a parte perdida novamente. No entanto, o
protocolo UDP não garante a ausência de perda de mensagens e nem possui uma forma
de administrar o envio de mensagens para garantir que chegaram ao destino. Portanto,
neste requisito, o TCP é mais adequado para o projeto.
Para garantir uma “comunicação de envio e resposta” - ou seja, uma comunicação
onde, após uma mensagem ser enviada, uma outra mensagem é enviada de volta como
resposta a partir do destino, conforme é ilustrado na Figura 2.2 - é necessário garantir que
as mensagens enviadas cheguem na mesma ordem em que foram enviadas. Assim, é
garantido que a resposta seja condizente com a mensagem que foi enviada, e que as
operações a serem realizadas no destino serão realizadas da forma esperada. Além disso,
caso o protocolo não ordene corretamente o envio de dados, pode ocorrer uma má
interpretação no ponto de destino, o que pode gerar um comportamento diferente do
esperado, onde o interpretador da mensagem do destino não consiguirá enquadrar os
dados para realizar nenhuma das operações possíveis do sistema, ou realizará uma
operação equivocada. O protocolo TCP garante que, caso sejam enviadas múltiplas
mensangens, a ordem será mantida. Caso segmentos de dados cheguem na ordem
incorreta, os buffers do protocolo causam um atraso até todo os dados chegarem na ordem
correta. Por outro lado, o protocolo UDP não consegue afirmar se os dados chegaram na
ordem correta. Portanto, o protocolo TCP para este requisito é o mais adequado para o
projeto.
Figura 2.2 – Comunicação “envio e resposta”.
10
A conexão de um protocolo define sua velocidade de envio e, portanto, a
velocidade de recebimento dos dados, além de definir o que é necessário para se realizar
a conexão. O protocolo UDP é uma camada de transporte leve, que foi construída por
cima de um IP. O UDP não requer que seja realizada uma conexão entre os dois pontos
da comunicação, enquanto o protocolo TCP determina que deve ser realizada uma
conexão pesada e que requer o uso de três pacotes para realizar uma conexão socket. O
TCP é descrito como um “three-way handshake”, pois é necessário um processo de iniciar
e reconhecer uma conexão. Este processo é melhor demonstrado na Figura 2.3. Somente
depois desse processo é possível realizar uma transmissão de dados entre os dois pontos
da conexão. Após o término da transmissão, a conexão é encerrada através do fechamento
de todos os circuitos virtuais estabelecidos. O protocolo UDP, por outro lado, não utiliza
o mesmo processo do TCP e não apresenta nenhuma caraterística semelhante. Portanto,
é um protocolo de transporte não ordenado e não confiável, como foi discutido
anteriormente, o que pode gerar erros que não serão corrigidos apropriadamente, como a
duplicação de dados, por exemplo. Embora o processo de conexão do UDP seja mais
rápido que o do TCP e, portanto, garante uma velocidade de transmissão mais ágil, ele
não atende os requisitos esboçados para o projeto devido à sua falta de confiabilidade.
Figura 2.3 – Three-way handshake.
11
Outro ponto a favor da utilização do protocolo TCP é que ele possui a capacidade
de realizar um controle de congestionamento, funcionalidade que não está disponível ao
se optar por utilizar o protocolo UDP. Como foi descrito anteriormente, esta
funcionalidade permite que a taxa de envio seja reduzida para evitar uma sobrecarga.
Como a possibilidade de tal sobrecarga ocorrer não é considerada baixa, a utilização do
protocolo TCP demonstraria ser muito útil para evitar falhas na comunicação devido a
problemas na rede.
O último critério da tabela se refere ao tipo de transmissão que é adotado por cada
protocolo. O protocolo TCP lê dados como uma corrente de bytes e a mensagem é
transmitida para os limites do segmento, enquanto o protocolo UDP transmite mensagens
como pacotes que são enviados individualmente e estes podem ser recebidos por todos os
dispositivos na subnet. Para este projeto, não há necessidade que o método de transmissão
seja um broadcast, como é o caso do protocolo UDP. Porém, como as mensagens não
contêm informações privadas que não deveriam ser recebidas por outros dispositivos ou
usuários, este critério acaba não sendo importante para definir qual deve ser o protocolo
utilizado.
Após a análise, foi concluído que, entre os protocolos mais utilizados no mercado,
o mais adequado para ser utilizado no projeto é o protocolo TCP. Este protocolo é o único
dos dois que garante que todos os critérios do projeto sejam atendidos, enquanto o UDP
poderia causar algumas falhas na transmissão de mensagens entre os dois pontos da
comunicação.
2.2 – Comunicação TCP
Para realizar a comunicação através do protocolo de transporte TCP, foi utilizada
a interface sockets. Foram desenvolvidas duas rotinas que utilizam esta interface: um para
o sistema e outro para controlar as operações do usuário.
O programa para o sistema foi desenvolvido utilizando a linguagem de
programação Python. O software desenvolvido tem como objetivo funcionar como um
receptor, onde este deve estar atento a qualquer tentativa de conexão, independentemente
da origem. Para isto, basta que aquele que está tentando entrar em contanto com o sistema
saiba o proxy e a porta de entrada deste. Como algumas portas já são consideradas
reservadas, elas não podem ser utilizadas, como é o caso da porta 80 de HTTP. Portanto,
12
houve a necessidade de utilizar uma porta que não estava sendo utilizada para outra
função. Por isso, foi escolhida a porta 5005. A Tabela 2.2 mostra todas as portas oficiais
mais utilizadas que são consideradas reservadas e quais são as suas funcionalidades. As
portas reservadas pelo sistema são as portas cujos números vão desde 0 até 1023.
Tabela 2.2 – Portas oficiais mais conhecidas.
Porta Funcionalidade
21 FTP
22 SSH
23 Telnet
25 SMTP
53 DNS
80 HTTP
110 POP3
119 NNTP
123 NTP
143 IMAP
161 SNMP
194 IRC
443 HTTPS
Através da biblioteca sockets, é possível configurar o protocolo de transporte. A
primeira etapa da configuração é determinar o IP, a porta de acesso e o tamanho do buffer.
Para garantir o protocolo TCP, pode-se utilizar as configurações AF_INET e
SOCK_STREAM conforme mostrado no Algoritmo 1. Após isso, associa-se o socket ao
proxy e à porta desejados, utilizando o método bind. Para iniciar o receptor, foi utilizado
o método listen, para que ele fique atento a qualquer tipo de conexão que esteja tentando
ser estabelecida com o dispositivo. Para aceitar a conexão, basta utilizar o método accept.
Algoritmo 1- Exemplo de configuração de TCP
host = ''
porta = 5005
endereco = (host,porta)
buf = 1024
configure socket(AF_INET, SOCK_STREAM) #configura o socket
bind(endereco)
listen
imprime "Esperando receber mensagens..."
conexao,endereco = accept (conexão) #aceita as mensagens do usuário
imprime 'Endereço de conexão:',endereco
13
Para receber dados através da interface socket, utiliza-se o método recv. Os dados
recebidos são de tamanho igual ao do buffer. O tamanho do buffer especificado neste
projeto foi de 1024 bytes. Para enviar, basta utilizar o método send, onde o único
parametro necessário é o dado a ser enviado. O Algoritmo 2 demonstra um exemplo de
utilização para realizar a comunicação utilizando Python e o protocolo TCP, onde o loop
somente termina quando não houver mais dados recebidos.
Algoritmo 2 Exemplo de envio do protocolo TCP
loop: #loop até não haver mais dados
data = recv(tamanho do buffer)
se nao data: termina loop
imprime “informação recebida:”, data
send(data) #envia a mesma informação recebida anteriormente
fecha conexao
Deve-se fechar o conector após sua utilização, para garantir que a porta esteja
disponível para outras aplicações. Uma boa prática do three-way handshake seria utilizar
dois métodos de envio e um de recebimento, no caso do cliente, e dois métodos de
recebimento e um de envio, no caso do servidor.
O sistema de alimentação desenvolvido espera no modo standby até receber uma
tentativa de conexão, que sempre será aprovada independente da origem, por motivos de
usabilidade. Após a conexão estabelecida, ou seja, realizado o processo do three-way
handshake, o sistema envia, no formato de uma linha de texto, todos os horários de
alimentação para o cliente. Ao término do envio, o alimentador entra em modo de
receptor, onde espera um comando do usuário conforme ilustrado no fluxograma da
Figura 2.4. Os comandos possíveis são descritos na Tabela 2.3.
Ao receber um dado, o sistema receptor separa o texto por espaços, verificando se
a primeira palavra corresponde a qualquer um dos comandos da Tabela 2.3. Caso o
comando diagnosticado não corresponda a nenhum dos comandos da tabela, o sistema
simplesmente ignora o comando. Porém, caso seja um dos comandos da tabela, o sistema
realiza a sua funcionalidade correspondente e depois envia uma resposta para o usuário.
Esta resposta para todos os comandos reconhecidos pelo alimentador é a lista de horários
de alimentação após a realização da funcionalidade associada à aquele comando, exceto
para o comando “feed” que envia como resposta a mensagem “OK”.
14
Figura 2.4 – Fluxograma do estabelecimento da comunicação utilizando TCP.
Tabela 2.3 – Comandos e funcionalidades do sistema de alimentação.
O Algoritmo 3 demonstra como o vetor de horários é transformado em uma
variável do tipo string que será enviada como resposta após o recebimento de todos os
comandos menos o comando “feed”, onde os horários de alimentação são separados por
espaços. Por exemplo, caso a string enviada ou sua primeira palavra seja igual a palavra
Comandos Primeiro
parâmetro
Segundo
parâmetro
Comando
completo
Funcionalidade
feed feed Ativa o alimentador no
mesmo instante
add_time newTime add_time
newTime
Adiciona um novo horário de
alimentação
remove_time oldTime remove_time
oldTime
Remove um horário de
alimentação
change_time oldTime newTime change_time
oldTime newTime
Substitui um horário de
alimentação por outro
exit exit Encerra conexão
15
feed, o comando de alimentação será ativado e a mensagem “OK” será enviada como
resposta. O comando “feed” ativa o motor do sistema para liberar certa quantidade de
ração. Caso sejam enviados mais parâmetros junto com o comando, eles serão ignorados
e não afetarão o resultado final.
O comando “add_time” adiciona um horário não existente ao alimentador e
necessita de um parâmetro: o novo horário de alimentação. Já o comando “remove_time”
remove um horário de alimentação da lista de horários. O comando “change_time” é o
único comando que necessita dois parâmetros: um horário que será removido e outro que
será adicionado. O último comando “exit” encerra a conexão entre o sistema e o cliente.
Algoritmo 3 Transformação de um vetor de horários em uma string
função stringHorarios(vetorHorarios):
strHora = “”
para cada itemHorario em vetorHorarios:
strHora concatena valor string(itemHorario)
retorna strHora
O sistema encerra a conexão ao detectar que se passaram 600 segundos após o
início da conexão sem recebimento de dados significativos, conforme mostrado no
Algoritmo 4. Isto garante que timeouts sejam levados em consideração, e permite que o
programa não fique aguardando por respostas que não têm possibilidade de chegar.
Algoritmo 4 Recebimento de comandos através de protocolo TCP
loop: #permanece sempre ativo para conexões
imprime (“Esperando receber mensagens...”)
aceita conexão
imprime (“Endereço da conexão: ”, endereço)
tempo corrido = 0
enquanto (tempo corrido <= 600) # tempo de timeout igual a 600 segundos
se é a primeira vez que está se conectando com o dispositivo:
envia(stringHorarios(vetorHorarios))
(data,endereco) = recebe do cliente(buf)
imprime (“Mensagem recebida: “ + data)
data = divive por espaços(data)
dorme(1 segundo) #põe uma pausa de 1 segundo para evitar crash
se possui data:
se comando de data é “feed”:
ativa servomotor
envia confirmação
se comando de data é “exit”:
quebra loop de timeout
16
se comando de data é “add_time”:
#executa o comando “add_time”
#passa para o método o vetor e o novo horário
adiciona horário
envia vetor de horários pro cliente
se comando de data é “remove_time”:
#executa o comando “remove_time”
#passa para o método o vetor e o horário a remover
remove horário
envia vetor de horários pro cliente
se comando de data é “change_time”:
#executa o comando “change_time”
#passa o vetor, horário antigo e o novo horário
troca horário
incrementa tempo corrido
Este programa se comunica com o dispositivo Raspberry Pi 2 Modelo B,
utilizando a interface sockets através do uso dos parametros proxy e porta, que lhe são
informados através do uso da API Weaved, que também será melhor discutida mais
adiante neste capítulo.
Após estabelecida a conexão, o programa cliente espera o envio do texto que
define a lista de horários de alimentação. O programa cliente, então, mostra os horários
de alimentação para o usuário e depois exibe um menu. Após a exibição do menu, o
programa pede ao usuário um comando a ser enviado para o sistema. O programa cliente
também possui um contador de tempo de timeout de 600 segundos que, após expirado,
libera o uso da porta para a tentativa de outra conexão.
O método connect, descrito no Algoritmo 5, estabelece a conexão e retorna uma
variável booleana que informa o resultado da operação: true (caso a conexão seja
estabelecida) ou false (caso a conexão não seja estabelecida). Para encerrar a conexão, foi
criado o método close mostrado no Algoritmo 6, cujo propósito é encerrar a interface
sockets, o leitor de dados e o escritor de dados. Este método retorna true caso a operação
seja bem sucedida, ou false caso ocorra algum erro durante a operação.
Algoritmo 5 Métodos connect
método connect:
se porta e proxy definidos:
17
tenta:
abre socket
aguarda recebimento de dados do socket
lê dados que chegam do socket vetor de horários = divide por espaços(dados recebidos)
retorna sucesso
caso erro:
retorna falha
caso não:
retorna falha
Algoritmo 6 Métodos close
método close :
# é necessário encerrar tudo o que não for mais necessário para evitar erros em outras
# aplicações e problemas como alocação de memória
tenta:
encerra leitor de dados
encerra escritor de dados
encerra comunicação via socket
retorna sucesso
caso erro:
retorna falha
Para a realização do envio dos comandos de alimentar imediatamente, adicionar
um horário de alimentação, remover um horário de alimentação e alterar um horário de
alimentação foram criados os métodos “sendFeed”, “addTime”, “removeTime” e
“changeTime”, respectivamente, os quais estão descritos no Algoritmo 7. Os três métodos
retornam true quando a operação foi bem sucedida, e false quando a operação não pôde
ser realizada.
Algoritmo 7 Métodos sendFeed, addTime, removeTime e changeTime
método sendFeed:
se conectado
tenta:
configura escritor de dados
envia o comando de alimentação
configura leitor de dados
espera dados do alimentador
lê dados
retorna sucesso
caso erro:
retorna falha
retorna falha
18
método addTime:
se conectado:
tenta:
configura escritor de dados
envia o comando de adicionar horários + novo horário
configura leitor de dados
espera dados do alimentador
lê dados
retorna sucesso
caso erro:
retorna falha
caso não:
retorna falha
método removeTime:
se conectado:
tenta:
configura escritor de dados
envia o comando de remover horário + horário antigo
configura leitor de dados
espera dados do alimentador
lê dados
retorna sucesso
caso erro:
retorna falha
caso não:
retorna falha
método changeTime:
se conectado:
tenta:
configura escritor de dados
envia o comando de remover horário + horário a ser trocado
+ horário novo
configura leitor de dados
espera dados do alimentador
lê dados
retorna sucesso
caso erro:
retorna falha
caso não:
retorna falha
O último método “logout”, descrito no Algoritmo 8, manda uma mensagem com
o comando exit para o sistema e depois executa o método close. Caso consiga realizar seu
propósito, retorna true, se não, retorna false.
19
Algoritmo 8 Método logout
método logout:
se conectado:
tenta:
configura escritor de dados
envia o comando de saída
chama e retorna método close
caso erro:
retorna falha
caso não:
retorna falha
2.3 – API Weaved
Já que a proposta do projeto é elaborar um sistema que seja de fácil instalação,
configuração e utilização, o usuário não deveria possuir a obrigação de entrar em contato
com um técnico, ou possuir conhecimentos prévios de redes de computadores para
configurar seu sistema. Por isso, o projeto tem como um dos seus objetivos facilitar a vida
do usuário ao ponto que este tenha somente que ligar o sistema a uma rede wi-fi e, depois,
não realizar mais nenhuma configuração, além de programar os horários de alimentação.
Para garantir que o usuário não necessite entender sobre conceitos de rede, como
VPN ou port forwarding, foi decidido usar uma API desenvolvida pela empresa remot3.it,
que permite administrar dispositivos remotos, sem que haja necessidade de realizar
nenhum dos dois procedimentos mencionados acima. A API Weaved permite que um
sistema possa estar disponível para comunicação independente da rede de internet ao qual
o sistema esteja conectado, e sem realizar processos externos ao sistema, como é o caso
de VPN ou port forwarding. Para isto, foi necessário criar um nome para o serviço
solicitado, determinar o tipo de configuração a ser instalada para esse serviço, e apontar
a porta que será utilizada. Neste caso, a configuração escolhida foi a de uma porta TCP
personalizada, criada a partir do uso da API Weaved. Vale ressaltar que um dispositivo
pode estar associado a vários tipos de configurações.
O serviço disponibilizado pela empresa remot3.it requer que seja instalado um
programa agente no dispositivo desejado. O programa, ao ser iniciado, se comunica com
o serviço da empresa remot3.it que, ao receber uma confirmação do dispositivo que está
conectado a internet, aloca em seu servidor um proxy e uma porta de acesso que estarão
20
associados a esse dispositivo e a configuração em questão. Assim, o programa cliente
consegue estabelecer uma conexão e enviar dados, que serão redirecionados pelo serviço
para o dispositivo, utilizando o proxy e a porta obtidos através do uso da API.
O uso da API Weaved necessita a realização de uma configuração de
complexidade baixa, enquanto que port forwarding e VPN seriam processos
extremamente mais complicados. Port forwarding ou redirecionamento de portas, é uma
configuração que permite que dispositivos que possuam um IP privado, ou seja, não
conseguem ser descobertos por máquinas fora da rede local, podem ser acessados através
de um redirecionamento realizado pelo roteador [3]. Assim, para iniciar uma troca de
informações utilizando o protocolo TCP com o alimentador, seria necessário configurar
um roteador com NAT (Network Address Translation) habilitado, para este poder
redirecionar a comunicação para a placa Raspberry Pi [4]. Um exemplo de uso de
redirecionamento de portas é ilustrado na Figura 2.5.
VPN ou Virtual Private Network, por outro lado, é uma rede de comunicações
privada que é construída acima da camada de comunicação pública, onde seu tráfego de
dados é realizado utilizando protocolos padrão [5]. Para isto, seriam necessários
conhecimentos de criptografia e tunelamento, o que torna o produto inviável para o
usuário final, que deseja simplesmente conectar a máquina à rede e começar a usufruir de
suas funcionalidades. Um exemplo de uso de VPN está ilustrado na Figura 2.6.
Figura 2.5 – Exemplo de redirecionamento de portas.
21
Figura 2.6 – Uma rede privada virtual estende uma rede privada em uma rede pública e
permite que os usuários enviem e recebam dados em redes públicas ou compartilhadas
como se seus dispositivos de computação estivessem diretamente conectados à rede
privada.
Para o projeto, foram configurados dois tipos de instalação: um para a produção,
e outro para manutenção e desenvolvimento. Para produção, como já foi discutido antes,
foi instalada a configuração de uma porta personalizada, utilizando o protocolo TCP. Para
o desenvolvimento, foi instalada uma configuração a fim de possibilitar a utilização de
SSH ou Secure Shell. O SSH é um protocolo de rede criptografada para operar serviços
de rede com segurança, através de uma rede insegura. O SSH permite que seja possível
alterar configurações, arquivos ou utilizar rotinas do sistema alvo a partir de outro sistema
ou computador. O sistema de alimentação foi configurado para permitir a utilização do
SSH com a ajuda do API Weaved [6].
A API Weaved foi desenvolvida para ser utilizada com Python ou cURL. Como
o programa cliente foi desenvolvido em Java, foi necessário reescrever o código
disponível na documentação da API em Java, levando em conta as diferenças das
linguagens Python e Java, e modificá-lo para ficar adequado ao projeto. Os métodos
disponíveis da API são login, list all, send e connect. Durante a realização desse projeto,
somente o método send não foi utilizado, pois permite somente enviar dados utilizando o
protocolo UDP. Conforme discutido na Seção 2.2, esse protocolo apresenta
22
especificações que se mostraram inadequadas para este projeto, quando comparado ao
protocolo TCP.
O método login, descrito no Algoritmo 9, valida o usuário no sistema de serviços
remot3.it. O serviço utiliza o protocolo de autorização OAuth: após uma validação bem
sucedida no sistema, o programa cliente recebe um token que é utilizado como parâmetro
de entrada para todos os métodos a serem utilizados no sistema [7]. Este token passa a ser
inválido após um intervalo de tempo, o que força o usuário a realizar a validação
novamente caso necessite realizar mais operações. Isto garante uma camada adicional de
segurança na comunicação com o alimentador.
Algoritmo 9 Método login
método login:
tenta:
configura conexão (HTTP)
HTTP <- método GET
HTTP <- atribui propriedades e chave de API
envia requerimento (HTTP)
retorna token
caso erro:
retorna falha
O serviço remot3.it permite que cada usuário tenha vários dispositivos, onde cada
dispositivo pode ter vários serviços atrelados a ele. O alimentador desenvolvido, por
exemplo, possui dois serviços atrelados ao dispositivo Raspberry Pi 2 Modelo B: o SSH
e uma porta personalizada de protocolo TCP. Após o recebimento do token, é necessário
identificar com qual dispositivo se iniciará a comunicação, e qual o tipo de serviço que
será utilizado para realizá-la. Para identificar ambos, dispositivo e serviço, foi utilizado o
método list all, descrito no Algoritmo 10, que recebe o token providenciado pelo método
login e retorna a lista de todos os dispositivos e seus serviços como um objeto do tipo
JSON, onde cada serviço é identificado por um número identificador.
Algoritmo 10 Método list all
método list all:
caso já tenha token:
tenta:
configura conexão (HTTP)
HTTP <- método GET
HTTP <- atribui propriedades, chave de API e token
envia requerimento (HTTP)
# a resposta do protocolo é um objeto JSON, logo
23
# é necessário utilizar o método parse para poder analisar a
# resposta como um objeto do tipo JSON
lista de dispositivos = parse (resposta do protocolo) # JSON
dispositivo alvo = procura dispositivo (lista de dispositivos,
nome)
id = procura serviço (dispositivo alvo)
caso erro:
retorna falha
caso sem token:
retorna falha
O último método utilizado da API é o método connect. Este método, descrito no
Algoritmo 11, envia uma requisição do tipo “POST”, enquanto os outros métodos enviam
requisições do tipo “GET”. Aplicações “GET” possuem todos os dados incluidos no URL
da aplicação, enquanto as do tipo “POST” requerem que certos dados sejam transmitidos
na mensagem de corpo da requisição HTTP.
O método connect, ao ser executado, retorna as informações de proxy e da porta
de rede, que serão utilizadas para iniciar a comunicação com a placa Raspberry Pi do
sistema alimentador através do uso do protocolo TCP. Este utiliza o token e o número de
identificação do serviço obtidos anteriormente nos métodos login e list all,
respectivamente, além do IP do dispositivo do usuário, como parâmetros de entrada.
Algoritmo 11 Método connect
método connect:
caso já tenha token e id:
tenta:
configura conexão (HTTP)
HTTP <- método POST
HTTP <- atribui propriedades, chave de API e token
HTTP <- configura corpo do proxy(id, IP)
envia requerimento (HTTP)
retorna porta e proxy
caso erro:
retorna falha
caso não tenha:
retorna falha
24
A Fígura 2.7 ilustra o fluxograma de chamados do programa cliente ao serviço
remot3.it, utilizando-se a API Weaved para conseguir o proxy e a porta de acesso
associados ao alimentador. Já a Figura 2.8 descreve através de um fluxograma como é
realizada a comunicação completa entre o programa cliente e o alimentador, incluindo a
comunicação inicial necessária com o serviço remot3.it.
Figura 2.7 – Fluxograma do uso da API Weaved.
Figura 2.8 – Fluxograma da comunicação entre o programa cliente e o
alimentador.
Os serviços providenciados pelo remot3.it devem estar sempre ativos. Para que
isto seja possível, tais serviços devem reiniciar após finalizar o processo de boot da placa
Raspberry Pi. Para alterar a lista de tarefas agendadas do sistema, deve-se abrir uma janela
do terminal e utilizar o comando “crontab -e” para iniciar o programa UNIX“crontab”.
Este programa edita o arquivo onde são especificados os comandos a serem iniciados nos
horários determinados. Quando o sistema iniciar ou reiniciar, os scripts associados a esses
25
serviços iniciarão com permissões de um “super usuário”, caso o arquivo possua um
registro igual ao do exemplo:
@reboot sudo /usr/bin/nome_do_servico.sh start | stop | restart
26
Capítulo 3
Configurações do ambiente
Em este capítulo se explica como a placa Raspberry Pi 2 Modelo B foi
configurada para poder controlar o alimentador. Ele é dividido em quatro partes: a
configuração inicial da placa para poder ser programada (Seção 3.1), a forma de
armazenamento da lista de horários de alimentação e como esses dados são tratados
(Seção 3.2), como a alimentação automática é realizada (Seção 3.3) e como o servo
motor é controlado (Seção 3.4).
3.1 – Configuração e instalação da placa Raspberry Pi
A placa Raspberry Pi é um computador de pequeno porte desenvolvido pela
Fundação Raspberry Pi no Reino Unido. Diferentemente do Arduino, uma placa popular
de uso comum para prototipagem no mercado, a placa Raspberry Pi possui um sistema
operacional. Porém, somente consegue lidar nativamente com entradas e saídas digitais
(o Arduino, por outro lado, consegue lidar com ambos tipos de saídas: digitais e
analógicas).
A placa Raspberry Pi consegue utilizar vários sistemas operacionais disponíveis
no mercado, como Ubuntu ou Windows 10, porém, foi utilizado o sistema operacional
Raspbian, um sistema operacional baseado em Debian, cujo sistema alvo é a própria placa
Raspberry Pi. Para utilizar este sistema operacional, é necessário instalar um arquivo de
extensão ISO, que contém a versão desejada do sistema operacional, em um cartão SD.
Como, a princípio, não é possível realizar o desenvolvimento de software na placa
remotamente, é necessário utilizar um monitor, um cabo HDMI para ligar o monitor à
placa, um mouse e um teclado para configurar o protocolo SSH. A próxima etapa requer
que o cartão SD seja inserido na entrada correspondente da placa e que o processo de boot
seja iniciado, quando, então, o sistema operacional poderá ser instalado, e as
configurações necessárias poderão ser realizadas [8].
A placa Raspberry Pi 2 Modelo B requer que o usuário escolha um fuso horário
para realizar as configurações, de modo a garantir que o relógio marque a hora correta da
27
região onde ela está sendo utilizada. O fuso horário da cidade do Rio de Janeiro foi
utilizado neste projeto para garantir que os horários de alimentação sejam respeitados.
Além disso, é necessário definir uma senha e um nome de usuário para o dispositivo. As
credenciais serão utilizadas para validar o usuário, quando ele desejar utilizar o recurso
SSH, caso habilitado. A Figura 3.1 ilustra o menu de configuração a que o usuário possui
acesso. Este menu pode ser acessado durante o boot ou após a inicialização da interface
com o usuário.
Figura 3.1 – Configuração da Raspberry Pi (raspi-config).
O sistema operacional Raspbian já possui as ferramentas da linguagem de
programação Python instaladas, porém, é necessário atualizá-las e também realizar todas
as demais atualizações pendentes do dispositivo, para garantir que todas as bibliotecas,
métodos e funcionalidades da placa Raspberry Pi operem da mesma forma que está escrito
na documentação. Isto pode ser realizado utilizando o comando “apt-get update”.
Também é necessário configurar uma conexão utilizando uma rede wi-fi ou um cabo
Ethernet ligado à placa para realizar as atualizações e ter acesso a outras funcionalidades
da web, entre elas, a comunicação utilizando o protocolo TCP, e utilizar a porta 80 para
acessar a interface HTTP. Para configurar uma conexão utilizando uma rede wi-fi na
placa Raspberry Pi, é necessário um adaptador para tal fim, depois deve-se pressionar o
ícone de rede no menu da interface gráfica, selecionar a rede a ser utilizada e, caso
necessário, entrar com as credenciais de acesso.
28
3.2 – Armazenamento da lista de horários de alimentação
Os horários de alimentação foram armazenados em um arquivo de texto para
garantir que todos os scripts do alimentador tivessem acesso aos horários de alimentação
e que, caso ocorra alguma falha na execução dos scripts ou na operação do alimentador,
os horários de alimentação ainda estejam armazenados no sistema. Embora outros
métodos de armazenar informações pudessem ter sido implementados, como um banco
de dados local SQLite, não havia necessidade de utilizar um método mais complexo. O
modelo em questão trata apenas de um tipo de dados e, geralmente, o sistema trabalharia
com poucos dados, o que torna o uso de um arquivo de texto mais do que suficiente para
o trabalho a ser realizado.
Foram criados cinco métodos para manipulação do arquivo texto:
“readFromFile”, “writeToFile”, “addTime”, “removeTime”, “changeTime”. Cada
método é responsável por uma funcionalidade relacionada aos horários da lista
armazenados no arquivo.
O primeiro método, descrito no Algoritmo 12, lê do arquivo a lista de horários
que estão no formato HHmm (“horaminutos”). Após ler o arquivo linha por linha, insere
cada horário dentro de um vetor que é retornado como parâmetro de saída.
O método “writeToFile”, também descrito no Algoritmo 12, só é utilizado por
outros métodos deste script. Ele recebe como parâmetro de entrada um vetor de horários
e escreve esses horários no arquivo texto.
Algoritmo 12 Métodos readFromFile e writeToFile
método readFromFile:
arquivo = abrir(caminho do arquivo)
vetor de horários = ler linha por linha (arquivo)
fechar (arquivo)
retorna (vetor de horários)
método writeToFile:
arquivo = abrir(caminho do arquivo)
para cada horário do vetor de horários:
escrever no arquivo(horário)
fechar (arquivo)
Os últimos três métodos alteram o arquivo que armazena os horários de
alimentação do sistema e são descritos no Algoritmo 13. O método “addTime” recebe
um novo horário de alimentação e a lista de horários como parâmetros de entrada, e
29
retorna a nova lista de horários. O método acrescenta o novo horário de alimentação na
lista, de modo que a lista de horários mantenha-se ordenada de forma crescente. Após
isso, escreve a lista no arquivo. O método “removeTime” recebe como parâmetros de
entrada a lista de horários e o horário para ser removido, e retorna como parâmetro de
saída a nova lista de horários. Esse método remove o horário da lista de horários e escreve
a lista no arquivo caso esse horário realmente pertença à lista; em caso negativo, a
operação não é realizada e a lista original é retornada. O último método, “changeTime”,
recebe três parâmetros: o horário a ser substituído, o novo horário e a lista de horários.
Este método executa primeiro o método “removeTime” para remover o horário a ser
substituído, e depois executa o método “addTime” para adicionar o horário que entrará
em seu lugar. Ele retorna como parâmetro de saída a lista de horários após as mudanças
realizadas.
Algoritmo 13 Métodos addTime, removeTime e changeTime
método addTime:
caso vetor de horários está vazio:
adiciona no vetor de horários (novo horário)
caso não:
encontrado = falso #determina se encontrou uma posição dentro da lista
# organiza a lista para que os horários fiquem em ordem crescente
para todos os horários do vetor de horários:
caso (horário atual > novo horário):
nova posição = posição atual no loop
encontrado = verdadeiro
quebra o loop
caso encontrado:
insere no vetor de horários (novo horário, nova posição)
caso não:
insere no final do vetor de horários (novo horário)
writeToFile (vetor de horários) #chama o método writeToFile
retorna vetor de horários
método removeTime:
tenta:
remover do vetor de horários (horário antigo)
writeToFile (vetor de horários)
caso erro: #horário não pertence ao vetor de horários
imprime (“Não existe esse horário)
retorna vetor de horários
método changeTime:
30
tenta:
#chama os métodos anteriores na seguinte ordem:
removeTime (horário antigo)
addTime (horário novo)
retorna vetor de horário
3.3 – Administração de horários de alimentação
Para garantir que os horários de alimentação sejam sempre cumpridos de forma
mais eficiente, deve-se garantir que: um horário de alimentação seja cumprido somente
uma vez por dia e que um horário de alimentação nunca passe despercebido, desde que o
sistema esteja em modo funcional. Desta forma, foi necessário criar um script para
comparar o horário atual com a lista de horários determinada pelo usuário, que está
armazenada no sistema de alimentação de animais domésticos. A partir desta análise, o
sistema deve chegar a uma decisão: caso o horário atual seja igual a algum dos horários
da lista de alimentação, o servomotor entrará em modo ativo e colocará ração no pote do
animal, em caso negativo, nenhuma ação será tomada.
Na linguagem de programação Python, o método que aguarda uma conexão via
protocolo TCP trabalha de forma síncrona, ou seja, não consegue realizar operações em
paralelo enquanto espera uma tentativa de conexão. Como não é possível verificar os
horários de alimentação e aguardar uma tentativa de comunicação utilizando o protocolo
TCP ao mesmo tempo em um mesmo script, foi necessário dividir as tarefas em dois
scripts diferentes. Logo, para garantir que ambos os scripts funcionem da forma correta,
é necessário que qualquer mudança nos horários seja armazenada o mais rápido possível
no arquivo de texto, para que ambos os scripts operem com os mesmos horários de
alimentação.
Algoritmo 14 Alimentação a partir dos horários programados
enquanto sistema ativo:
vetor de horários = readFromFile #chama o método de leitura do arquivo texto
para cada horário da lista:
caso horário atual = horário da lista:
ativa alimentador
#evita do sistema realizar esta operação desnecessariamente
dorme (60 segundos)
31
Como o sistema trabalha com uma precisão de tempo em minutos, a cada sessenta
segundos, a lista de horários é lida utilizando o método “readFromFile” para,
posteriormente, aferir se o horário atual é igual a algum dos horários do vetor de horários
que foi recebido através da leitura do arquivo texto. Caso algum dos horários seja igual,
o sistema ativa o servomotor e, consequentemente, enche o pote de comida de ração para
o animal. Todo este processo foi programado conforme o pseudocódigo do Algoritmo 14.
Foram desconsiderados os segundos dos horários de alimentação (os horários são
compostos somente de horas e minutos) para facilitar o processamento do programa. Caso
fossem considerados os segundos, haveria que checar a cada segundo os horários e isso
poderia sobrecarregar a placa Raspberry Pi 2 Modelo B desnecessariamente, já que uma
precisão de segundos normalmente não faz muita diferença para os usuários deste
produto. O fluxograma que explica como é realizada a alimentação programada está
ilustrado na Figura 3.2.
Figura 3.2 – Fluxograma de operação da alimentação programada.
Para garantir que este script sempre estará ativo após o boot do sistema, foi
necessário adicionar seu início ao arquivo “crontab”, conforme abaixo:
@reboot/usr/bin/python /home/pi/CatFeeder/server/feeder_timer.py
32
3.4 – Controlador do servomotor
A placa Raspberry Pi 2 Modelo B vem com um conector GPIO, que pode ser utilizado
para controlar como funcionam os pinos da placa e suas saídas e entradas de dados. O
conector GPIO possui uma grande variedade de conexões disponíveis, entre elas:
• True GPIO: conexão que funciona em dois estados (ligado e desligado, ou falso e
verdadeiro). Utilizado para acender LEDs, por exemplo;
• I2C: permite a conexão com módulos de hardware utilizando apenas dois pinos;
• SPI ou Serial Peripheral Interface: permite que o microcontrolador se comunique
com diversos outros componentes, formando uma rede. Esta especificação de
interface de comunicação série síncrona é usada para comunicação de curta
distância [9];
• Rx e Tx serial: pinos para comunicação periférica.
Alguns dos pinos podem, inclusive, ser utilizados para controle utilizando a técnica
PWM, permitindo controlar um servomotor. Entretanto, existe uma maior dificuldade
para controlar a frequência dos servomotores e sua velocidade, já que a placa Raspberry
Pi 2 Modelo B somente consegue entender entradas e saídas digitais, diferentemente do
Arduino, por exemplo, que consegue lidar com saídas e entradas analógicas.
Por esta razão, foi decidido utilizar um controlador PWM da Adafruit: Adafruit
16-Channel 12-bit PWM Servo Driver [10], mostrado na fotografia da Figura 3.3. Para
sua utilização, foi necessário configurar os pinos de GPIO e I2C.
Figura 3.3 – Controlador PWM da Adafruit.
33
Através da biblioteca Rpi.GPIO é possível controlar as portas GPIO utilizando a
linguagem de programação Python. Essas portas digitais possuem dois estados (3.3 V ou
HIGH e 0 V ou LOW), que podem ser controlados através do desenvolvimento de rotinas.
No sistema operacional Raspbian, ou qualquer outro sistema semelhante à base de UNIX,
os comandos descritos a seguir atualizam o sistema, instalam o kit de desenvolvimento
para Python e a biblioteca Rpi.GPIO, respectivamente:
I2C é uma interface utilizada para permitir a comunicação entre circuitos
integrados. Através do uso de I2C, a placa Raspberry Pi 2 Modelo B consegue se conectar
com o controlador PWM da Adafruit. A interface I2C permite que diversos dispositivos
estejam conectados à placa, cada um com um endereço único, que podem ser modificados
através de uma alteração nas configurações do módulo. Para instalar os utensílios
associados ao I2C, são utilizados os seguintes comandos no terminal:
A fim de configurar o suporte do kernel, deve-se rodar o comando sudo raspi-
config. Este comando abrirá a interface de configurações da placa apresentada
anteriormente e permitirá mudar as configurações do ARM core e do kernel linux. Para
habilitar a interface I2C, deve-se escolher a opção “Opções avançadas”, depois “I2C” e,
por fim, permitir que a interface ARM I2C seja habilitada e que o modulo kernel I2C seja
carregado por padrão. Após isso, será necessária uma reinicialização do sistema. O
comando sudo i2cdetect -y 0 mostra os endereços I2C em uso, onde os endereços da
placa que podem ser utilizados por padrão são 0x40 e 0x70.
Para fixar a conexão entre a placa Raspberry Pi e o controlador PWM da Adafruit,
utilizaram-se quatro pinos da placa: pino 1, pino 3, pino 5 e pino 6. O pino 1 corresponde
ao pino de saída de tensão baixa da placa, ou seja, 3.3 V. O pino 3 e o pino 5 são os pinos
sudo apt-get install -y python-smbus
sudo apt-get install -y i2c-tools
sudo apt-get update
sudo apt-get install python-dev
sudo apt-get install python-rpi.gpio
34
da funcionalidade I2C, onde o pino 3 é o pino SDA ou Serial Data Line e o pino 5 o de
SCL ou Serial Clock Line. O último pino, o 6, corresponde ao ground. Todas as saídas
utilizadas foram ligadas às suas correspondentes no controlador PWM da Adafruit, e o
servomotor foi ligado ao canal 0 do controlador.
Cada pino na placa possui um número que é utilizado pela biblioteca Rpi.GPIO,
para identificar a qual pino o programa está se referindo. Os pinos, seus números de
identificação e as funcionalidades atreladas a cada um, estão representados na Figura 3.4.
Para não sobrecarregar a placa Raspberry Pi 2 Modelo B, utilizaram-se duas fontes
de alimentação: uma para a placa Raspberry Pi 2 Modelo B, e a outra para o controlador
PWM da Adafruit. Caso não fosse utilizada uma fonte externa específica para o
controlador da Adafruit, a fonte do Raspberry Pi poderia desligar ou queimar ao atingir
um nível de sobrecarga. A Figura 3.5 mostra uma foto do circuito interno do sistema que
controla o alimentador e a Figura 3.6 ilustra o esquema elétrico do alimentador.
Figura 3.4 – Pinos GPIO da placa Raspberry Pi 2 Modelo B.
35
Figura 3.5 – Montagem do circuito do sistema.
Figura 3.6 – Esquema elétrico do alimentador.
Para conseguir desenvolver um programa para controlar o servomotor, foi
necessário adquirir o driver do controlador PWM da Adafruit. Posteriormente, foi
36
desenvolvido o software para controlar o servomotor, utilizado pelo script tanto de
comunicação, como pelo que administra os tempos de alimentação. Este software está
apresentado no Algoritmo 15. O endereço e a frequência atribuídos ao controlador foram,
respectivamente, 0x40 e 60 Hz.
Algoritmo 15 Controle do servomotor
valor mínimo do tique = 150 # 610 μs
valor máximo do tique = 600 # 2440 μs
valor de parada do servomotor = 0
canal = 0
iniciar pwm com frequência (60)
servomotor (canal,0,valor máximo do tique)
dorme (1) #este script não realiza nada durante a movimentação do servomotor
servomotor (canal, 0,valor do tique de parada do servomotor)
dorme (1)
servomotor (canal, 0,valor mínimo do tique)
dorme (1)
servomotor (canal, 0,valor de parada do servomotor)
dorme (1)
A função que determina o tamanho do pulso PWM do servomotor requer três
parâmetros de entrada: o canal do controlador que vai ser atualizado com os novos valores
de pulso, o número de tiques (de 0 até 4095) que determina quando o valor do pulso passa
de LOW (0 V) para HIGH (3.3 V) e o número de tiques (de 0 até 4095), necessário para
que o valor do pulso passe de HIGH para LOW[11]. Por exemplo, um servo motor no
canal 0 que tem um valor de número de tiques inicial igual a 1024 e um valor de número
de tiques final igual a 3072, transicionará do valor LOW para HIGH após percorrer 25%
do pulso (tique 1024 de 4096) e passará de HIGH para LOW após percorrer 75% do pulso
(tique 3072 de 4096). A partir do valor da duração do ciclo é possível encontrar o valor
do comprimento do pulso. O valor da duração do ciclo é obtido através de (3.1), onde c é
a duração do ciclo e f é a frequência do pulso. Através de (3.2), pode-se obter o tempo
por tique representado por t.
𝑐 =
1
𝑓 (3.1)
𝑡 = 𝑐
4096
(3.2)
37
A partir de (3.3), pode-se obter o comprimento do pulso, onde x é o tique e λ é o
comprimento do pulso.
𝜆 = (𝑥𝑓𝑖𝑛𝑎𝑙 − 𝑥𝑖𝑛𝑖𝑐𝑖𝑎𝑙) ∗ 𝑡 (3.3)
Adotando o valor do tique inicial como 0, é possível encontrar o valor do
comprimento do pulso utilizando (3.4).
𝜆 = 𝑥 ∗ 𝑡 (3.4)
Os valores utilizados foram de 150 tiques e 600 tiques, o que correspondem aos
comprimentos de pulso de valores 610 μs e 2440 μs, respectivamente, obtidos através do
uso de (3.4). O comprimento de pulso de 610 μs faz o servomotor girar na direção
contrária ao do valor de tique de 2440 μs, garantindo que a engrenagem não trave caso
algum pedaço de ração fique preso entre a engrenagem e a lateral do reservatório. Quando
os scripts ativam o servomotor, o controlador é chamado para ativar o canal 0 (canal
escolhido para utilização do servomotor) com um pulso de comprimento igual a 2440 μs,
retorna ao valor de comprimento de pulso igual a 0 e sobe até um valor igual a 610 μs,
recomeçando posteriormente. Cada subida ou descida tem duração de um segundo. De
forma a garantir a utilização ideal de servomotores foi realizado um estudo a partir do
livro [12]. A Figura 3.7 detalha através do uso de um fluxograma como é realizado o
controle do dispensador. Além disso, o código-fonte da configuração do alimentador se
encontra no Apêndice B.
Figura 3.7 – Fluxograma do controle do dispensador.
38
Capítulo 4
Interfaces física e virtual
Este capítulo apresenta os elementos do projeto aos quais o usuário tem acesso
diretamente. Ele é dividido em duas partes: o desenvolvimento da interface gráfica para
o programa cliente (Seção 4.1) e a construção da estrutura do protótipo do projeto
(Seção 4.2).
4.1 – Interface gráfica destinada ao usuário
A interface gráfica do programa cliente foi desenvolvida em Java, com a utilização
da biblioteca Swing. Seu desenvolvimento seguiu as boas práticas de design: fácil
navegação, pouca necessidade de explicação ou conhecimento prévio sobre o assunto e
um padrão de design que seja confortável ao usuário.
Ao iniciar o programa, o usuário é direcionado para a interface de login, que é um
simples pop-up onde será requisitado do usuário seu email e senha cadastrados no
sistema, conforme mostrado na Figura 4.1. Há apenas dois possíveis resultados (sucesso
e erro) ao tentar realizar o log in no sistema. Para o caso de erro, o conceito de Design
Thinking nos força a retornar uma mensagem de resposta amigável ao usuário. Se o
usuário inseriu seu email ou senha incorretamente, ele será informado que o usuário é
inválido, conforme mostrado na Figura 4.2. Caso o sistema esteja indisponível, seja por
mau funcionamento do AD ou Active Directory do sistema remot3.it, sistema em
manutenção ou simplesmente fora do ar, o usuário será informado que o sistema não está
disponível neste momento e que ele deve tentar a conexão mais tarde. O Algoritmo 16
demonstra o processo de login.
Figura 4.1 – Login no programa de alimentação.
39
Figura 4.2 – Usuário não cadastrado/inválido.
Após um log in bem-sucedido, o usuário será redirecionado para a tela do menu
principal, composta por quatro botões e uma janela de texto, conforme mostrado na Figura
4.3. Este último elemento lista todos os horários de alimentação do sistema que já foram
cadastrados pelo usuário. Caso a lista esteja vazia, nenhum elemento será informado.
Figura 4.3 – Menu principal.
Algoritmo 16 Login com interface gráfica
campo texto = novo campo texto
campo de senha = novo campo de senha
pop up = criar pop up (campo texto, campo de senha)
autorizado = falso
enquanto não autorizado:
#deve-se checar através da resposta qual botão o usuário clicou
40
resposta = mostrar pop up
caso resposta = OK:
#uma condicional para cada caso, já que cada etapa precisa da outra
caso login:
caso consiga encontrar dispositivo:
caso consiga estabelecer conexão:
autorizado = verdadeiro
carrega lista de horários
carregar elementos
tornar elementos responsivos
inicia menu principal
caso erro para qualquer dos métodos de conexão:
mostrar alerta de erro
caso resposta != OK:
fecha interface
Cada botão está associado a uma funcionalidade do sistema. O botão de adicionar
horário abre um pop-up de diálogo de entrada que pede ao usuário um horário no formato
horas:minutos que, após inserido, entra na lista de horários. O botão de remover horário
primeiro checa se o usuário selecionou um horário da lista, caso não o tenha feito, o
programa avisará o usuário, através de um alerta, que deve selecionar um horário para
poder prosseguir; caso o tenha feito, o horário da lista é removido. O botão de alterar
horário funciona como se fosse uma combinação dos processos necessários de ambos os
botões anteriores: o usuário deve selecionar um horário da lista antes de pressionar o
botão, e deve inserir um horário novo quando o pop-up aparecer. Quando isto ocorrer, o
horário selecionado da lista será substituido pelo novo horário. Para ativar o alimentador
imediatamente, o usuário deve pressionar o botão de ativação do alimentador. Este botão,
ao ser pressionado, inicialmente confirma se o usuário tem certeza que deseja ativar o
alimentador; em caso positivo, uma mensagem é enviada ao alimentador e o servo motor
será ativado; em caso negativo, o usuário retorna ao menu principal. O usuário também
pode realizar o log out da rede de comunicação, e fechar a interface através do uso do
botão de logout. A Figura 4.3 mostra o menu principal do sistema com uma lista de
horários criada como exemplo e os botões, cujas funcionalidades foram explicadas
previamente.
Algoritmo 17 Iniciação de elementos gráficos do menu principal
iniciar tela com nome (“Alimentador de animais domésticos)
selecionar layout (layout escolhido)
41
#os elementos foram ajustados de acordo com as dimensões da tela
dimensão x = tela.tamanhoHorizontal
dimensão y = tela.tamanhoVertical
ajustar tamanho da tela (dimensão x, dimensão y)
para cada botão:
cria botão = botão com nome (nome do botão)
#cada botão é posicionado em um sistema semelhante ao sistema cartesiano
ajustar tamanho do botão (botão, posição x, posição y, tamanho x, tamanho y)
scroll = novo campo visual de scroll
lista de horários = nova lista de texto
ajustar tamanho do scroll (scroll, posição x, posição y, tamanho x, tamanho y)
#permite utilizar o scroll caso a lista fique de forma muito grande
inserir no scroll (lista de horários)
O código que define a interface gráfica é dividido em duas partes: a inicialização
da interface gráfica e a própria interface gráfica, conforme foi mostrado no Algoritmo 16.
Ao utilizar a biblioteca Swing em Java, deve-se fazer uma chamada para inicialização da
interface gráfica, a fim de que seus elementos possam aparecer na tela para o usuário,
conforme foi mostrado no Algoritmo 17. O código da interface gráfica, por outro lado,
primeiro configura todos os elementos gráficos do menu principal, depois abre a caixa de
dialogo de login e espera uma resposta correta do usuário em relação às suas credenciais.
Caso o usuário insira suas credenciais corretamente e o serviço remot3.it esteja
disponível, a caixa de diálogo de login desaparece, a lista de horários é preenchida com
os dados recebidos pela comunicação utilizando o protocolo TCP, os elementos gráficos
da tela como botões são adicionados ao menu e estes passam a ser responsivos.
Em programação de interfaces gráficas, um evento é qualquer ação ou ocorrência
que pode ser entendida ou manipulada pelo programa. Um exemplo de um evento é
pressionar um botão, pois pode ser carategorizado como um evento do tipo click. Para ser
possível escutar e entender esses eventos, utilizamos listeners ou handlers. Listeners
conseguem processar os eventos que ocorrem durante a execução do programa e ativam
o comportamento desejado a partir do evento ocorrido.
A linguagem de programação Java consegue entender eventos de várias formas.
Para este projeto, foi criado um listener global que consegue entender todos os eventos
que ocorrem durante a execução. Para que ele funcione da forma esperada, o método
associado ao listener deve checar o tipo de evento (caso não seja especificado, adota-se o
evento do tipo click) e o elemento relacionado ao evento. A manipulação dos eventos
42
envolvidos com a operação da interface gráfica está escrita no pseudocódigo do
Algoritmo 18.
Algoritmo 18 Manipulação de eventos
método de controle de eventos:
caso origem do evento = click do botão de alimentação:
escolha = confirma com usuário usando pop up
caso escolha = OK:
manda mensagem de ativação do alimentador
resposta = espera mensagem
caso resposta = OK:
alerta de sucesso
caso erro:
alerta de erro
caso origem do evento = click do botão de adicionar horário:
novo horário = mostrar pop up pedindo novo horário
#chama método addTime
addTime (novo horário, vetor de horários)
caso origem do evento = click do botão de remover horário:
caso item selecionado:
item da lista = pega item da lista de horários
removeTime (item da lista, vetor de horários)
alerta de sucesso
caso não selecionado:
mostra alerta de aviso para o usuário
caso origem do evento = click do botão de alterar horário:
caso item selecionado:
novo horário = mostrar pop up pedindo novo horário
item da lista = pega item da lista de horários
changeTime (item da lista, novo horário, vetor de horários)
alerta de sucesso
caso não selecionado:
mostra alerta de aviso para o usuário
caso origem do evento = click do botão de logout:
#chama o método logout
logout
fecha interface
O fluxograma completo de operação do projeto se encontra no Apêndice A e o
código-fonte da interface gráfica desenvolvida para o programa cliente se encontra no
Apêndice C.
43
4.2 – Construção da estrutura do alimentador
Como o projeto se trata de um protótipo de um alimentador, sua construção foi
idealizada a partir de objetos comuns em um ambiente residencial. Por outro lado, foi
utilizada uma impressora 3D Prusa para construir a engrenagem responsável por controlar
a saída da ração, que fica armazenada dentro de um pote de plástico. A estrutura
construída é apresentada na fotografia da Figura 4.4.
Figura 4.4 – Estrutura do alimentador.
O tamanho de toda a estrutura do alimentador foi idealizado segundo o tamanho
da engrenagem já desenvolvida previamente. O cano de PVC utilizado possui uma
bifurcação, onde uma saída fica presa em uma caixa de madeira, posicionada em cima de
uma tábua de madeira, e a outra determina o fluxo da ração. Um pote de plástico com
tampa foi encaixado em cima da saída superior do cano, utilizando fita adesiva para criar
mais atrito entre o pote e o cano. Dentro do pote, fica a engrenagem, fixada por um
44
parafuso a um lado do pote. O servo motor está preso à parede contralateral por parafusos.
Na saída superior à saída inferior do cano que não está apoiada na caixa da madeira, está
posicionada uma garrafa de plástico, que leva a ração do pote para a saída correta,
garantindo que nenhuma ração fique dentro do cano ao sair do pote.
Para imprimir um objeto em 3D, é necessário modelar o objeto primeiro utilizando
um programa de modelagem como AutoCAD ou SolidWorks. O esboço do modelo 3D
da engrenagem foi criado utilizando o programa SolidWorks a partir de um plano
qualquer. No esboço apresentado na Figura 4.5 foi criado um circulo de diâmetro de 30
mm, que foi extrudado até atingir um comprimento de 90 mm. A partir de um plano
transversal a ele, criou-se um arco que parte de uma das extremidades do cilindro até o
seu centro oposto. O arco foi, então, extrudado para gerar uma espessura de 5 mm.
Depois, foi espelhado cinco vezes em volta do cilindro com uma distância de 60 graus
um do outro, para formar, assim, um total de seis arcos.
Figura 4.5 – Modelo da peça criada no SolidWorks.
A peça foi impressa utilizando poliácido láctico, sendo necessárias três tentativas
de impressão para conseguir imprimir corretamente a peça. Na primeira tentativa, a peça
não tinha uma base bem definida e, por isso, se fragmentou. Na segunda tentativa, foi
construída uma base destacável para que a peça não cedesse com o eventual
adicionamento de peso durante a sua impressão. Porém, ocorreu uma falha de energia
que, consequentemente, gerou um problema na impressão que fez com que a engrenagem
se rachasse em seu ponto médio, conforme mostrado na fotografia da Figura 4.6.
45
Figura 4.6 – Peça rachada na segunda impressão.
A peça conseguiu ser impressa na terceira tentativa, pois foi utilizado o método
da segunda tentativa, porém, não houve falha de energia. Finalmente, foi colado com cola
resistente o encaixe do servomotor na peça, conforme mostrado nas Figuras 4.7 e 4.8. O
custo total para imprimir a engrenagem, após todas as tentativas, foi 30 reais.
Figura 4.7 – Engrenagem na fase final.
47
Capítulo 5
Conclusões e pontos de aprimoramento
no futuro
O projeto conseguiu cumprir todos os objetivos discutidos na Seção 1.4, porém
existem alguns problemas que no futuro devem ser levados em consideração. O primeiro
é a necessidade de reiniciar a placa do alimentador para que ela consiga se conectar com
o serviço da empresa remot3.it, caso o alimentador esteja se conectando a uma rede de
wi-fi não previamente configurada. Outro problema é que algumas rações conseguem
passar pelo mecanismo mesmo quando ele não é acionado, caso a ração depositada possua
um tamanho muito pequeno. Além disso, o alimentador não consegue prever problemas
relacionados a diferença do horário que o usuário está utilizando como referência e o da
Raspberry Pi. Caso a interface com o usuário mostrasse o relógio da Raspberry Pi,
problemas deste tipo poderiam ser evitados.
A parte mais desafiadora do projeto foi permitir que o alimentador operasse em
qualquer rede de internet, sem a necessidade de uma configuração prévia, como um
redirecionamento de portas. Porém, embora o protótipo esteja pronto, o produto final
ainda não está finalizado: o alimentador deve passar por um rigoroso processo de
melhorias para poder, no futuro, ser vendido como um produto. Dentre essas melhorias,
pode-se citar: utilização de uma balança digital, instalação de uma câmera, chamado de
voz, desenvolvimento de um aplicativo e facilitar a conexão com uma rede wi-fi, além da
melhoria no design da estrutura, que poderia ser toda impressa em 3D.
A utilização de uma balança digital permitiria que o produto alimentasse o animal
doméstico sempre que o pote de ração estivesse vazio, ou em um nível abaixo do
esperado. O uso de uma balança no projeto também permitiria um melhor controle da
quantidade de ração necessária para preencher o pote de comida em cada comando de
alimentação, otimizando, assim, a alimentação do animal. Isto não só evitaria
desperdícios, caso o animal não tivesse comido o pote inteiro, mas também permitiria ao
usuário escolher a quantidade de ração a ser posta no pote.
48
A instalação de uma câmera compatível com a placa (como a Raspberry Pi
Câmera), permitiria o usuário receber fotos ou vídeos dos seus animais domésticos na
hora de sua alimentação. Para perceber se o animal está se alimentando ou não, bastaria
utilizar a balança do item anterior para avaliar uma possível redução no peso no pote do
animal.
O chamado de voz poderia ser implantado através da instalação de um speaker. O
speaker seria ativado sempre que o alimentador também o fosse, independente do modo
de ativação. O som emitido pelo speaker seria gravado pelo usuário e reproduzido sempre
que o pote de ração fosse preenchido.
Para facilitar a interação do usuário com o sistema de alimentação, um aplicativo
para dispositivos móveis será desenvolvido no futuro. O aplicativo permitiria uma
interação mais rápida e mais fácil de ser utilizada se o usuário estiver se locomovendo. O
aplicativo será desenvolvido utilizando a ferramenta multi-plataforma Appcelerator
Titanium.
As mudanças e melhorias propostas tornariam o projeto desenvolvido em um
produto mais singular no mercado. O alimentador passaria a possuir funcionalidades
ainda não presentes em outros alimentadores no mercado como o Automatic Pet Feeder
da Autopetfeeder ou o produto da PetLove de mesmo nome, além de outras que, embora
já existam, não se encontram em uma faixa de preço acessível a consumidores da classe
média no mercado brasileiro.
49
Bibliografia
[1] Wikipedia [homepage na internet]. TCP/IP [acesso em 16 dez 2016]. Disponível
em: https://pt.wikipedia.org/wiki/TCP/IP
[2] Wordpress [homepage na internet]. TCP and UDP and difference between them
[acesso em 4 dez 2016]. Disponível
em: https://networkwolves.wordpress.com/2015/03/20/tcp-and-udp-and-difference-
between-them/
[3] PortForward [homepage na internet]. Portforwarding [acesso em 03 jan 2017].
Disponível em:https://portforward.com/help/portforwarding.htm
[4]Wikipedia [homepage na internet]. Redirecionamento de portas [acesso em 3 de
jan 2017]. Disponível
em: https://pt.wikipedia.org/wiki/Redirecionamento_de_portas/
[5] Wikipedia [homepage na internet]. Virtual private networks [acesso em 28 dez
2016]. Disponível em: https://pt.wikipedia.org/wiki/Virtual_private_network
[6] Weaved [homepage na internet]. Documentation. [acesso em 14 ago 2016].
Disponível em: http://docs.weaved.com/docs/
[7] OAuth Community Site [homepage na internet]. Getting Started. [acesso em 10
set 2016]. Disponível em: https://oauth.net/getting-started/
[8] Adafruit [homepage na internet]. Running raspi-config after booting [acesso em
14 set 2016]. Disponível em:https://learn.adafruit.com/adafruits-raspberry-pi-lesson-
2-first-time-configuration/running-raspi-config-after-booting
[9] Wikipedia [homepage na internet]. Serial Peripheral Interface [acesso em 7 jun
2017]. Disponível em: https://pt.wikipedia.org/wiki/Serial_Peripheral_Interface
[10] Adafruit [homepage na internet]. Adafruit 16 channel servo driver with
Raspberry Pi [acesso 21 ago 2016]. Disponível em:
https://learn.adafruit.com/adafruit-16-channel-servo-driver-with-raspberry-
pi/overview
[11] Adafruit [homepage na internet]. Library Reference. [acesso 7 jun 2017].
Disponível em: https://learn.adafruit.com/adafruit-16-channel-pwm-servo-hat-for-
raspberry-pi/library-reference
[12] SIEGWART, Roland; NOURBAKHSH, Illah Reza; Introduction to
Autonomous Mobile Robots.2 ed. Massachusetts: The MIT Press, 2011.
51
Apêndice B
Código fonte do alimentador
Controle do servomotor
import time
# Importa o módulo PCA9685 do driver do controlador da Adafruit.
import Adafruit_PCA9685
#classe que controla o servomotor
class FeederController:
# Inicializa o PCA9685 com o endereco padrao (0x40) da Rpi
pwm = Adafruit_PCA9685.PCA9685()
# Configura o pulso minimo e o maximo
servo_min = 150 # Min pulse length out of 4096
servo_max = 600 # Max pulse length out of 4096
#módulo que facilita o uso da biblioteca internamente
def set_servo_pulse(channel, pulse):
pulse_length = 1000000 # 1,000,000 us per second
pulse_length //= 60 # 60 Hz
print('{0}us per period'.format(pulse_length))
pulse_length //= 4096 # 12 bits of resolution
print('{0}us per bit'.format(pulse_length))
pulse *= 1000
pulse //= pulse_length
pwm.set_pwm(channel, 0, pulse)
#ao inicializado, configura o servomotor com a frequência ideal de 60 Hz
def _init_(self):
self.pwm.set_pwm_freq(60)
#Gira o servomotor para um lado, depois para outro e depois o mantém parado
def feedMotion(self):
self.pwm.set_pwm(0,0,self.servo_max)
time.sleep(1)
self.pwm.set_pwm(0,0,self.servo_min)
time.sleep(1)
self.pwm.set_pwm(0,0,0)
time.sleep(1)
52
Administração dos horários de alimentação
# coding=utf-8
#Observações:
#Os horários estão definidos no formato HHMM, onde HH = horas e MM = minutos
#Funcionalidades principais do módulo: adicionar, remover e alterar horários
#Funcionalidades coadjuvantes do módulo: ler e escrever arquivo de horários
#Função de leitura do arquivo de horários
def readFromFile():
#Abre o arquivo para leitura
readFile = open("/home/pi/CatFeeder/server/feed_times.txt",'r')
#lê o arquivo linha por linha e armazena em um vetor de horários
with readFile as it:
timeArray = []
#checa se todas as linhas são válidas para garantir a operação correta do sistema
for line in it:
if (line):
timeArray.append(int(line))
readFile.close()
return timeArray
#Função de escrita do arquivo de horários
def writeToFile(timeArray):
#para evitar um readers-writers lock (*):
#o programa entra em loop até conseguir realizar a escrita
while (True):
#tenta escrever
try:
#abre o arquivo para escrita (overwrite)
writeFile = open("/home/pi/CatFeeder/server/feed_times.txt",'w')
#escreve cada elemento do vetor no arquivo
for timeItem in timeArray:
writeFile.write("%s\n" % (str(timeItem)))
writeFile.close()
#armazena um vetor temporário de horários
tArray = readFromFile()
#caso os conteúdos dos vetores sejam iguais, sai do loop
if (timeArray == tArray):
print("equal!")
break
#mantém no loop caso erro na escrita
except:
print("unable to write to file")
53
writeFile.close()
# adiciona novos horários no arquivo e o organiza em ordem alfabética
def addTime(timeArray,newTime):
print("start")
#caso o vetor está vazio, insere no vetor sem necessidade de checar
if len(timeArray) == 0:
timeArray.append(newTime)
else:
foundInsert = False
#para cada elemento do vetor de horários
for index,currentTime in enumerate(timeArray):
#caso o horário da posição atual é maior que o inserido, insere na posição e
# quebra o loop
if (currentTime > newTime):
newIndex = index
foundInsert = True
break
#caso seja maior que todos os horários, insere ao final do vetor
if not foundInsert:
timeArray.append(newTime)
#se não insere na posição do último elemento
else:
timeArray.insert(newIndex,newTime)
print ("inserted")
writeToFile(timeArray)
return timeArray
#remove horários pertencentes ao arquivo de horários
def removeTime(timeArray,rmTime):
try:
#remove o horário do vetor e escreve no arquivo
timeArray.remove(rmTime)
writeToFile(timeArray)
#caso erro (horário não pertence ao vetor)
except Exception as excp:
print ("No element %s found \n" % rmTime)
return timeArray
#substitui um horário da lista por outro horário
def changeTime(timeArray,oldTime,newTime):
#retira um horário e depois o substitui
try:
timeArray.remove(oldTime)
54
timeArray = addTime(timeArray,newTime)
except Exception as excp:
print ("No element %s found \n" % oldTime)
return timeArray
#* Como o alimentador opera através de dois scripts que rodam ao mesmo tempo,
# deve-se tomar um certo cuidado com a escrita, pois caso os dois scripts tentarem
#realizar leituras ou mudanças ao mesmo tempo, um deles não conseguirá escrever
#ou ler informações incorretas
Administração dos horários de alimentação
import datetime
import time
#utiliza as bibliotecas desenvolvidas para este projeto
from time_functionalities import readFromFile
from feeder_controller import FeederController
#from feeder_tcp_server import feedMotion
#import Adafruit_PCA9685
#Este script é responsável por alimentar nos horários especificados
#Retorna o horário atual
def getCurrentTime():
dateNow = datetime.datetime.now()
currentTime = str(dateNow.hour) + str(dateNow.minute)
currentTime = int(currentTime)
return currentTime
#Checa se o horário atual é igual a um dos horários de alimentação
def checkTime(timeArray):
#utilize a função anterior
currentTime = getCurrentTime()
#checa para todos os horários do vetor se são iguais ao horário atual
for timeIndex,timeItem in enumerate(timeArray):
if (currentTime == timeItem):
return True
#retorna falso caso não seja o horário de alimentação
return False
55
#cria um objeto de controle do servomotor
feedCon = FeederController()
#este script roda infinitamente
while True:
#mantém o vetor de horários atualizado
timeArray = readFromFile()
#alimenta caso seja o horário de alimentação
if (checkTime(timeArray)):
feedCon.feedMotion()
#executa esta operação a cada 1 minuto
time.sleep(60)
Comunicação TCP para o alimentador
import time
from datetime import datetime
import os
#utiliza a biblioteca de interface de comunicação sockets
from socket import *
import pickle
#utiliza as bibliotecas criadas para este projeto
from time_functionalities import addTime, removeTime, changeTime,readFromFile
from feeder_controller import FeederController
#recebe um vetor de horários e o transforma em uma única string
#utilizada para enviar os dados dos horários
def timeString(timeArray):
timeStr = ""
for timeItem in timeArray:
timeStr += str(timeItem) + " "
timeStr += "\r"
return timeStr
#lê a lista de horários da memória
timeArray = readFromFile()
#cria um objeto de controlador do servomotor
feedCon = FeederController()
#configurações do recebimento de uma conexão:
#qualquer usuário pode estabelecer uma conexão
host = ''
#define a porta de acesso como 5005
56
port = 5005
address = (host,port)
#define o tamanho do buffer como 1024
buf = 1024
#define o protocolo de transporte como TCP
sock = socket(AF_INET, SOCK_STREAM)
#espera uma tentative de conexão
sock.bind(address)
sock.listen(1)
#após a conexão
while True:
print ("Waiting to receive messages...")
conn,addr = sock.accept()
print ('Connection address:',addr)
timePassed = 0
firstConn = True
#após o tempo de timeout de 600 segundos encerra a conexão
while (timePassed <= 600):
#caso seja a primeira troca de mensagens, envia o vetor de horários para o usuário
if (firstConn):
firstConn = False
print(timeString(timeArray))
conn.send(timeString(timeArray))
(data,address) = conn.recvfrom(buf)
print ("Received message: " + data)
#separa a informação recebida por espaços
data = data.split()
#Dorme por um segundo para evitar crashes
time.sleep(1)
#caso a informação recebida é válida
if data:
#dependendo do dado recebida, opera da seguinte forma:
#alimenta no mesmo instante
if data[0]=="feed":
feedCon.feedMotion()
conn.send("ok\r")
print ("Sent!")
57
#if connection is terminated, closes socket connection and makes it available for
a new one
#encerra a conexão atual e libera o socket para a próxima conexão
elif data[0] == "exit":
break
#adiciona um horário de alimentação
elif data[0] == "add_time":
timeArray = addTime(timeArray,int(data[1]))
conn.send(timeString(timeArray))
#remove um horário de alimentação
elif data[0] == "remove_time":
timeArray = removeTime(timeArray,int(data[1]))
conn.send(timeString(timeArray))
#altera um horário de alimentação
elif data[0] == "change_time":
#data[1] = old time ; data[2] = new time
timeArray = changeTime(timeArray,int(data[1]),int(data[2]))
conn.send(timeString(timeArray))
timePassed+= 1
sock.close()
os._exit(0)
Exemplo de um arquivo que contém uma lista de horários de alimentação
1040
1200
1340
2048
2100
2107
58
Apêndice C
Código fonte do cliente
Funcionalidades da interface do cliente
package feederinterface;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Socket;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import org.json.simple.JSONObject;
public class feedCon {
private static String token = "";
private static String deviceUID = "";
private static String proxy = "";
private static int port = 0;
private static Socket sock;
private static boolean connected = false;
private static BufferedReader in;
private static PrintWriter out;
private static String[] timeArray;
// valida o usuário no AD e retira o token
59
public static boolean login(String userName, String password){
try {
//URL de acesso
String url = "https://api.weaved.com/v22/api/user/login/" +
userName + "/" + password;
//Configura a conexão
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
//adiciona o header da requisição
con.setRequestProperty("Content-Type", "application/json");
//adiciona a chave de uso
con.setRequestProperty("apikey", "WeavedDemoKey$2015");
//Recebe o código de resposta
int responseCode = con.getResponseCode();
//Caso o serviço esteja offline
if (responseCode == 404) //not found
return false;
//Lê as informações recebidas em uma string
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//print result
String result = response.toString();
//Encontra o token na resposta e o armazena
Pattern pattern = Pattern.compile("\"token\":\"(.*?)\",");
Matcher matcher = pattern.matcher(result);
while (matcher.find()) {
token = matcher.group(1);
}
//Caso ocorra algum erro na execução, retorna falso
} catch (MalformedURLException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
60
} catch (ProtocolException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
} catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
//Operação bem sucedida = retorna true
return true;
}
#encontra o dispositivo e o serviço alvo
public static boolean findDevice(String device){
//Caso já tenha recebido um token valido
if (token.length() >= 1)
{
try {
//Configuração da conexão
String url = "https://api.weaved.com/v22/api/device/list/all";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// optional default is GET
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
//adiciona a chave
con.setRequestProperty("apikey", "WeavedDemoKey$2015");
//adiciona o token da operação anterior
con.setRequestProperty("token", token);
int responseCode = con.getResponseCode();
if (responseCode == 404) //not found
return false;
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
61
//print result
String result = response.toString().substring(1);
//Procura os dispositivos na resposta recebida e armazena
Pattern pattern = Pattern.compile("\"devicealias\":\"(.*?)\",");
Matcher matcher = pattern.matcher(result);
int counter = -1;
int deviceIndex = -1;
while (matcher.find()) {
counter++;
if (matcher.group(1).equals(device))
{
deviceIndex = counter;
break;
}
}
//Encerra a operação caso não haja dispositivos
if (deviceIndex == -1)
return false;
//Procura e armazena o número de identificação do serviço
pattern = Pattern.compile("\"deviceaddress\":\"(.*?)\",");
matcher = pattern.matcher(result);
counter = -1;
while (matcher.find()) {
counter++;
if (counter == deviceIndex)
{
deviceUID = matcher.group(1);
break;
}
}
return true;
//Retorna falso caso houve algum erro na operação
} catch (MalformedURLException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
} catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
return false;
}
//Retorna o proxy e a porta associada ao dispositivo e serviço selecionado
62
public static boolean getGateway(){
#caso possua valores válidos para o id do dispositivo e o token
if (token.length() >= 1 || deviceUID.length() >= 1)
{
try {
//Checa no site configurado abaixo o IP do dispositivo tentando estabelecer
//uma comunicação com o alimentador
String this_ip = ";
URL oracle = new URL("http://ip.42.pl/raw/");
BufferedReader br = new BufferedReader(
new InputStreamReader(oracle.openStream()));
String inputLine;
while ((inputLine = br.readLine()) != null)
this_ip = inputLine;
br.close();
//Configura a requisição do tipo POST a ser realizada
String url = "https://api.weaved.com/v22/api/device/connect";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// optional default is GET
con.setRequestMethod("POST");
//adiciona request header
con.setRequestProperty("Content-Type", "application/json");
//adiciona chave
con.setRequestProperty("apikey", "WeavedDemoKey$2015");
//adiciona o token
con.setRequestProperty("token", token);
//configura a codificação para utf-8
con.setRequestProperty( "charset", "utf-8");
//Cria um objeto JSON com as informações necessárias para enviar no
//corpo da requisição (ip, id do dispositivo e o parâmetro wait necessário)
JSONObject sendjson = new JSONObject();
sendjson.put("deviceaddress", deviceUID);
sendjson.put("hostip", this_ip);
sendjson.put("wait", "true");
//byte[] out = urlParam .getBytes(StandardCharsets.UTF_8);
// Envia a requisição
con.setDoOutput(true);
con.setDoInput(true);
con.setUseCaches(false);
63
DataOutputStream wr = new
DataOutputStream(con.getOutputStream());
wr.write(sendjson.toString().getBytes("UTF-8"));
wr.flush();
wr.close();
//Recebe o código da resposta e armazena
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
#procura as informações da porta e do proxy
String access = "";
String result = response.toString();
Pattern pattern = Pattern.compile("\"proxy\":\"(.*?)\",");
Matcher matcher = pattern.matcher(result);
while (matcher.find()) {
access = matcher.group(1);
}
#separa as informações e classifica como o tipo de variável correto
int index = access.indexOf(':');
access = access.substring(index + 5);
index = access.indexOf(':', index);
port = Integer.parseInt(access.substring(index + 1));
proxy = access.substring(0,index);
return true;
//Caso erro na operação
} catch (MalformedURLException | ProtocolException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
} catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
return false;
}
64
//Métodos de retorno da porta e do proxy
public int getPort(){return port;}
public String getProxy(){return proxy;}
//Estabelece uma conexão com o alimentador
public static boolean connect()
{
//Caso a porta e o proxy já estejam definidos
if (port != 0 || proxy.length() >= 1)
{
try {
sock = new Socket(proxy,port);
//Recebe as informações da conexão
in = new BufferedReader(new
InputStreamReader(sock.getInputStream(),"UTF-8"));
while (!in.ready()) {}
String line = in.readLine();
//Armazena o vetor de horários
timeArray = line.split("\\s+");
//out = new PrintWriter(sock.getOutputStream(), true);
connected = true;
return true;
//Caso ocorra erro na operação
} catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
return false;
}
//Retorna o vetor de horários
public static String[] getTimeArray() {return timeArray;}
//Encera a conexão, o leitor de dados e o escritor de dados
private boolean close()
{
try {
in.close();
out.close();
sock.close();
return true;
65
} catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
#envia o commando de alimentação imediata
public boolean sendFeed() //sends feed command
{
#caso a conexão já foi estabelecida
if (connected)
{
//Tenta enviar a mensagem
try {
//Envia a mensagem
out = new PrintWriter(sock.getOutputStream(), true);
out.print("feed");
out.flush();
BufferedReader in = new BufferedReader(new
InputStreamReader(sock.getInputStream()));
//Lê a resposta após sua chegada
while (!in.ready()) {}
in.readLine();
return true;
//Caso ocorra algum erro retorna falso
} catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
return false;
}
//Envia a ordem de adicionar um novo horário de alimentação
public boolean addTime(String newTime){
if (connected){
//Checa se o novo horário já existe
if (!checkExists(newTime)){
try {
66
//Envia a ordem de alimentação
out = new PrintWriter(sock.getOutputStream(), true);
out.print("add_time " + newTime);
out.flush();
BufferedReader in = new BufferedReader(new
InputStreamReader(sock.getInputStream()));
//Aguarda uma confirmação do sistema
while (!in.ready()) {}
String line = in.readLine();
timeArray = line.split("\\s+");
return true;
}catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
}
return false;
}
//Envia uma ordem de remover um horário de alimentação
public boolean removeTime(String oldTime){
if (connected){
try {
//Envia a ordem
out = new PrintWriter(sock.getOutputStream(), true);
out.print("remove_time " + oldTime);
out.flush();
BufferedReader in = new BufferedReader(new
InputStreamReader(sock.getInputStream()));
//Aguarda uma confirmação do sistema
while (!in.ready()) {}
String line = in.readLine();
timeArray = line.split("\\s+");
return true;
}catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
return false;
67
}
//Envia uma ordem de subsituição de horário
public boolean changeTime(String oldTime, String newTime){
if (connected){
if (!checkExists(newTime)){
try {
//Envia a ordem
out = new PrintWriter(sock.getOutputStream(), true);
out.print("change_time " + oldTime + " " + newTime);
out.flush();
BufferedReader in = new BufferedReader(new
InputStreamReader(sock.getInputStream()));
//Aguarda confirmação
while (!in.ready()) {}
String line = in.readLine();
timeArray = line.split("\\s+"); //armazena vetor de horários
return true;
}catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null,
ex);
return false;
}
}
}
return false;
}
//Encerra a conexão através do logout
public boolean logout(){
if (connected){
try {
//Envia a mensagem de encerramento
out = new PrintWriter(sock.getOutputStream(), true);
out.print("exit");
out.flush();
return close();
}catch (IOException ex) {
Logger.getLogger(feedCon.class.getName()).log(Level.SEVERE, null,
ex);
return false;
}
}
return false;
}
68
//Checa se o valor já existe no vetor de horários
private boolean checkExists(String time){
for (String timeArray1 : timeArray) {
if (timeArray1.equals(time)) {
return true;
}
}
return false;
}
}
Interface do menu e do login
package feederinterface;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.Arrays;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
public class mainMenu extends JFrame implements ActionListener{
//Widgets da interface
private JButton feedBtn;
private JButton addBtn;
private JButton removeBtn;
private JButton changeBtn;
private JButton logoutBtn;
private JList feedList;
private JScrollPane scroll;
private String[] timeArray;
//Cria um objeto de comunicação com o alimentador
69
feedCon con = new feedCon();
public mainMenu(){
//Define o layout do menu inicial
super("Cat Feeder"); //nome provisório
setLayout(null);
Toolkit tk = Toolkit.getDefaultToolkit();
int xSize = ((int) tk.getScreenSize().getWidth());
int ySize = ((int) tk.getScreenSize().getHeight());
setSize(xSize,ySize);
//UIManager.put("OptionPane.minimumSize",new Dimension(1200,400));
UIManager.put("OptionPane.font", new Font("yourFontName", Font.BOLD,
30));
UIManager.put("OptionPane.buttonFont", new FontUIResource(new
Font("ARIAL",Font.PLAIN,30)));
//Configuração dos botões
feedBtn = new JButton("Feed Now");
feedBtn.setBounds(xSize*2/3,ySize/8,xSize/6,100);
feedBtn.setFont(new Font("Verdana", Font.BOLD, 32));
addBtn = new JButton("Add New Feeding Time");
addBtn.setBounds(xSize*2/3,2*ySize/8,xSize/6,100);
addBtn.setFont(new Font("Verdana", Font.BOLD, 32));
removeBtn = new JButton("Remove Feeding Time");
removeBtn.setBounds(xSize*2/3,3*ySize/8,xSize/6,100);
removeBtn.setFont(new Font("Verdana", Font.BOLD, 32));
changeBtn = new JButton("Change Feeding Time");
changeBtn.setBounds(xSize*2/3,4*ySize/8,xSize/6,100);
changeBtn.setFont(new Font("Verdana", Font.BOLD, 32));
logoutBtn = new JButton("Logout");
logoutBtn.setBounds(xSize*2/3,5*ySize/8,xSize/6,100);
logoutBtn.setFont(new Font("Verdana", Font.BOLD, 32));
//Configura lista de horários
scroll = new JScrollPane();
feedList = new JList();
feedList.setFont(new Font("Verdana", Font.PLAIN, 32));
scroll.setBounds(xSize/6,ySize/8,xSize/3,ySize*2/3);
scroll.setViewportView(feedList);
#Configura os elementos do pop up de login
70
JTextField fldUser = new JTextField(15);
JPasswordField fldPass = new JPasswordField(15);
//fldUser.setPreferredSize( new Dimension( 300, 50 ) );
fldUser.setFont(new Font("Verdana", Font.PLAIN, 32));
fldPass.setFont(new Font("Verdana", Font.PLAIN, 32));
JLabel lblUser = new JLabel("Username:");
lblUser.setFont(new Font("Verdana", Font.PLAIN, 32));
JLabel lblPassword = new JLabel("Password:");
lblPassword.setFont(new Font("Verdana", Font.PLAIN, 32));
Object[] message = {
lblUser, fldUser,
lblPassword, fldPass
};
//Mantém o usuário no loop até a autenticação
boolean reqAuth = true;
while (reqAuth){
//Mostra popup de login
int result = JOptionPane.showConfirmDialog(null, message,
"Login", JOptionPane.OK_CANCEL_OPTION);
//Se o usuário clicou no botão de ok
if (result == JOptionPane.OK_OPTION){
//Checa os valores dos campos de usuário e senha
//e executa as rotinas de autenticação
if (feedCon.login(fldUser.getText(),
String.valueOf(fldPass.getPassword()))){
if (feedCon.findDevice("feederPi")){
if (feedCon.getGateway())
if (feedCon.connect()){
reqAuth = false;
//Carrega os horários na interface
populateList();
}
}
}
//Usuário inválido
else{
JOptionPane.showMessageDialog(null,
"Usuário inválido",
"Por favor, tente novamente.",
JOptionPane.WARNING_MESSAGE);
}
}
//Caso usuário feche a janela, encerra o programa
71
else{
this.setVisible(false);
this.dispose();
reqAuth = false;
}
}
//Configura responsividade dos elementos
feedBtn.addActionListener(this);
addBtn.addActionListener(this);
removeBtn.addActionListener(this);
changeBtn.addActionListener(this);
logoutBtn.addActionListener(this);
//feedList.addActionListener(this);
add(feedBtn);
add(addBtn);
add(removeBtn);
add(changeBtn);
add(logoutBtn);
add(scroll);
}
//Listener de eventos
public void actionPerformed(ActionEvent event){
//Caso o botão de alimentão foi selecionado
if (event.getSource() == feedBtn){
int choice;
Object[] options = {"Yes", "No"};
//Mostra um popup
choice = JOptionPane.showOptionDialog(null,
"Are you sure you want to feed your pets?",
"Choose an option",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[1]);
if (choice == 0)
//alimenta e mostra mensagem de confirmação
if (con.sendFeed()){
JLabel lblReached = new JLabel("Your pet was fed! Going
back to main menu!");
72
lblReached.setFont(new Font("Verdana", Font.PLAIN,
32));
JOptionPane.showMessageDialog(null, lblReached);
}
else{
//Mostra mensagem de erro
JLabel lblNotReached = new JLabel("We couldn't reach your
device right now! Please try again later!");
lblNotReached.setFont(new Font("Verdana", Font.PLAIN, 32));
JOptionPane.showMessageDialog(null,
lblNotReached);
}
}
//Adiciona um novo horário
else if (event.getSource() == addBtn){
//Configura pop up
JTextField fldTime = new JTextField(15);
fldTime.setFont(new Font("Verdana", Font.PLAIN, 32));
JLabel lblTime = new JLabel("Time to be added:");
lblTime.setFont(new Font("Verdana", Font.PLAIN, 32));
Object[] timeMessage = {
lblTime, fldTime,
};
//mostra pop up
int response = JOptionPane.showConfirmDialog(null, timeMessage,
"Login", JOptionPane.OK_CANCEL_OPTION);
if (response == JOptionPane.OK_OPTION){
//Checa entrada valida
String newTime = fldTime.getText();
//formato HH:MM -> HHMM
newTime = newTime.replace(":","");
//Adiciona horário
if (con.addTime(newTime)){
//Pop up de sucesso para usuário
JOptionPane.showMessageDialog(null, "New feeding time was added!
Going back to main menu!");
populateList();
}
//mensagem de erro
else
JOptionPane.showMessageDialog(null, "We couldn't reach your
device right now or the requested feeding time already exists! Please try again later!");
}
73
}
//Caso o botão de alteração de horário foi selecionado
if (event.getSource() == changeBtn){
//Pega o index do elemento selecionado da lista de horários da interface
int index = feedList.getSelectedIndex();
//Caso nenhum elemento tenha sido selecionado
if (index == -1){
JOptionPane.showMessageDialog(null, "Please select a feeding time to
change.");
}
//Caso horário selecionado, mostra um popup pedindo o horário novo
else{
JTextField fldTime = new JTextField(15);
fldTime.setFont(new Font("Verdana", Font.PLAIN, 32));
JLabel lblTime = new JLabel("New feeding time:");
lblTime.setFont(new Font("Verdana", Font.PLAIN, 32));
Object[] timeMessage = {
lblTime, fldTime,
};
int response = JOptionPane.showConfirmDialog(null, timeMessage,
"Login", JOptionPane.OK_CANCEL_OPTION);
//Caso novo horário inserido
if (response == JOptionPane.OK_OPTION){
String newTime = fldTime.getText();
newTime = newTime.replace(":","");
//altera horário
if(con.changeTime(timeArray[index],newTime)){
JOptionPane.showMessageDialog(null, "Feeding time was changed!
Going back to main menu!");
populateList();
}
else
JOptionPane.showMessageDialog(null, "We couldn't reach your
device right now or the requested feeding time already
exists! Please try again later!");
}
}
}
//Botão para remover horário
if (event.getSource() == removeBtn){
int index = feedList.getSelectedIndex();
74
//Checa se algum foi selecionado
if (index == -1){
JOptionPane.showMessageDialog(null, "Please select a feeding time to
remove.");
}
else
if(con.removeTime(timeArray[index])){
JOptionPane.showMessageDialog(null, "Feeding time was changed!
Going back to main menu!");
populateList();
}
else
JOptionPane.showMessageDialog(null, "We couldn't reach your
device right now or the requested feeding time already
exists! Please try again later!");
}
//Botão de logout selecionado
if (event.getSource() == logoutBtn){
con.logout();
this.setVisible(false); //Desliga interface
this.dispose();
}
//Método chamado quando a lista da interface deve ser preenchida
private void populateList(){
//Pega a lista do objeto
timeArray = feedCon.getTimeArray();
//Escreve a lista em um buffer e insere no element gráfico
String[] listArray = Arrays.copyOf(timeArray,timeArray.length);
for (int counter = 0; counter < timeArray.length; counter++){
StringBuffer sb = new StringBuffer(listArray[counter]);
sb.insert(listArray[counter].length() - 2, ":");
listArray[counter] = sb.toString();
}
feedList.setListData(listArray);
}
}
Método inicial que inicializa a interface
package feederinterface;
import javax.swing.JFrame;