alimentador de animais domÉsticos automÁtico … · a dificuldade de cuidar de animais...

89
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

Upload: ngonhi

Post on 24-Jan-2019

220 views

Category:

Documents


0 download

TRANSCRIPT

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.

46

Figura 4.8 – Engrenagem acoplada ao servomotor.

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.

50

Apêndice A

Fluxograma de operação do projeto

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;

75

public class FeederInterface {

public static void main(String[] args) {

//Mostra a interface para o usuário

mainMenu menu = new mainMenu();

menu.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

menu.setVisible(true);

}

}