O Mundo da Internet das Coisas (IoT)

Enfim a Internet chegou em todas as coisas! Hoje, qualquer objeto possui potencial de ser conectado à Internet e de gerar dados que sejam úteis para tomadas de decisão de uma pessoa ou até mesmo de outras coisas; mais do que isso, qualquer desenvolvedor é um criador com potencial de lançar novos dispositivos com capacidades sensoriais e que permitam diferentes experiências da mesma Internet que sempre esteve presente.

Neste artigo o leitor inicia um mergulho em uma Internet nova e já palpável: a Internet de vários dispositivos interconectados em um ambiente totalmente informatizado e sensorial.

Nesta trilha rumo à exploração de novas formas de interação entre humanos e máquinas, veremos que o Java tem todo o potencial para continuar sendo a linguagem principal de controle desses dispositivos. Portanto, prepare seus sensores!

Com tanta opção de aparelhos como interfaces para acessar aos mesmos sistemas, os laptops se tornaram equipamentos burocráticos para se conectarem à internet e aos poucos estão ganhando funções mais específicas como ferramentas de escritórios, já que esses outros dispositivos estão suprindo as necessidades de entretenimento, comunicação e coleta de informações, antes atendidas somente pelos laptops e PCs.

Gradativamente, as tecnologias vão surgindo e se mesclando em uma trilha de avanço contínuo, melhorando a experiência de interação dos usuários com os sistemas e ganhando velocidade de comunicação e processamento.

E para que tudo isso se torne possível, os servidores corporativos têm acompanhado tal evolução; seja através da migração para as nuvens, ganhando flexibilidade para crescer em infraestrutura e processamentos distribuídos, seja através de arquiteturas orientadas a serviços, tornando as complexidades de negócio cada vez mais modularizadas.

Os desktops e laptops continuam e possivelmente continuarão presentes por muito tempo, sendo predominantes em escritórios de call center, ambientes de desenvolvimento e afins, enquanto os smartphones seguem abrindo o caminho para uma interconexão sem precedentes na história da comunicação.

Agora está chegando a era em que várias coisas pessoais conectarão não só pessoas a pessoas, mas principalmente, coisas a coisas, tudo isso através de uma internet descentralizada e composta por dispositivos capazes de intercomunicação de forma automática. Mesmo uma adega de vinho, uma geladeira, um relógio ou o sistema elétrico de uma casa; coisas impessoais.

A estimativa para os próximos anos é de que aproximadamente 50 bilhões de dispositivos estarão conectados à grande rede. Isto representa um grande desafio de computação em nuvem, infraestrutura de armazenamento e processamento de eventos complexos em uma arquitetura mais elaborada e descentralizada. Este será, certamente, o grande desafio dos servidores corporativos e até mesmo pessoais (leiam-se caseiros). Porém, a maior evolução com a Internet das Coisas provém do fato que hoje os sistemas embarcados têm maior capacidade de processamento. Em grande parte, esses sistemas suportam Java, conseguem rodar serviços mais elaborados, conversar com sensores e dispositivos como câmeras e microfones.

Neste cenário, o foco tenderá a ser concentrado nos dispositivos embarcados. Serão esses dispositivos que efetuarão o processamento final, em vez de coletar informações e enviá-las a um servidor que concentre a inteligência e a força de processamento, como é o modo mais tradicional de arquitetura de sistemas. Esta mudança de paradigma já representa, por si só, um grande desafio no que diz respeito à instalação do software (deploy/delivery contínuo) por causa da quantidade e variedade de dispositivos simultâneos a serem atualizados individualmente com novas versões. Nestas condições, o Java já começa a empreitada levando a vantagem de permitir atualização dinâmica via rede; aliás, vale lembrar que o Java já nasceu com este intuito: o de ser a linguagem de programação de diversas coisas, flexível e potente o suficiente para caber desde em um chip de cartão de crédito, até em um servidor com arquiteturas complexas de processamento.

Pensando neste mercado que está se ampliando, a Oracle se antecipou e preparou mecanismos para que o desenvolvedor Java que hoje se concentra em aplicações comerciais e está acostumado com o desenvolvimento orientado a servidores como JBoss, GlassFish, Tomcat, Jetty, entre outros, possa continuar concentrado nestas ferramentas sem precisar mudar de paradigma ou se preocupar com muitas mudanças no desenvolvimento para plataformas embarcadas. O Java Embedded Suite, assunto explorado na Java Magazine 118, foi criado com a finalidade de suprir estas novas demandas. Através dele, é possível embarcar servidores completos como GlassFish, por exemplo, e utilizá-los como gateway de comunicação com outros dispositivos para enviar o estado de algum sensor ou receber comandos para executar alguma ação como ligar um sensor de Bluetooth, ler o valor de um sensor de humidade ou temperatura, tudo remotamente.

Em um dispositivo de arquitetura ARM, como um Raspberry PI, que é um computador bastante compacto e suficientemente barato, é possível configurar facilmente um servidor e ainda utilizar a sua saída de vídeo HDMI para trabalhar com interface gráfica em um ambiente Linux que hoje possui várias distribuições desenvolvidas para a sua arquitetura. Ele ainda conta com conectores Ethernet e USB, além de GPIO para conexão com sensores, o que permite conectar diversos dispositivos em ideias inovadoras. O Raspberry PI é considerado a tecnologia da vez nesta tendência de Internet das Coisas por apresentar-se já suficientemente completo, barato e compacto para rodar aplicações de processamento mais pesado; podendo ainda ser embarcado em uma solução de automação mais complexa.

Muito se tem falado sobre o termo IoT (Internet of Things), geralmente associado a automação de casas e criação de mecanismos inteligentes em que geladeiras gerenciam a si próprias e às suas necessidades de reabastecimento; banheiras que se enchem sozinhas quando o dono da casa está para chegar de um dia cansativo de trabalho; entre outros cenários. Mas o que faz com que tudo se conecte?

Quando tudo isto se tornará realidade e começará a funcionar como parte do cotidiano? Como o Java se encaixa nesse novo mundo? Como o desenvolvedor deve se preparar para estes novos desafios?

Neste artigo estas questões, entre outras, serão não somente respondidas, mas também exploradas, através da prototipação de um pequeno dispositivo capaz de medir a altura de uma pessoa e enviar essa informação a um Tablet, que poderá ficar na mesa de centro da sala de estar para apresentar ao visitante a sua altura, quando ele passar por baixo de um sensor ao atravessar a porta de entrada da casa.

NOTA: Sistemas embarcados são sistemas computacionais embutidos em um dispositivo completo, cujas funções dedicadas residem juntas a um sistema eletrônico ou mecânico maior. Tipicamente utilizados tanto em aplicações industriais como em aplicações de cliente final, os sistemas embarcados estão incorporando a linha de Internet das Coisas (IoT).

O que é a Internet das Coisas?

Não existe uma definição que explique de maneira geral este termo, principalmente pelo fato de que a Internet das Coisas representa uma ampliação de praticamente toda a ciência da computação como uma evolução natural do modo como as pessoas, de usuários finais a engenheiros, arquitetos e desenvolvedores, enxergam a tecnologia e seus novos meios de proporcionar outras experiências de usabilidade.

Essa evolução implica em diferenças na criação de novos produtos desde a arquitetura de novos dispositivos até o desenvolvimento de softwares e serviços, arquiteturas e infraestruturas de processamento.

Mas existe uma pergunta cuja resposta determina se algo faz parte da Internet das Coisas: um produto de um determinado fabricante pode se conectar aos produtos de outros fabricantes de forma automática? Um leitor de RFID (vide BOX 1) de um fabricante poderia, por exemplo, se conectar ao sistema de um portão eletrônico de outro fabricante para abri-lo ou fechá-lo através de um protocolo padrão?

BOX 1. Sistemas RFID (Radio Frequency Identification)

RFID ou identificação por radiofrequência é um termo genérico para as tecnologias que utilizam frequência de rádio para captura de dados. A forma mais tradicional desta tecnologia se dá através da leitura de etiquetas – mais conhecidas pelo nome em inglês (Tags) – que possuem códigos utilizados para identificar coisas, artigos de produtos, dispositivos ou pessoas.

Na Internet das Coisas, os leitores RFID, junto às etiquetas, apresentam-se como solução eficaz e barata para identificação de objetos ou ainda pessoas em soluções que exigem ações personalizadas.

Um exemplo é o reconhecimento de moradores na automação de uma casa. Qual quarto deve ter sua luz acesa quando determinado morador chegar? Que tipo de música tocar no aparelho de som? Para qual temperatura o ar-condicionado deve ser regulado? Todos estes parâmetros podem ser personalizados em um sistema orientado ao reconhecimento através de um cartão ou etiqueta via leitor RFID.

Para exemplificar esta premissa de intercomunicação entre dispositivos para que estejam aptos à Internet das Coisas, podemos considerar o seguinte cenário: Um morador se aproxima do portão de entrada de sua casa e então uma etiqueta RFID instalada em seu carro é detectada pelo sistema residencial, que desbloqueia o portão e ainda é capaz de identificar quem é o morador que está chegando.

O dispositivo wireless do portão envia uma mensagem via rede e dá o comando para que o quarto de solteiro tenha a luz ligada e o televisor é sintonizado em seu canal de esportes predileto, uma vez que o morador detectado é o filho adolescente de dezoito anos de idade.

Ainda neste cenário, ao reconhecer o adolescente, o sistema regula o ar condicionado do quarto, que teve sua potência reduzida enquanto o adolescente saía para estudar, para ficar mais confortável, na temperatura pré-estabelecida pelo garoto. Como é possível verificar, tudo está funcionando de forma coordenada, pois esta é a grande premissa da Internet das Coisas: coordenação entre múltiplas coisas (sem a intervenção humana).

Por que a Internet das Coisas é o futuro?

Estamos em uma era em que as crianças já nascem com acesso às mais avançadas tecnologias e estão desde muito cedo conectadas ao mundo, muitas vezes através dos próprios pais, que querem compartilhar o crescimento e os momentos dos filhos com toda a família e amigos.

As pessoas querem e fazem questão de estarem conectadas umas às outras, noticiando os mais diversos tipos de atividades cotidianas: uma corrida em um parque, uma cena engraçada do bichinho de estimação, um momento importante no trabalho, como estão ganhando peso, como estão perdendo peso, como estão evoluindo em uma atividade de estudo, na carreira, na escola.

Diante disso, a frase “The internet is everywhere”, vinda do inglês e traduzida livremente para “A internet está em todos os lugares”, nunca se fez tão latente.

Em um cenário como este, com tantos dispositivos se conectando e conectando pessoas, com tanta informação sendo compartilhada, os dispositivos ganham uma extensão essencial para que essa onda de conexão entre pessoas se torne possível e para que seja possível coletar tudo sobre qualquer coisa: os sensores.

Eles podem registrar mudanças de temperatura, claridade, pressão, som, movimento, entre outros. Se entrarmos no campo dos smartphones, então esta lista fica ainda mais interessante: sensores de batimentos cardíacos, velocidade, localização (GPS), vibração, etc.

Esses sensores são os olhos e orelhas dos dispositivos para o que acontece no mundo e permitem que máquinas ganhem sensibilidades que humanos possuem ou, em algumas ocasiões, sensibilidades que nem mesmo humanos possuem, como a detecção de presença de álcool.

Em uma rede em que todos compartilham quase tudo sobre si próprio, aplicativos e dispositivos que necessitem de entradas diretas, ou seja, que precisem de uma tecla digitada ou de um botão pressionado começam a se tornar alternativas menos utilizadas, já que a tendência é que os dígitos e cliques sejam substituídos por movimentos, momentos, cheiros, sabores, substâncias, ondas, luzes e vozes detectados por sensores, sendo estas alternativas mais naturais e sucintas, exigindo menor esforço e concentração do usuário para que a interação ocorra com a mesma ou melhor eficiência.

Antes de falarmos sobre o que os sensores fazem, vamos entender sua eletrônica. Os sensores são parte de uma categoria de dispositivos chamados de sistemas Micro-eletro-mecânicos (sim, esta palavra existe em Português e é abreviada como MEMS em inglês: Micro-Electro-Mechanical Systems) e são manufaturados de forma muito semelhante à produção dos microprocessadores: através de um processo de litografia, possibilitando a concepção de micro sistemas fáceis de serem embarcados nos circuitos integrados de um microcontrolador como um Arduino ou em um microprocessador como um Raspberry PI através dos conectores GPIO ou via wireless (por meio de frequência de rádio) para detectar informações do ambiente.

Nota: Sistema Micro-eletro-mecânico (Micro-Electro-MechanicalSystem, em inglês) é o nome dado para a tecnologia que integra elementos mecânicos, sensores, atuadores e eletrônicos em um pequeno chip que possui instruções de funcionamento inalterável em um firmware. São praticamente micromáquinas programadas para cumprir determinada atividade.

Nota: Na engenharia computacional, litografia é o processo pelo qual se criam os microprocessadores. Neste processo, um líquido foto resistente (ou seja, cuja resistência varia conforme a intensidade da luz) é aplicado sobre um disco de silício em rotação, fazendo com que o líquido seja espalhado uniformemente na placa.

Em seguida, através da projeção de luz Ultravioleta sobre o disco, o líquido reage com a intensidade da luz e forma o desenho dos circuitos em tamanho microscópico. Todos os pontos atingidos pela luz tornam-se solúveis e, portanto, removíveis.

Em seguida, íons de cobre são utilizados para recobrir a área removida e o processo se repete formando as várias camadas que criam o circuito final do chip. Esta etapa de injeção do líquido foto resistente e da sua “escrita” na placa através da luz Ultravioleta é o que chamamos de litografia.

Na prática, como os sensores funcionam?

O funcionamento dos sensores pode ser compreendido através da análise da seguinte situação: um desenvolvedor de sistemas um pouco sedentário resolve entrar em forma andando de patins. Assim, ele resolve instalar sensores de velocidade e distância percorrida em seus patins, além de um dispositivo capaz de enviar essas informações via Bluetooth para outros aparelhos.

O nosso novo esportista também adquire uma balança que, além de medir o peso, é capaz de medir a altura e enviar, também via Bluetooth, essas informações, assim como o IMC (Índice de Massa Corpórea), a outros dispositivos. O nosso desenvolvedor então tem a ideia de unificar todas essas informações da balança e dos patins em um aplicativo de celular que receberá automaticamente o peso aferido.

O aparelho, por sua vez, envia esses dados a um mini servidor Raspberry PI que executa uma aplicação Java Embedded em um web container Tomcat que ele instalou na rede local de sua casa. Com estas informações já sincronizadas com o servidor, ele sai para patinar e ao término emparelha o seu celular com os patins para coletar a velocidade média, tempo e distância percorrida.

Ao chegar em casa, o aplicativo envia essas novas informações ao servidor Raspberry PI, que utilizará essas novas entradas na geração de dados estatísticos sobre a evolução dos treinos, perda de peso, entre outras informações importantes para o nosso novo atleta.

Após todo esse exercício, o desenvolvedor então decide ir até a geladeira para comer um lanche, geladeira essa que possui um display como atuador conectado ao servidor Raspberry PI através da rede local.

Ao abrir a porta, o dispositivo é acionado e exibe uma sugestão de quantas calorias são recomendadas que ele consuma para repor a energia gasta durante a patinação e sugere até qual alimento consumir com base em seus hábitos alimentares. Para isso, manualmente, o nosso usuário também configurou o servidor com uma relação de quais produtos estão na geladeira junto às suas informações nutricionais, permitindo, assim, que o sistema consiga verificar a melhor alimentação para o momento, de acordo com a evolução física e nutricional.

Portanto, na prática existem poucas coisas que não possam ser feitas com alguns sensores e um pouco de criatividade. Nessa aplicação fictícia, os dados não saíram da rede local do usuário e o celular funcionou como uma interface amigável para que o atleta pudesse coletar as informações de todos os sensores e também foi capaz de manter essas informações armazenadas até poder descarregá-las em um servidor central ainda bastante pequeno e simples, mas suficiente para o processamento na geração de estatísticas que posteriormente foram utilizadas como dados de saída para o display embutido na geladeira.

O celular poderia ainda ter sido configurado para coletar e descarregar essas informações de modo automático, sem intervenção do usuário, criando assim um fluxo completo em que nenhuma intervenção humana seria necessária para que sensores em dispositivos diferentes pudessem trabalhar de forma orquestrada em função da geração dessas sugestões de consumo mostradas no display da geladeira.

Esta é a Internet das Coisas em sua forma mais latente, funcionando através do uso prático de dispositivos simples, que já existem ou que podem facilmente ser adaptados, como no caso do sensor nos patins.

Note que o desenvolvedor poderia optar também por estender a funcionalidade do sistema e integrar-se a redes sociais para gabar-se de sua evolução e da sua boa forma, ou então poderia convidar outros amigos a seguir sua rotina nutricional ou a seguir uma bateria de corridas sob patins. Na prática, a criatividade e a utilidade são os únicos limites para a utilização de sensores.

Pensando em prototipação

Um protótipo é a melhor forma de iniciar o projeto de um produto na Internet das Coisas e a sua utilização como parte desse processo de criação possui muitos benefícios, como: durante uma prototipagem o designer irá, inevitavelmente, passar por problemas que irão demandar mudanças em várias iterações até chegar a um resultado aceitável.

Neste processo, a realização de testes em um experimento individual é uma abordagem trivial se comparado com a modificação de centenas ou milhares de produtos que venham a ser colocados em produção.

A construção de um produto para a Internet das Coisas envolve três atividades essenciais e paralelas: a construção da coisa física, a parte eletrônica que dá a inteligência à coisa e o serviço web com o qual ela irá se comunicar. Esta última é relativamente fácil e barata de ser alterada, porém, não é tão simples modificar o objeto e seus componentes controladores, sensores e atuadores, a não ser que seja feito um recall em todos os dispositivos finais.

Nota: Recall é a palavra em inglês adotada com o significado de convocação por parte de um fabricante ou distribuidor para que determinado produto seja levado de volta para substituição ou reparo de possíveis ou reais defeitos.

Portanto, o protótipo deve ser otimizado para facilitar e acelerar o desenvolvimento, além de ser flexível para a alteração de seu comportamento.

Tal experimento, como na maioria dos projetos de dispositivos para a Internet das Coisas, inicia-se com um microcontrolador ou microprocessador ligado a componentes através de fios interconectados por meio de uma placa de prototipação normalmente conhecida como Protoboard, termo em inglês popularizado no Brasil, ou Breadboard, termo em inglês popularizado em outros países (vide BOX 2).

BOX 2. Matriz de contatos – Protoboard

A matriz de contatos ou Protoboard é utilizada para fazer montagens provisórias em testes de projetos eletrônicos. Trata-se de uma composição simples feita a base de plástico e vários orifícios ligados uns aos outros, permitindo a interconexão serial e paralela através de circuitos criados pela conexão de fios e componentes eletrônicos, sem a necessidade de solda ou qualquer ligação rígida. A vantagem de utilizar a Protoboard é o fato de ela ser reutilizável em vários projetos, o que possibilita a montagem e desmontagem de vários modelos de circuitos eletrônicos.

Protoboards podem ser encontradas em diversos tamanhos e classificadas pela quantidade de orifícios. Quanto mais furos a placa tiver, maiores e mais complexos podem ser os projetos. Independentemente do tamanho da placa e da quantidade de furos, no entanto, o funcionamento é o mesmo: existem pelo menos duas carreiras paralelas na vertical com um circuito negativo e outro positivo, sendo estes ligados a linhas horizontais, que podem ser alimentadas por fios vindos do circuito positivo.

Ao conectar uma ponta de um fio na parte negativa e a outra ponta em uma das linhas perpendiculares, e outro fio com uma das pontas em uma parte positiva e a outra ponta em mais uma das linhas perpendiculares, é possível criar circuitos que unam componentes como transistores, resistores, sensores, atuadores (como LED e display), chips, entre outros componentes eletrônicos, formando um modelo de circuito que tenha alguma finalidade.

Normalmente as Protoboards são conectadas a outras placas de prototipagem como um Arduino, que é uma solução também muito barata e que possui sistema GPIO, viabilizando a ligação e o controle de componentes elétricos via programação em software.

Este tipo de prototipagem é relativamente barato de ser desenvolvido e normalmente culmina em algo que pode ser demonstrado como um produto viável, mas ainda sem o acabamento de algo vendável ao consumidor final. Além disso, um protótipo com essas características possivelmente irá custar mais caro do que alguém estaria disposto a pagar em uma loja.

Na Figura 1 é possível visualizar uma Protoboard de 400 furos, sendo 300 em paralelo e duas carreiras com 25 orifícios de cada polaridade (positivo e negativo) em ambos os lados.

Protoboard de 400 furos
Figura 1. Protoboard de 400 furos.

Nesta Protoboard podemos perceber que existem 30 fileiras horizontais ilustradas por números de 1 (um) a 30 (trinta). Se um furo for energizado, todos os outros do mesmo segmento passarão a ter corrente elétrica. Já os orifícios das extremidades, ilustrados pelos sinais de + (mais), vermelho e – (menos), azul, são conectados em série, como sugerido pelas linhas coloridas.

Da linha com furos de polaridade positiva saem fios conectando energia aos orifícios representados por números, permitindo assim a energização do segmento horizontal. Ademais, vale ressaltar que a linha vermelha determina o polo positivo e indica que o fluxo de eletricidade inicia-se ali, enquanto a azul indica o término do circuito.

Como todo circuito elétrico precisa de um ponto de partida como fornecimento de energia e de um ponto de término no qual a corrente termine, na Protoboard o fluxo elétrico pode ser iniciado por uma fonte qualquer (normalmente de cinco volts), que permanece fornecendo energia ininterruptamente, ou por um pino GPIO (vide BOX 3), que pode ser microcontrolado dependendo da necessidade da aplicação, e pode ser finalizado com a conexão do segmento indicado com o sinal de negativo (-) à saída de corrente da fonte ou ao pino Ground (GND) da mesma placa GPIO.

BOX 3. General Purpose Input/Output – GPIO

General Purpose Input/Output ou I/O de propósito geral são pinos ou portas que podem ser programadas para receberem dados ou enviá-los através de pulsos elétricos em pinos configuráveis para entrada ou saída em diálogo principalmente com sensores.

No Arduino ilustrado pela Figura 2, é possível localizar os pinos GPIO acima do símbolo da marca Arduino, indicados pela linha branca. Também nesta figura, podemos ver os pinos numerados de 0 a 13 e ainda o último pino GND (Ground), que deve ser utilizado para encerrar a corrente no sistema eletrônico.

Em um projeto de Internet das Coisas, o GPIO é imprescindível para dar ao sistema capacidades sensoriais e de exibição de dados. Na construção de novos dispositivos, a quantidade de portas para entrada e saída de dados indica qual o tipo de microcontrolador ou processador deve ser utilizado e, consequentemente, qual modelo pode ser adquirido para a prototipagem.

Como exemplo, imagine uma calculadora baseada em valores binários, ativados ou desativados por interruptores, e que mostre os resultados de cálculos em LEDs acesos ou apagados para representar um número.

Em uma operação em que a calculadora some dois números em formato binário de até um byte cada, a quantidade de pinos a serem utilizados como entrada seria de no mínimo 16, para garantir a entrada dos dois números a serem somados.

Além disso, mais nove portas GPIO seriam utilizadas para exibir o resultado nos LEDs em modo binário, já que a soma dos dois binários poderia retornar um número com estouro de base, obrigando o resultado a ter um binário a mais em sua composição.

Na Figura 2 observa-se um protótipo desenhado na ferramenta computacional Fritzing (ver BOX 4) para ilustrar um circuito simples controlador de um LED.

Nesta imagem os orifícios coloridos de verde simbolizam energia correndo pelo circuito. Note que a saída 13 da placa simbolizando um Arduino está conectando o primeiro pino de energia positiva da Protoboard com o polo também positivo do dispositivo através do fio vermelho. Neste mesmo segmento, um resistor é conectado em outro orifício, com sua outra extremidade ligada ao furo da outra divisão da placa, que passa a se energizar em todo o segmento.

Ainda no mesmo circuito, observa-se a extremidade positiva do LED (identificada pela ponta ligeiramente entortada exatamente para indicar esta polaridade) sendo alimentada pela mesma linha paralela energizada pelo resistor.

A finalização do circuito na Protoboard se dá pela união da extremidade negativa do LED ao furo que levará a corrente a corrente em série representada pela linha de cor azul na placa. Por fim, com a saída do fio azul conectado ao pino GND (Ground) do Arduino, encerra-se o circuito completo.

BOX 4. Fritzing

Fritzing é uma iniciativa de hardware open-source que torna a eletrônica acessível a todos. Trata-se de um projeto que, além de fornecer uma ferramenta de edição de circuitos, possui uma comunidade já bastante grande e ativa na troca de experiências e ideias no que diz respeito à criação e processamento com Arduino, promovendo um ecossistema criativo que permite aos usuários a documentação de seus protótipos, o compartilhamento com outros usuários, o ensinamento de eletrônica através de classes virtuais, além de permitir o layout e manufatura de placas de circuitos impressos (PCB ou Printed Circuit Board, em inglês) em um ciclo completo, da prototipação à produção.

A ferramenta Fritzing é ótima para que o entusiasta que ainda não possua as ferramentas necessárias (Protoboard, Arduino, Raspberry PI, LEDs, resistores, entre outros) possa iniciar imediatamente o design de seus protótipos, mapeando toda a ideia, desde a simulação em uma Protoboard, até o desenho do circuito impresso, que posteriormente poderá ser produzido em larga escala.

A partir disso, durante a execução do Arduino, se o pino 13 for configurado para enviar energia, então o LED se acenderá, caso contrário, ele permanecerá apagado, uma vez que não existirá passagem de energia para alimentá-lo. Este costuma ser o exemplo mais simples para ilustrar um projeto eletrônico com Arduino e Protoboard.

Uma vez que o pino 13 no Arduino já possui um resistor embutido, o que impede que o equipamento seja queimado em um uso incorreto, na prática, o resistor utilizado na Figura 2 torna-se opcional, podendo ser substituído por um fio comum apenas para continuar ligando os pontos e manter a corrente elétrica.

A partir disso, o leitor mais destemido poderá realizar testes como mudar a conexão do pino 13 para o pino 12, substituir o resistor por um fio simples e ter a primeira lição sobre LEDs: sem um resistor, uma vez que LEDs geralmente exigem menos corrente elétrica que cinco volts, o LED queima.

Design de um circuito acendedor de LED
Figura 2. Design de um circuito acendedor de LED.

Sketches, para que te quero!

Em um projeto de circuitos eletrônicos para a construção do protótipo de um produto da Internet das Coisas é preciso que haja pelo menos um chip controlador com possibilidade de ser programado de forma rígida, de modo a exercer alguma ação sistemática (firmware).

Tais controladores são geralmente programados em C ou em outra linguagem de programação nativa do controlador, como Assembly. O chip é essencial para que o sistema eletrônico deixe de ser estático e passe a ser dinâmico, permitindo a mudança de comportamento e tomada de decisão sem a necessidade de alteração nos componentes físicos.

Nota: Em eletrônica e computação, firmware é o conjunto de instruções operacionais programadas diretamente no hardware de um equipamento eletrônico, armazenado permanentemente em seu circuito integrado (chip) como uma ROM, PROM, EPROM, EEPROM ou memória flash, no momento da fabricação do componente.

O uso desse tipo de controlador através do Arduino se tornou viável em prototipações devido à sua flexibilidade de programação, seu baixo preço e facilidade de aquisição. A programação do chip no Arduino é feita na linguagem C através da criação de códigos chamados Sketches (ou, no singular, Sketch), que são basicamente instruções compiladas em um template bem definido dentro de uma API de programas para Arduino.

Para a criação desses Sketches, existe uma IDE (vide Figura 3) desenvolvida em Java que facilita não somente a escrita desse tipo de programa, como também o seu envio para os dispositivos Arduino (ou compatíveis) de forma fácil e transparente.

IDE Arduino
Figura 3. IDE Arduino.

Como firmwares, os Sketches são escritos para executar uma função específica e são limitados pela quantidade de memória disponível no microcontrolador. Portanto, para o desenvolvimento de programas mais elaborados, o desenvolvedor pode ter que lidar com muitas iterações de testes somente para garantir que a memória esteja sendo utilizada de forma otimizada.

Além disso, pequenas modificações podem representar maiores problemas de otimização e manutenção pela complexidade inerente aos códigos escritos na linguagem C.

Este processo é bastante cansativo para o desenvolvedor que está acostumado às vantagens oferecidas pela linguagem Java, principalmente por ela ser Orientada a Objetos (e agora também Funcional) e por permitir que todo o desenvolvimento seja realizado em qualquer plataforma antes de ser implantado em um ambiente final, vantagens estas que não podem ser alcançadas através da escrita tradicional dos Sketches em C.

A fim de eliminar esta curva inicial de aprendizado para trabalhar com Sketches, o desenvolvedor Java pode contar com o projeto JArduino, disponível no GitHub. Este possibilita o controle de um Arduino via Java através da porta serial.

Com isso, apenas um Sketch necessita ser instalado no controlador de uma vez por todas e todo o controle passa a ser feito via Java de forma dinâmica, com possibilidade de alteração do comportamento do microcontrolador sem necessidade de mudança de firmware. Esta abordagem se apresenta como ótima escolha para o desenvolvedor Java que quer iniciar imediatamente seus primeiros protótipos sem ter a preocupação de mergulhar nos detalhes de códigos Sketches mais complexos.

Além disso, como o controle via Java necessita de um microprocessador atuando em conjunto com o Arduino para que a comunicação ocorra, automaticamente o desenvolvedor ganha um meio mais poderoso de processamento através de uma JVM instalada no dispositivo micro processado. Nestas circunstâncias, o Raspberry PI se apresenta como uma ótima forma de integração com o Arduino para fornecer não somente capacidade e flexibilidade de processamento, mas também de comunicação com a internet e com outros dispositivos; tudo isto em um computador muito pequeno e fácil de ser embarcado.

É preciso realmente conhecer eletrônica para criar “coisas”?

A melhor forma de responder a esta pergunta é através da seguinte analogia: suponha que exista uma oportunidade de negócios para a sua empresa e que o seu cliente tenha agendado uma reunião em seu escritório para ver a amostra de um sistema que você prometeu, contendo pesquisas, relatórios, persistência de dados, acesso via qualquer browser (cross-browser), envio de e-mails avisando sobre eventos importantes que tenham ocorrido, tudo em uma interface super bonita, fácil de usar e intuitiva.

Você certamente não terá tempo para lançar mão de todas as suas aptidões de arquiteto Java e de toda a sua capacidade para conceber um sistema bem distribuído, com processamento paralelo, orientado a serviços, com processamento de mensagens em background e com o que há de mais bem pensado em design patterns e orientação a objetos e, quem sabe, até utilizar o paradigma Funcional com Java 8.

É claro que é essa a sua intenção depois de vender o projeto. Você quer fazer algo bem feito, que seja escalável e de fácil manutenção, mas para a apresentação que deverá acontecer em dois dias, o melhor que você conseguirá é um protótipo persistindo dados na sessão do usuário em java.util.List ou, na melhor das hipóteses, java.util.Map.

E isto não será um problema, pois o que o cliente quer ver em um primeiro momento é se a ideia funciona, se vai agilizar o seu negócio, se vai facilitar a sua vida e a de seus funcionários, gerando valor, seja financeiro ou de outra natureza. Ele não precisa ter certeza de que você é um Arquiteto Java naquele momento ou se você tem esse profissional em sua equipe.

Essas aptidões você pode aprender depois que o projeto estiver em suas mãos, ou até mesmo esse profissional pode ser contratado.

Assim como um protótipo de sistema não requer a utilização de todas as técnicas de arquitetura e desenvolvimento, a prototipação em dispositivos físicos também não precisará das melhores práticas em termos de composição de circuitos complexos e cálculos de resistência ideal.

Tais complexidades podem ser pensadas quando o projeto chegar na fase de construção para uma versão de produção que exija algo mais requintado. Sendo assim, para o desenvolvimento de um modelo funcional de uma primeira ideia é possível começar adquirindo kits completos, baratos, de montagem simplificada (através de componentes plugáveis uns aos outros), com sensores, atuadores, fios, Protoboards e Arduino.

Com um kit desses em mãos, criar um protótipo de um robô com capacidade de se locomover, de utilizar uma câmera para identificar coisas, de proferir algumas palavras através de uma placa de som e que pisque um LED quando estiver “feliz” é relativamente fácil e de fato existem muitos kits já prontos para isso disponíveis no mercado.

É claro que a ideia deste artigo não é que o leitor aprenda a criar seus próprios brinquedos, mas sim que tenha um ponto de partida para a descoberta de um mundo novo, que está aí para ser explorado.

Da mesma forma que dificilmente o aprendizado de JSPs se iniciará em um projeto sofisticado com PrimeFaces ou qualquer outra implementação de JSF, o caminho para se chegar a um produto acabado de um dispositivo IoT deve ter seu início em protótipos simples, mas com alguma utilidade, de forma a levar o desenvolvedor a conhecimentos mais complexos conforme ele se sentir mais familiarizado com as tecnologias disponíveis.

Enfim, “baby steps” são essenciais no aprendizado de qualquer tecnologia, mesmo em eletrônica.

Baby-Steps é um termo utilizado para denominar a prática de dividir problemas complexos ou muito grandes em problemas menores, mais fáceis de serem resolvidos e compostos em uma solução maior que culmine na resolução do problema inicial. Podemos substituir facilmente este termo pela frase “dividir para conquistar”.

Nota: Atuadores, assim como sensores, são componentes eletrônicos que se acoplam a controladores como Arduino ou Raspberry PI. A diferença está na função, pois os atuadores são componentes destinados à exibição de informações ou ao diálogo com o usuário, diferentemente dos sensores, que se destinam à obtenção de dados do mundo externo.

Prototipação na prática

Agora, vamos modelar um dispositivo que será embutido na parede, no teto de uma casa, preferencialmente próximo à porta de entrada, para que quando chegue uma visita o aparelho calcule e exiba sua altura em um tablet localizado em cima da mesa central (quem sabe, no futuro, coloquemos uma balança no carpete da entrada e integremos as duas informações para determinar o IMC do visitante).

Como verificado, o experimento será simples: utilizaremos um sensor ultrassônico capaz de detectar objetos com precisão de dois a quatro metros de distância, dependendo do ruído. Este sensor será conectado a um Arduino e terá suas portas controladas através de um Raspberry PI. Na Figura 4 são apresentados todos os componentes utilizados no protótipo do medidor de altura, a saber:

· Quatro fios (Jumpers);

· Um sensor ultrassônico;

· Um suporte para o sensor;

· Uma pequena placa Protoboard de 170 furos;

· Um Arduino Uno R3;

· Um cabo de alimentação USB de 5V (cinco volts) para o Arduino;

· Um Raspberry PI 2 Modelo B;

· Um adaptador Wi-Fi que irá conectar o Raspberry PI à rede local;

· Um cartão SD que irá conter o sistema operacional Linux;

· Uma fonte de alimentação de 2A (dois amperes) para o Raspberry PI.

Componentes utilizados para criar o medidor de altura
por ultrassom
Figura 4. Componentes utilizados para criar o medidor de altura por ultrassom.

O experimento final terá a aplicação Java rodando no Raspberry PI. Todavia, durante o desenvolvimento é possível realizar todo o procedimento de testes diretamente em um laptop ou PC, sem a necessidade do Raspberry PI, pois a comunicação com o Arduino se dá via USB.

Portanto, toda a implementação do código Java pode ser feita em uma IDE Java qualquer e, inclusive, incluir testes unitários que realizem a conexão diretamente com o dispositivo para verificar a funcionalidade, sendo este um dos maiores benefícios desta abordagem.

O JArduino será essencial em todas as etapas do experimento, desde a compilação do Sketch diretamente no dispositivo até a utilização da API disponibilizada como forma de comunicação com o microcontrolador na aplicação Java.

Dito isso, o primeiro passo é baixar o projeto JArduino do repositório GIT, que pode ser encontrado na seção Links (é necessário que o projeto seja obtido deste link, pois ele possui customizações feitas especificamente para o protótipo aqui apresentado).

Com o código do GitHub em mãos, no subprojeto jarduino.core é possível encontrar o diretório src/main/arduino/ e nele o projeto JArduinoFirmware, que deve ser aberto via IDE Arduino e posteriormente compilado e transferido ao dispositivo. Este procedimento é bem simples: com o projeto aberto na IDE, basta clicar no botão carregar – segundo ícone no canto superior esquerdo – e aguardar a compilação e transferência do programa ao chip.

Após o primeiro passo de transferência do código de firmware para o Arduino, todo o restante do processo se desenvolve na camada Java. Antes disso, no entanto, ainda no projeto baixado do GitHub, iremos encontrar vários módulos compostos em um projeto Maven que pode ser compilado para que seus artefatos finais (os arquivos JAR) sejam incorporados como bibliotecas em novos projetos.

Estes componentes viabilizarão a comunicação serial com o microcontrolador e conterão a camada de comunicação com o Sketch (firmware) anteriormente carregado no dispositivo, facilitando todo o controle do Arduino via Java como se o controle fosse diretamente via firmware.

A partir deste ponto é possível criar um projeto Java e incorporar a nova API recém-compilada como dependência da aplicação para comunicação com o controlador Arduino.

No exemplo deste artigo, o Maven foi utilizado para gerenciar as dependências e o build. Como é possível notar na Listagem 1, a declaração do projeto inclui três dependências principais da API JArduino: org.sintef.jarduino:core, que fornece a estrutura base de controle dos Sketches via Java (principalmente a classe abstrata org.sintef.jarduino.JArduino); org.sintef.jarduino:serial, que fornece a classe de acesso à comunicação serial com o Arduino (através da classe org.sintef.jarduino.comm.Serial4JArduino); e, finalmente, a dependência org.kevoree.extra.osgi:rxtx, que proporciona à implementação Serial4JArduino a comunicação com o GPIO do dispositivo.

Listagem 1. Declaração do projeto hmeter com Maven.


 1.  <?xml version="1.0"?>
  2.  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4.   <modelVersion>4.0.0</modelVersion>
  5.  
  6.   <parent>
  7.    <groupId>br.com.pontoclass</groupId>
  8.    <artifactId>iot</artifactId>
  9.    <version>1.0.0-SNAPSHOT</version>
  10.  </parent>
  11. 
  12.  <groupId>br.com.pontoclass.iot</groupId>
  13.  <artifactId>hmeter</artifactId>
  14.  <name>Internet of Things - Height Meter Electronic Project</name>
  15. 
  16.  <properties>
  17.   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18.  </properties>
  19. 
  20.  <dependencies>
  21.   <dependency>
  22.    <groupId>junit</groupId>
  23.    <artifactId>junit</artifactId>
  24.    <scope>test</scope>
  25.   </dependency>
  26. 
  27.   <dependency>
  28.    <systemPath>${basedir}/lib/JArduino/org.sintef.jarduino.core-0.1.7.jar</systemPath>
  29.    <scope>system</scope>
  30.    <groupId>org.sintef.jarduino</groupId>
  31.    <artifactId>core</artifactId>
  32.    <version>0.1.7-SNAPSHOT</version>
  33.   </dependency>
  34. 
  35.   <dependency>
  36.    <systemPath>${basedir}/lib/JArduino/org.sintef.jarduino.serial-0.1.7.jar</systemPath>
  37.    <scope>system</scope>
  38.    <groupId>org.sintef.jarduino</groupId>
  39.    <artifactId>serial</artifactId>
  40.    <version>0.1.7-SNAPSHOT</version>
  41.   </dependency>
  42. 
  43.   <dependency>
  44.    <systemPath>${basedir}/lib/JArduino/org.kevoree.extra.osgi.rxtx-2.2.0.jar</systemPath>
  45.    <scope>system</scope>
  46.    <groupId>org.kevoree.extra.osgi</groupId>
  47.    <artifactId>rxtx</artifactId>
  48.    <version>2.2.0</version>
  49.   </dependency>
  50. 
  51.   <dependency>
  52.    <groupId>javax.websocket</groupId>
  53.    <artifactId>javax.websocket-client-api</artifactId>
  54.    <version>1.0</version>
  55.   </dependency>
  56. 
  57.   <dependency>
  58.    <groupId>org.glassfish.tyrus</groupId>
  59.    <artifactId>tyrus-container-grizzly-client</artifactId>
  60.    <version>1.8.3</version>
  61.   </dependency>
  62.  </dependencies>
  63. 
  64.  <build>
  65.   <finalName>hmeter</finalName>
  66.   <plugins>
  67.    <plugin>
  68.     <groupId>org.apache.maven.plugins</groupId>
  69.     <artifactId>maven-compiler-plugin</artifactId>
  70.     <version>3.3</version>
  71.     <configuration>
  72.      <source>1.8</source>
  73.      <target>1.8</target>
  74.     </configuration>
  75.    </plugin>
  76. 
  77.    <plugin>
  78.     <groupId>org.apache.maven.plugins</groupId>
  79.     <artifactId>maven-shade-plugin</artifactId>
  80.     <version>2.4.1</version>
  81.     <executions>
  82.      <execution>
  83.       <phase>package</phase>
  84.       <goals>
  85.        <goal>shade</goal>
  86.       </goals>
  87.       <configuration>
  88.        <transformers>
  89.         <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
  90.          <manifestEntries>
  91.           <Main-Class>br.com.pontoclass.iot.hmeter.main.Bootstrap</Main-Class>
  92.           <Build-Number>${project.version}</Build-Number>
  93.          </manifestEntries>
  94.         </transformer>
  95.        </transformers>
  96.        <minimizeJar>true</minimizeJar>
  97.        <artifactSet>
  98.         <includes>
  99.          <include>br.com.pontoclass.iot:hmeter</include>
  100.        </includes>
  101.       </artifactSet>
  102.      </configuration>
  103.     </execution>
  104.    </executions>
  105.   </plugin>
  106.  </plugins>
  107. </build>
  108.</project>

Esta declaração do pom.xml possui como projeto pai (parent) o br.com.pontoclass:iot, que irá configurar o experimento por completo, já que, como veremos mais adiante, o exemplo deste artigo também contará com um módulo web para a implementação da camada que ligará a aplicação de controle do sensor com a visualização do seu estado em um browser.

É importante notar que as dependências supracitadas foram declaradas no projeto com escopo system, fornecendo um diretório através do systemPath, que indica aonde o arquivo JAR da dependência se encontra diretamente dentro do projeto.

Esta abordagem é necessária porque o projeto JArduino ainda não é armazenado em nenhum repositório público do Maven, pelo menos até o momento da escrita deste artigo.

Por este motivo, para que essas dependências sejam geradas, é necessário que o desenvolvedor efetue o build – mvn install – do projeto JArduino baixado do repositório GitHub citado anteriormente e as configure diretamente no projeto como demonstrado na Listagem 1.

Uma vez realizado o procedimento de criação do projeto e configuração das dependências, podemos partir para a codificação do Sketch Java que irá controlar o verdadeiro Sketch no firmware do Arduino.

A partir deste momento é importante que o leitor observe a mudança de significado do termo Sketch, pois uma vez instalado o Sketch do projeto JArduino no dispositivo através da IDE Arduino, todo o resto da implementação em Java tratará de encapsular a complexidade de comunicação com essa camada nativa e começaremos a chamar de Sketch esta implementação Java, que por sua vez possuirá os métodos de controle: setup() e loop().

Antes de chegarmos à codificação da classe que implementará estes dois métodos, no entanto, precisamos criar o código Java capaz de encontrar o dispositivo através de sua porta, independentemente de qual Sistema Operacional executará esse código.

Isto é extremamente importante para que seja mantida a portabilidade inerente ao Java; caso contrário, a aplicação poderá funcionar em determinados sistemas, como Linux ou OS X, e deixar de funcionar em outros sistemas, como o Windows.

Na Listagem 2, a classe br.com.pontoclass.iot.hmeter.scketch.AbstractArduinoSketch foi criada com a finalidade de facilitar este processo de conexão dos Sketches Java com o Arduino. Tal conexão acontece no bloco estático da classe, quando tentamos encontrar o possível nome da porta de comunicação serial para o Sistema Operacional detectado por meio da propriedade de sistema obtida na linha 12, através da chamada System.getProperty("os.name").toLowerCase().

Listagem 2. Código da classe abstrata AbstractArduinoSketch.


 1.  package br.com.pontoclass.iot.hmeter.scketch;
  2.
  3.  import gnu.io.CommPortIdentifier;
  4.  import java.util.Enumeration;
  5.  import org.sintef.jarduino.JArduino;
  6.  
  7.  public abstract class AbstractArduinoSketch extends JArduino {
  8.       private static final String[] PORT_NAMES;
  9.       private static String    TRIED_PORT_NAME;
  10. 
  11.     static {
  12.                 final String OS = System.getProperty("os.name").toLowerCase();
  13.                 if(OS.contains("win")) {
  14.                            PORT_NAMES = new String[] {"COM"};
  15.                 } else if(OS.contains("mac")) {
  16.                            PORT_NAMES = new String[] {"/dev/tty.usbmodem"};
  17.                 } else if(OS.contains("nix") || OS.contains("nux") || OS.contains("aix")) {
  18.                            PORT_NAMES = new String[] {"/dev/usbdev", "/dev/tty", "/dev/serial"};
  19.                 } else if(OS.contains("sunos")) {
  20.                            PORT_NAMES = new String[] {"/dev/tty"};
  21.                 } else {
  22.                            PORT_NAMES = new String[] {"/dev/tty"};
  23.                 }
  24.                 @SuppressWarnings("unchecked") Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers();
  25.                 System.out.println("Trying:");
  26.                 boolean set = false;
  27.                 outer: while (portEnum.hasMoreElements()) {
  28.                            CommPortIdentifier currPortId = (CommPortIdentifier) portEnum.nextElement();
  29.                            System.out.println(String.format("\tPort [%s]", currPortId.getName()));
  30.                            for (String portName: PORT_NAMES) {
  31.                                        if (currPortId.getName().startsWith(portName)) {
  32.                                                    TRIED_PORT_NAME = currPortId.getName();
  33.                                                    set = true;
  34.                                                    break outer;
  35.                                        }
  36.                            }
  37.                 }
  38.                 if(!set) {
  39.                            TRIED_PORT_NAME = "/dev/tty";
  40.                 }
  41.     }
  42. 
  43.     public AbstractArduinoSketch() {
  44.                 super(TRIED_PORT_NAME);
  45.     }
  46. }

Observa-se que a classe AbstractArduinoSketch está estendendo (herdando) org.sintef.jarduino.JArduino e se mantendo abstrata, obrigando às suas implementações concretas que sobrescrevam os métodos <setup() e loop().

Estes são os métodos disponíveis na API JArduino para que o desenvolvedor possa, respectivamente, configurar a aplicação e os pinos GPIO a serem utilizados e executar as ações inerentes ao comportamento esperado do dispositivo. Uma vez que a classe AbstractArduinoSketch é abstrata, ela precisará ser estendida por uma classe concreta para implementar estes métodos e, desta forma, criar o comportamento do Sketch. Para esta função, na Listagem 3, a classe br.com.pontoclass.iot.hmeter.sketch.Ultrassonic é declarada.

Listagem 3. Declaração da classe Ultrassonic.


 1.  package br.com.pontoclass.iot.hmeter.sketch;
  2.  
  3.  import java.util.logging.Logger;
  4.  import java.util.stream.IntStream;
  5.  import org.sintef.jarduino.DigitalPin;
  6.  import org.sintef.jarduino.PinMode;
  7.  import br.com.pontoclass.iot.hmeter.strategy.Strategy;
  8.  import br.com.pontoclass.iot.hmeter.websocket.WebSocket;
  9.  
  10.  public class Ultrassonic extends AbstractArduinoSketch {
  11.  
  12.     private static final int AVERAGE_PRECISION = 10;
  13.     private static final Logger LOGGER = Logger.getLogger(Ultrassonic.class.getName());
  14.     private Strategy strategy = Strategy.Available;
  15.     private WebSocket socket = new WebSocket(this, "ws://localhost:8080/websocket/hmeter");
  16.     private float lastResult = -1;
  17.     
  18.     @Override protected void loop() {
  19.                 synchronized(strategy) {
  20.                            float result = strategy.execute(this);
  21.                            if(lastResult != result) {
  22.                                        LOGGER.info(String.format("Último resultado (diferente) obtido do medidor: [%f].", result));
  23.                                        lastResult = result;
  24.                            }
  25.                 }
  26.     }
  27.  
  28.     @Override protected void setup() {
  29.                 LOGGER.info("Iniciando o medidor Ultrassônico...");
  30.                 socket.connect();
  31.                 pinMode(DigitalPin.PIN_4, PinMode.OUTPUT);
  32.                 pinMode(DigitalPin.PIN_5, PinMode.INPUT);
  33.     }
  34.  
  35.     public float average() {
  36.                 return IntStream.range(1, AVERAGE_PRECISION)
  37.                                          .mapToObj(i -> ultrassonic(DigitalPin.PIN_4,
  38.                                                                                  DigitalPin.PIN_5)/29f/2f/100f)
  39.                                          .reduce((a, b) -> a+b)
  40.                                          .orElse(0f)/AVERAGE_PRECISION;
  41.     }
  42.     
  43.     public void setStrategy(Strategy strategy) {
  44.                 synchronized(strategy) {
  45.                            this.strategy = strategy;
  46.                 }
  47.     }
  48.  
  49.     public WebSocket getWebSocket() {
  50.                 return this.socket;
  51.     }
  52.  }

Os métodos mais importantes a serem observados nessa classe são setup() e loop(). São eles que simulam o mecanismo dos Sketches nativos do Arduino. Nesses Sketches (escritos em C) também são disponibilizados dois métodos homônimos que possuem as mesmas funções dos aqui implementados em Java.

Ou seja, a classe Ultrassonic é basicamente um Sketch Java com as mesmas funções que teria se fosse escrito como firmware diretamente no micro controlador, porém com todos os benefícios de ser uma classe Java.

Em nosso exemplo, o método setup() realiza a configuração do pino 5 como saída – trigger – e do pino 4 como entrada – echo – do sensor ultrassom (vide BOX 5).

Na Figura 5 podemos ver a conexão física entre o Arduino e o sensor através do fio amarelo no pino 5 do dispositivo conectando-se à entrada trigger do sensor, e através do fio azul no pino 4 do dispositivo conectado à saída echo do sensor. Para que o sistema sensorial funcione, o fio vermelho conectado à saída de energia do Arduino recebe 5V (cinco volts) para alimentá-lo, e a saída GND do sensor finaliza o circuito ao ser conectado ao pino de mesmo nome no Arduino.

BOX 5. Sensores Ultrassônicos

Sensores ultrassônicos são dispositivos capazes de detectar corpos (objetos, pessoas, substâncias) por meio do envio de sinais ultrassonoros propagados pela parte emissora (trigger) e da recepção da onda refratada pelo corpo e recebida pelo detector (echo). Através do cálculo do tempo passado entre a emissão e a recepção da onda em microssegundos, o sensor consegue calcular com boa precisão a distância entre ele e o objeto refrator (corpo).

Curiosamente, os sensores ultrassônicos, como o apresentado neste artigo, utilizam o mesmo princípio de localização de objetos realizada pelos morcegos, que também se utilizam da técnica de emissão sonora para obter uma base da localização das coisas que estão pelo seu caminho.

imagem
Figura 5. Conexão entre Arduino e sensor ultrassônico.

Ainda no método setup(), podemos perceber que existe uma instância da classe br.com.pontoclass.iot.hmeter.websocket.WebSocket tendo o seu método connect() invocado (linha 30).

Esta classe e todo o restante do experimento serão explorados no próximo artigo, quando implementaremos toda a camada web com WebSockets para apresentar os dados extraídos da medição com o sensor ultrassônico e para permitir a interação via interface com o dispositivo, permitindo solicitar a sua calibragem e criando uma integração de ponta a ponta, do hardware à visualização web.

No momento da chamada ao método connect() é realizada a tentativa de conexão com o servidor WebSocket que conectará o Sketch Ultrassonic à camada de visualização responsável por exibir a medida de altura das pessoas que se posicionarem abaixo do dispositivo.

Note que a variável socket é instanciada na linha 15, recebendo o próprio objeto Ultrassonic através do this (no próximo artigo veremos como o WebSocket irá utilizar esta instância) e uma String com a URL da aplicação web em que o WebSocket está escutando por novas conexões.

Nesta primeira parte do nosso estudo sobre o mundo da Internet das Coisas, avançamos bastante na compreensão da IoT e porquê ela é o futuro.

Com o conhecimento adquirido até aqui já é possível exercitar a integração de novos sensores ao dispositivo, dando a ele quaisquer outras funções de acordo com a capacidade desejada (detectar imagens ou sons, por exemplo), pois o princípio de controle do GPIO é sempre o mesmo. Para tanto, é importante informar-se muito bem quando da aquisição de novos sensores, para saber como eles trabalham, se necessitam de resistores ou outros tipos de componentes auxiliares na montagem de seus circuitos.


PARTE II
Veja abaixo a segunda parte do artigo - Agora as partes I e II foram compiladas em um único artigo. Bons estudos :)


Na primeira parte que iniciou esta jornada, vimos o que é e como funciona a Internet das Coisas, bem como porquê ela é importante e se apresenta como um presente já promissor.

Para isso, criamos um protótipo de hardware que funciona como medidor de altura e agora chegou a vez de conectá-lo à internet e dar sentido ao termo IoT. Deste modo, vamos transformar o nosso dispositivo limitado em um aparelho que pode exibir essas medidas de altura a qualquer usuário na internet e com potencial, inclusive, de se conectar a redes sociais com poucas alterações.

No artigo que deu início aos nossos primeiros passos em IoT concluímos que conhecer a Internet das Coisas, bem como saber aonde essas novas tecnologias podem nos levar é extremamente importante, pois percebemos que IoT é o modelo de construção de soluções na internet do futuro e que já se apresenta muito promissora no presente.

Neste cenário, a internet ganha uma tendência de descentralização ao passo que os dispositivos começam a se intercomunicar independentemente de servidores, alimentando outros sistemas de forma mais automática, com menor dependência de interação humana.

Ao mesmo tempo, as pessoas passam a estar mais conectadas não somente a outras pessoas, mas também a outros dispositivos e ambientes, como a própria casa ou aparelhos eletrodomésticos.

Neste paradigma, ambientes e coisas ganham a possibilidade de gerenciarem a si próprios com pouca ou nenhuma interação humana. Esta é uma visão futurística, mas que, na verdade, já faz parte da realidade em países mais evoluídos tecnologicamente; uma pesquisa rápida pelo termo “Internet of Things” em vídeos na internet, demonstra que existe uma quantidade enorme de organizações já visualizando IoT como o próximo passo de evolução das tecnologias modernas.

Seguindo o caminho de descoberta, neste artigo daremos continuidade ao experimento iniciado na publicação anterior, quando iniciamos o desenvolvimento de um dispositivo capaz de medir a altura de um corpo que se posicione abaixo do sensor ultrassônico.

Já criamos toda a camada de controle do Arduino através da instalação do JArduino como firmware e da criação do Sketch Java, porém ainda não desenvolvemos a solução web que colocará o nosso aparelho na internet, para que o seu estado esteja disponível como informação em uma aplicação. Para esta solução, vamos utilizar WebSockets e ligar o dispositivo à camada de visualização web, promovendo interatividade entre o usuário e o aparelho. Portanto, mãos à obra!

Conectando coisas à Internet

Habilitar o nosso medidor de altura para a Internet é uma parte importante do processo de criar um dispositivo IoT. Sem que haja conectividade e disponibilidade do equipamento para o “mundo”, não há também a Internet das Coisas.

Em nosso experimento, o objetivo é torna-lo disponível para que qualquer interface, seja através de um dispositivo móvel como um smartphone ou através de uma página web, possa apresentar o estado do sensor e também possa interagir com o dispositivo a qualquer momento solicitando a sua calibragem.

Neste experimento abordaremos a implementação de uma página web que se comunique com um WebSocket para obter e enviar informações ao equipamento, porém, uma vez desenvolvida a aplicação WebSocket, qualquer outro meio de diálogo com usuário poderá se conectar ao dispositivo simultaneamente, permitindo que diferentes modelos de apresentação da informação do sensor sejam integrados.

Um exemplo positivo desta flexibilidade pode ser verificado em uma situação em que o desenvolvedor queira dar suporte a deficientes auditivos, por exemplo. Neste caso, uma aplicação conectada a um alto-falante e a um microfone pode proporcionar o mesmo nível de interatividade ao usuário com tal deficiência, sem a necessidade de reconstrução de toda a aplicação, dependendo somente da criação de mais um cliente WebSocket.

Como verificado na Listagem 1, o cliente WebSocket é criado através da classe br.com.pontoclass.iot.hmeter.webcosket.WebSocket, que atua como um Endpoint – ou seja, um ponto de destino do WebSocket – encapsulando toda a complexidade de conexão e mensageria.

É através dessa classe que o Sketch Java se comunica com o “mundo” para informar sobre sua alteração de estado e para receber o pedido de calibragem. Nesta classe, o método onMessage (String message) recebe as mensagens que chegam do servidor web que criaremos mais adiante e as redireciona para o método byName (String message), do enum br.com.pontoclass.iot.hmeter.strategy.Strategy (vide Listagem 2).

Este, por sua vez, retorna um valor de enum dependendo do conteúdo da mensagem. Tal resultado é utilizado para reconfigurar o Sketch representado pela instância ultrassonic através do seu método setStrategy (Strategy strategy).

Listagem 1. Código da classe WebSocket.


  1.  package br.com.pontoclass.iot.hmeter.websocket;
  2.  
  3.  import java.io.IOException;
  4.  import java.net.URI;
  5.  import java.net.URISyntaxException;
  6.  import javax.websocket.ClientEndpoint;
  7.  import javax.websocket.ContainerProvider;
  8.  import javax.websocket.DeploymentException;
  9.  import javax.websocket.OnMessage;
  0.  import javax.websocket.OnOpen;
  1.  import javax.websocket.Session;
  2.  import javax.websocket.WebSocketContainer;
  3.  import br.com.pontoclass.iot.hmeter.sketch.Ultrassonic;
  4.  import br.com.pontoclass.iot.hmeter.strategy.Strategy;
  5.  
  6.  @ClientEndpoint
  7.  public class WebSocket {
  8.  
  9.       private URI uri;
  10.     private Session session;
  11.     private Ultrassonic ultrassonic;
  12.     
  13.     public WebSocket(Ultrassonic ultrassonic, String uri) {
  14.                 this.ultrassonic = ultrassonic;
  15.                 try {
  16.                            this.uri = new URI(uri);
  17.                 } catch (URISyntaxException e) {
  18.                            throw new RuntimeException(e.getMessage());
  19.                 }
  20.     }
  21.     
  22.     public void connect() {
  23.                 WebSocketContainer container = ContainerProvider.getWebSocketContainer();
  24.                 try {
  25.                            this.session = container.connectToServer(this, getURI());
  26.                            this.session.getBasicRemote().sendText("CLIENT:HW");
  27.                 } catch (DeploymentException | IOException e) {
  28.                            throw new RuntimeException(e.getMessage());
  29.                 }
  30.     }
  31.  
  32.     public URI getURI() {
  33.                 return uri;
  34.     }
  35.  
  36.     public void setUrl(URI uri) {
  37.                 this.uri = uri;
  38.     }
  39.      
  40.      public @OnOpen void onOpen(Session session) {
  41.          this.session = session;
  42.      }
  43.      
  44.      public @OnMessage void onMessage(String message) {
  45.                 ultrassonic.setStrategy(Strategy.byName(message));
  46.      }
  47.      
  48.      public void sendMessage(String value) throws IOException {
  49.                 session.getBasicRemote().sendText(value);
  50.      }
  51.  }

Desta forma, conforme o diálogo com o servidor WebSocket se desenvolve, o comportamento do Sketch Ultrassonic (visto no artigo publicado na Java Magazine 147) pode mudar devido à atualização do Strategy configurado para ser executado no método loop(). Para tornar mais clara a interatividade neste fluxo, o leitor deve se lembrar que na classe Ultrassonic o método loop() era acionado periodicamente para executar o Strategy configurado.

O Strategy padrão tratava de enviar uma mensagem ao servidor WebSocket a fim de informá-lo sobre a disponibilidade do dispositivo para ser utilizado. A partir desta primeira comunicação, este servidor WebSocket (que criaremos logo mais) tratará de intermediar a comunicação entre o Endpoint WebSocket da camada front-end (também a ser criado ainda neste artigo) e o WebSocket da Listagem 1.

Listagem 2. Declaração do enum Strategy.


1.  package br.com.pontoclass.iot.hmeter.strategy;
2.  
3.  import java.io.IOException;
4.  import java.util.Optional;
5.  import java.util.logging.Logger;
6.  import java.util.stream.IntStream;
7.  import br.com.pontoclass.iot.hmeter.sketch.Ultrassonic;
8.  
9.  public enum Strategy {
10.   Calibrate() {
11. 
12.    private static final int PRECISION = 5;
13.                 
14.      @Override
15.      public float execute(Ultrassonic ultrassonic) {
16.        try {
17.         ultrassonic.getWebSocket()
18.         .sendMessage("HW:BeginCalibrate");
19.         Float result = IntStream.range(1, PRECISION)
20.                                                                .mapToObj(i -> ultrassonic.average())
21.                                                                                       .reduce((a, b) -> a+b+0f)
22.                                                                                       .map(f -> f/PRECISION)
23.                                                                                       .orElse(-1f);
24.         ultrassonic.getWebSocket()
25.         .sendMessage(String.format("HW:Calibrate:%.2f",
26.                                                                                         result));
27.         return result;
28.       } catch (IOException e) {
29.         throw new RuntimeException(e.getMessage());
30.       } finally {
31.          ultrassonic.setStrategy(KeepAlive);
32.       }
33.     }
34.                 
35.    },
36.    KeepAlive() {
37. 
38.     @Override
39.     public float execute(Ultrassonic ultrassonic) {
40.       try {
41.        Float result = ultrassonic.average();
42.        ultrassonic.getWebSocket()
43.        .sendMessage(String.format("HW:KeepAlive:%.2f",
44.                                                                                          result));
45.        return result;
46.       } catch (IOException e) {
47.         throw new RuntimeException(e.getMessage());
48.       }
49.     }
50.    },
51.    Available() {
52. 
53.     private Logger LOGGER = Logger.getLogger(this.getClass().getName());
54.             
55.     @Override
56.     public float execute(Ultrassonic ultrassonic) {
57.                            
58.       LOGGER.info("No action set up...");
59.       try {
60.        ultrassonic.getWebSocket()
61.        .sendMessage("HW:HWAvailable");
62.       } catch (IOException e) {
63.         throw new RuntimeException(e.getMessage());
64.       }
65.       try {
66.        Thread.sleep(3000);
67.       } catch (InterruptedException e) {
68.         LOGGER.warning(e.getMessage());
69.       }
70.       return -1;
71.      }
72.     };
73.     
74.     public String getName() {
75.       return this.toString();
76.     }
77.     
78.     public abstract float execute(Ultrassonic ultrassonic);
79. 
80.     public static Strategy byName(String message) {
81.      return Optional.ofNullable(Strategy.valueOf(message))
82.      .orElse(KeepAlive);
83.     }
                                                                                                                                                                        
84. }

Neste experimento o enum Strategy é essencial, uma vez que é ele o responsável por encapsular todos os comportamentos do Sketch Ultrassonic.

Optamos por um enum para facilitar o processo de transformação da mensagem no formato String para o valor correto de enum, já que pela utilização de enum estamos limitando as instâncias possíveis e forçando que esses valores não tenham “estado” que possa gerar efeitos colaterais durante a execução.

Esses valores, então, determinam como o Sketch irá se comportar durante a execução do método loop() da classe Ultrassonic, dado que nesta classe existe apenas o redirecionamento da execução para a variável de instância strategy, que pode ser modificada dependendo da mensagem recebida pelo método onMessage (String message) de WebSocket.

Criamos este mecanismo para que durante a troca de mensagens o sensor possa receber e responder aos comandos do WebSocket. Com isto, estabelece-se um padrão de troca de mensagens em que a classe WebSocket modifica o Strategy em execução em função do valor recebido em formato de String (pedindo para que o sensor seja calibrado através do valor Calibrate, por exemplo), ao passo que durante a execução do próprio Strategy, ele pode enviar mensagens informando sobre o que está acontecendo (dizendo que o processo de calibragem foi iniciado, por meio da mensagem “HW:BeginCalibrate”, por exemplo).

No enum Strategy temos os três valores representando as ações possíveis dentro do Sketch: Available, Calibrate e KeepAlive. Available não executa nenhuma ação diretamente no sensor, mas sim na comunicação com o socket, através da chamada ultrassonic.getWebSocket().sendMessage("HW:HWAvailable"), nas linhas 60 e 61, enviando a informação com o valor “HW:HWAvailable” para informar aos outros nós – ou clientes – do WebSocket que o sensor está disponível para ser utilizado.

Como veremos mais tarde, tais clientes são as interfaces web acessadas via browser para que haja a interação do usuário com o dispositivo.

Esta e todas as outras mensagens seguem um pequeno protocolo criado neste experimento, que consiste no padrão “ID:AÇÃO[:PARAMS]”, onde ID é o identificador do cliente (aqui utilizamos HW, uma vez que o servidor WebSocket compreenderá este valor como sendo uma ação do hardware), e AÇÃO é o nome da ação a ser realizada pelos receptores. Caso existam outros valores separados por dois-pontos (:) – [:PARAMS] – eles serão considerados como parâmetros a serem enviados ao receptor. Calibrate, por sua vez, executa a ação de calibração do medidor de altura.

Para isso, inicialmente é enviada uma mensagem ao socket por meio da chamada ultrassonic.getWebSocket().sendMessage("HW:BeginCalibrate"), nas linhas 17 e 18, com o valor “HW:BeginCalibrate”, informando aos clientes do WebSocket sobre o início da calibração, para que eles tenham a oportunidade de exibir o estado do medidor.

Em seguida, após o término da calibração realizada no código da linha 19 a 23, é enviada uma nova mensagem, “HW:Calibrate:VALOR”, onde VALOR é o valor obtido através da média de leituras no medidor. Neste envio, o método String.format() é utilizado para transformar e formatar o resultado da calibração do tipo float em uma String com duas casas decimais (“%.2f”), desta forma permitindo que o padrão “ID:AÇÃO[:PARAMS]” seja mantido no envio da mensagem.

No final da calibração, o próprio enum Calibrate configura o Sketch Ultrassonic para que ele passe a executar a ação KeepAlive, através da chamada ultrassonic.setStrategy(KeepAlive). Com isso, o Sketch passa a realizar consultas periódicas no sensor, enviando o resultado dessa consulta no formato “HW:KeepAlive:VALOR”, onde VALOR é o resultado da média obtida através da chamada ultrassonic.average().

Como podemos perceber, a maior vantagem de utilizar Java para controlar o Arduino é a possibilidade de mudar o comportamento do Sketch em tempo de execução, da mesma forma que fazemos em uma aplicação Java qualquer.

Esta opção é sutil, mas extremamente poderosa, pois a partir desta implementação é possível adicionar comportamentos ao Sketch através da criação de novos valores no enum Strategy e de sua utilização no fluxo do loop no Sketch Ultrassonic. Por exemplo, imagine se o desenvolvedor decide que também vai adicionar um comportamento que transforme o medidor de altura em um alarme quando detectar a abertura da porta em um determinado horário.

Este comportamento seria muito fácil de ser adicionado. Seria possível, inclusive, alterar a implementação do Strategy para a utilização de uma interface com implementações em classes concretas ao invés de em um enum e com isso permitir a transferência de novos códigos Java que alterem o comportamento do Sketch – e consequentemente do sensor – sem necessidade de recompilar o código em execução.

Após a implementação do Sketch, é possível criar uma classe que o inicie e permaneça rodando, como o que foi feito na Listagem 3, que apresenta o código de br.com.pontoclass.iot.hmeter.main.Bootstrap.

Listagem 3. Código da classe Bootstrap.


1.  package br.com.pontoclass.iot.hmeter.main;
2.  
3.  import br.com.pontoclass.iot.hmeter.sketch.Ultrassonic;
4.  
5.  public class Bootstrap {
6.  
7.       private Ultrassonic ultrassonic;
8.  
9.       public static void main(String[] args) throws InterruptedException {
10.                 Bootstrap instance = new Bootstrap();
11.                 instance.start();
12.                 try {
13.                            instance.wait();
14.                 } finally {
15.                            instance.stop();
16.                 }
17.     }
18. 
19.     public Bootstrap() {
20.                 ultrassonic = new Ultrassonic();
21.     }
22.     
23.     private void start() {
24.                 ultrassonic.runArduinoProcess();
25.     }
26. 
27.     private void stop() {
28.                 ultrassonic.stopArduinoProcess();                 
29.     }
                                                            
30. }

A classe Bootstrap é uma implementação simples de uma classe com o método main() que instancia e inicia o Sketch Ultrassonic para que ele entre em loop durante todo o tempo de execução, comunicando-se diretamente com o Arduino para enviar e receber instruções. Com esta classe será possível salvar a aplicação em um arquivo JAR e executá-lo individualmente no Raspberry Pi ou em qualquer outro computador.

Porém, como o Sketch Ultrassonic conecta-se a um WebSocket, precisamos de uma aplicação Web com a implementação do lado servidor do socket. Tal implementação pode ser criada separadamente como um webapp que será executado no Raspberry Pi em um Web container Tomcat – ou qualquer outro container Web – como veremos no próximo tópico.

WebSocket das Coisas

No exemplo deste artigo, a implementação da camada web será realizada através da utilização da API WebSocket, especificada na JSR 356, da plataforma Java EE.

A opção pela utilização de WebSockets é bastante conveniente devido à possibilidade de fazer push – isto é, chamadas do servidor diretamente à camada front-end –, diferentemente de outras tecnologias como Servlets, que necessitariam de monitoração ativa por parte dos clientes no front-end.

Esta camada é criada em outro módulo Maven, cuja declaração pode ser conferida na Listagem 4. A declaração é bem simples e basicamente adiciona as dependências principais javax.websocket:javax.websocket-api:1.1 e javax:javaee-web-api:7.0.

Neste módulo criaremos tanto o WebSocket (server) quanto a página para visualização do estado no sensor, bem como a implementação Angular.js que possibilitará a interação entre as duas camadas.

Listagem 4. Declaração do projeto websocket com Maven.


1.  <?xml version="1.0"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4.       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5.       <modelVersion>4.0.0</modelVersion>
6.       
7.       <parent>
8.                   <groupId>br.com.pontoclass</groupId>
9.                   <artifactId>iot</artifactId>
10.                 <version>1.0.0-SNAPSHOT</version>
11.     </parent>
12.     
13.     <groupId>br.com.pontoclass.iot</groupId>
14.     <artifactId>websocket</artifactId>
15.     <packaging>war</packaging>
16.     <name>Internet of Things - Web Socket</name>
17.     
18.     <dependencies>
19.                 <dependency>
20.                            <groupId>junit</groupId>
21.                            <artifactId>junit</artifactId>
22.                            <scope>test</scope>
23.                 </dependency>
24.                 
25.                 <dependency>
26.                            <groupId>javax.websocket</groupId>
27.                            <artifactId>javax.websocket-api</artifactId>
28.                            <version>1.1</version>
29.                 </dependency>
30.                 
31.                 <dependency>
32.                            <groupId>javax</groupId>
33.                            <artifactId>javaee-web-api</artifactId>
34.                            <version>7.0</version>
35.                 </dependency>
36.     </dependencies>
37.     
38.     <build>
39.                 <finalName>websocket</finalName>
40.     </build>
41. </project>

A classe br.com.pontoclass.iot.websocket.HMeterServer (vide Listagem 5), cria um WebSocket de forma simplificada através da utilização das anotações previstas pela especificação. A sua função é realizar o tratamento de sessões e o recebimento das mensagens dos clientes.

Visto que queremos ver o resultado da medição de altura do nosso dispositivo em um navegador web, torna-se necessário abrir uma conexão WebSocket também da camada de visualização web com o servidor WebSocket, que, por sua vez, como visto na primeira parte do artigo, já tem sua conexão estabelecida com a implementação Sketch controladora do dispositivo.

Listagem 5. Implementação do WebSocket – classe HMeterServer.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.ArrayList;
4.  import java.util.Collections;
5.  import java.util.List;
6.  import java.util.Optional;
7.  import java.util.logging.Level;
8.  import java.util.logging.Logger;
9.  import javax.websocket.OnClose;
10. import javax.websocket.OnError;
11. import javax.websocket.OnMessage;
12. import javax.websocket.Session;
13. import javax.websocket.server.ServerEndpoint;
14.
15. public @ServerEndpoint("/hmeter") class HMeterServer {
16.     private static final Logger LOGGER = Logger.getLogger(HMeterServer.class.getName());
17.     private static Optional<Session> hWSession = Optional.empty();
18.     private static List<Session> webSessions = Collections.synchronizedList(new ArrayList<>());
19.
20.     public @OnClose void onClose(Session session) {
21.                 LOGGER.log(Level.INFO, "Conexão finalizada com o cliente: {0}", session.getId());
22.                 hWSession.ifPresent(s -> {
23.                            if(s.getId().equals(session.getId())) {
24.                                        ActionFactory.factory("HW")
25.                                                         .handle("HW:HWLost".split("\\:"), hWSession, webSessions);
26.                            }
27.                 });
28.                 webSessions.remove(session);
29.     }
30.
31.     public @OnError void onError(Throwable exception, Session session) {
32.                 LOGGER.log(Level.INFO, "Erro de conexão com o cliente: {0}", session.getId());
33.                 Optional.ofNullable(session)
34.                                        .map(Session::isOpen)
35.                                        .filter(Boolean.FALSE::equals)
36.                                        .ifPresent(webSessions::remove);
37.     }
38.     
39.     public @OnMessage void onMessage(String message, Session session) {
40.                 LOGGER.log(Level.INFO, "Mensagem recebida do cliente [{0}]: {1}",
41.                                                      new Object[]{session.getId(), message});
42.                 String[] protocol = message.split("\\:");
43.                 if("CLIENT".equalsIgnoreCase(protocol[0])) {
44.                            if("HW".equalsIgnoreCase(protocol[1])) {
45.                                        hWSession = Optional.of(session);
46.                            } else if("WEB".equalsIgnoreCase(protocol[1])) {
47.                                        webSessions.add(session);
48.                            }
49.                 } else {
50.                            Action action = ActionFactory.factory(protocol[0]);
51.                            LOGGER.log(Level.INFO, "Nova mensagem recebida do cliente [{0}]: {1}",
52.                                                                  new Object[] {session.getId(), message});
53.                            action.handle(protocol, hWSession, webSessions);
54.                 }
55.     }
                                                                                                                                                                                                          
56. }

Essa classe possui a função básica de limpar as sessões que forem fechadas nos métodos onClose() e onError() (quando houver perda de conexão) e, principalmente, realizar o mapeamento das mensagens recebidas para as ações correspondentes, como podemos ver no método onMessage().

As primeiras linhas deste método verificam se a mensagem recebida é a identificação de um novo dispositivo (HW) ou de um novo cliente web (WEB).

Desta forma, como podemos verificar no código anterior, da Listagem 1, a classe WebSocket envia a mensagem “CLIENT:HW” após o sucesso na conexão com o servidor WebSocket e esta mensagem é processada pela condição inicial do método onMessage() aqui citado, realizando a identificação dos clientes – Hardware ou Web.

Para todas as outras situações em que a mensagem siga o padrão “ID:AÇÃO[:PARAMS]”, existe a transformação da mensagem em uma ação através da chamada ActionFactory.factory(protocol[0]). O retorno deste método é uma instância da interface br.com.pontoclass.iot.websocket.Action, que será responsável pela interpretação do comando recebido. Nas Listagens 6 e 7 observamos as declarações da classe br.com.pontoclass.iot.websocket.ActionFactory e da interface br.com.pontoclass.iot.websocket.Action.

Listagem 6. Código da classe ActionFactory.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.HashMap;
4.  import java.util.Map;
5. 
6.  public class ActionFactory {
7.  
8.       private static final Map<String, Action> actions = new HashMap<>();
9.       private static final Action DEFAULT_ACTION = new NoAction();
10.     
11.     static {
12.                 actions.put("HW", new HardwareAction());
13.                 actions.put("WEB", new WebAction());
14.     }
15.     
16.     public static Action factory(String actionName) {
17.                 return actions.getOrDefault(actionName, DEFAULT_ACTION);
18.     }
19. }

Listagem 7. Código da interface Action.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  
6.  import javax.websocket.Session;
7. 
8.  public interface Action {
9. 
10.     void handle(String[] protocol, Optional<Session> hWSession, List<Session> webSessions);
11.
12. }

Na classe ActionFactory, os comportamentos são divididos em ações recebidas do Hardware (HW) e ações recebidas da Web (WEB), pois o front-end pode solicitar ao servidor WebSocket que o medidor de altura realize a calibragem, enquanto o Hardware enviará vários eventos como início de calibragem, execução de calibragem, valores lidos do sensor, entre outros, fornecendo informações sobre todas as etapas necessárias para que o dispositivo realize sua função e com isso o front-end possa dar feedbacks a respeito do estado do sensor.

A Action responsável por receber tais eventos é a classe br.com.pontoclass.iot.websocket.HardwareAction, que tem sua declaração na Listagem 8.

Sua função é a de agrupar os comportamentos relacionados ao hardware, realizando o direcionamento da requisição ao comando correto de acordo com a mensagem recebida.

Tais comandos estão separados em objetos do tipo Command, associados no bloco estático da classe, como podemos conferir no código das linhas 15 a 21 (ainda na Listagem 8). Neste bloco são associadas todas as ações já vistas no enum Stategy e mais a ação “HWLost”, que será utilizada para informar ao front-end que a conexão com o hardware foi perdida.

É claro que esta ação não poderia ser enviada pelo hardware, uma vez que com a perda da conexão isso se torna impossível. Por esse motivo, o método onClose() da classe HMeterServer (Listagem 5) simula o recebimento desta mensagem para que o comando responsável por informar ao front-end tal evento tenha a sua execução garantida.

Listagem 8. Declaração da classe HardwareAction.


 1.  package br.com.pontoclass.iot.websocket;
 2.  
 3.  import java.util.HashMap;
 4.  import java.util.List;
 5.  import java.util.Map;
 6.  import java.util.Optional;
 7.  import java.util.logging.Logger;
 8.  import javax.websocket.Session;
 9.  
 10.  public class HardwareAction implements Action {
 11.  
 12.    private static final Logger LOGGER = Logger.getLogger(HardwareAction.class.getName());
 13.    private static Map<String, Command> commands =  new HashMap<>();
 14.    
 15.    static {
 16.                commands.put("Calibrate", new HWCalibrateCommand());
 17.                commands.put("BeginCalibrate", new HWBeginCalibrateCommand());
 18.                commands.put("KeepAlive", new HWKeepAliveCommand());
 19.                commands.put("HWLost", new HWLostCommand());
 20.                commands.put("HWAvailable", new HWAvailableCommand());
 21.    }
 22.    
 23.    @Override
 24.    public void handle(String[] protocol, Optional<Session> hWSession, List<Session> webSession) {
 25.                Command command = commands.get(protocol[1]);
 26.                if(command == null) {
 27.                           LOGGER.warning(String.format("Um comando desconhecido foi solicitado pelo Hardware: [%s]", protocol[1]));
 28.                } else {
 29.                           command.execute(protocol, hWSession, webSession);
 30.                }
 31.    }
32.  }

No código da interface Action, a declaração do parâmetro hwSession é feita através da utilização do tipo java.util.Optional<Session>, a fim de evitar erros com ponteiros nulos (NullPointerException), já que o código chamador do método handle() pode passar o valor Optional.empty(), enquanto a implementação do método pode utilizar o método hwSession.ifPresent() para verificar a existência de valor na variável Optional.

Esta prática não somente evita erros com ponteiros, mas também prevê a possibilidade de que o front-end se conecte ao WebSocket antes do hardware, o que implica, nesse caso, em um parâmetro vazio de sessão com o hardware. Nas Listagens 9 a 14, a interface br.com.pontoclass.iot.websocket.Command e todas as suas implementações utilizadas na classe HardwareAction podem ser conferidas.

Listagem 9. Declaração da interface Command.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  import javax.websocket.Session;
6. 
7.  public interface Command {
8.
9.       public void execute(String[] protocol,
10.                                   Optional<Session> hWSession,
11.                                   List<Session> webSessions);
12. }

Listagem 10. Declaração da classe HWCalibrateCommand.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  import java.util.logging.Logger;
6.  import javax.websocket.Session;
7. 
8.  public class HWCalibrateCommand implements Command {
9. 
10.     @Override
11.     public void execute(String[] protocol,
12.                                  Optional<Session> hWSession,
13.                                  List<Session> webSessions) {
14.                 webSessions.stream()
15.                                .forEach(session -> {
16.                                        try {
17.                                                    session.getBasicRemote()
18.                                                                .sendText(String.format("Calibrate:%s", protocol[2]));
19.                                        } catch (Exception e) {
20.                                                    Logger.getLogger(this.getClass().getName())
21.                                                          .warning(String.format("Something went wrong by trying to answer session [%s]: [%s]", session.getId(), e.getMessage()));
22.                                                       }
23.                                        });
24.     }
25. }

Podemos observar que todas as classes são implementações da interface Command e funcionam de forma muito semelhante. A classe HWCalibrateCommand, por exemplo, utiliza a lista webSessions para percorrer todas as sessões web ativas com o objetivo de enviar uma mensagem aos clientes informando sobre o evento Calibrate, com o seu valor – de calibragem – parametrizado, como podemos ver nas linhas 17 e 18 da Listagem 10.

Já a classe HWKeepAliveCommand, apresentada na Listagem 12, utiliza o mesmo método de envio de mensagem às sessões web para informar sobre o evento KeepAlive, que indica a altura do corpo que acabou de se posicionar abaixo do sensor. Desta forma, entendemos que, para cada evento recebido do hardware, um multicast do mesmo evento é direcionado a todos os clientes web.

Neste experimento foram mapeados os seguintes eventos de Hardware:

· Calibrate: informa a altura máxima que pode ser medida pelo sensor, de acordo com a calibragem realizada;

· BeginCalibrate: indica o início da calibragem do hardware, dando a oportunidade aos clientes web de atualizarem a página informando o acontecimento;

· KeepAlive: indica o último valor da altura do corpo que acabou de passar pelo sensor;

· HWLost: Informa aos clientes web sobre a indisponibilidade do hardware (este é o único evento provocado pelo próprio serviço WebSocket e simula o evento como se fosse do próprio hardware);

· HWAvailable: informa aos clientes web que o hardware está disponível para ser utilizado e pronto para receber o primeiro pedido de calibragem.

Listagem 11. Declaração da classe HWBeginCalibrateCommand.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  import java.util.logging.Logger;
6.  import javax.websocket.Session;
7. 
8.  public class HWBeginCalibrateCommand implements Command {
9. 
10.     @Override
11.     public void execute(String[] protocol,
12.                                  Optional<Session> hWSession,
13.                                  List<Session> webSessions) {
14.                 webSessions.stream()
15.                    .forEach(session -> {
16.                               try {
17.                                           session.getBasicRemote().sendText("BeginCalibrate");
18.                               } catch (Exception e) {
19.                                                    Logger.getLogger(this.getClass().getName())
20.                                                                  .warning(String.format("Something went wrong by trying to answer session [%s]: [%s]", session.getId(), e.getMessage()));
21.                               }
22.                    });
23.     }
24. }

Listagem 12. Declaração da classe HWKeepAliveCommand.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  import java.util.logging.Logger;
6.  import javax.websocket.Session;
7. 
8.  public class HWKeepAliveCommand implements Command {
9. 
10.     @Override
11.     public void execute(String[] protocol,
12.                                  Optional<Session> hWSession,
13.                                  List<Session> webSessions) {
14.                 webSessions.stream()
15.                    .forEach(session -> {
16.                                try {
17.                                           session.getBasicRemote()
18.                                                       .sendText(String.format("KeepAlive:%s", protocol[2]));
19.                               } catch (Exception e) {
20.                                                    Logger.getLogger(this.getClass().getName())
21.                                                                  .warning(String.format("Something went wrong by trying to answer session [%s]: [%s]", session.getId(), e.getMessage()));
22.                               }
23.                    });
23.     }
25. }

Listagem 13. Declaração da classe HWAvailableCommand.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  import java.util.logging.Logger;
6.  import javax.websocket.Session;
7. 
8.  public class HWAvailableCommand implements Command {
9. 
10.     @Override
11.     public void execute(String[] protocol,
12.                                  Optional<Session> hWSession,
13.                                  List<Session> webSessions) {
14.                 webSessions.stream()
15.                                .forEach(session -> {
16.                                        try {
17.                                                    session.getBasicRemote().sendText("HWAvailable");
18.                                        } catch (Exception e) {
19.                                                    Logger.getLogger(this.getClass().getName())
20.                                                          .warning(String.format("Something went wrong by trying to answer session [%s]: [%s]", session.getId(), e.getMessage()));
21.                                        }
22.                             });
23.     }
24. }

Listagem 14. Declaração da classe HWLostCommand.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  import java.util.logging.Logger;
6.  import javax.websocket.Session;
7. 
8.  public class HWLostCommand implements Command {
9. 
10.     @Override
11.     public void execute(String[] protocol,
12.                                   Optional<Session> hWSession,
13.                                  List<Session> webSessions) {
14.                 webSessions.stream()
15.                                            .forEach(session -> {
16.                                                       try {
17.                                                                   session.getBasicRemote().sendText("HWLost");
18.                                                       } catch (Exception e) {
19.                                                                Logger.getLogger(this.getClass().getName())
20.                                                                      .warning(String.format("Something went wrong by trying to answer session [%s]: [%s]", session.getId(), e.getMessage()));
21.                                                       }
22.                                            });
23.     }
24. }

Visualizando os dados do sensor

Uma vez que a finalidade do módulo web em nosso experimento é fornecer a medida da altura de alguém, precisamos de uma página para visualizar os resultados da leitura do dispositivo. Até agora, no entanto, abordamos apenas a camada back-end do WebSocket e sua comunicação com o hardware.

Deste modo, criaremos uma camada front-end que tratará a comunicação com o socket tanto para enviar quanto para receber informações relacionadas ao sensor ultrassônico, permitindo assim a exibição das mudanças de estado detectadas.

Neste exemplo, para controlar a camada de visualização e sua interação com o back-end, utilizaremos o AngularJS. Na Listagem 15 podemos observar o código HTML com a página que irá exibir a altura do visitante que se posicionar abaixo do sensor.

Listagem 15. Código da página HTML (JSP) para exibição das informações.


1.  <%@ page  contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  %>
2.  <!DOCTYPE html>
3.  <html ng-app="iot">
4.      <head>
5.          <title page-title></title>
6.          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7.          <meta name="viewport" content="width=device-width, initial-scale=1.0">
8.          
9.          <link rel="stylesheet" href="css/style.css" />
10.         
11.         <!-- bower:js -->
12.         <script src="bower_components/jquery/dist/jquery.js"></script>
13.         <script src="bower_components/angular/angular.js"></script>
14.         <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
15.         <!-- endbower -->
16.         
17.         <script src="js/script.js"></script>
18.         <script src="js/config.js"></script>
19.         <script src="js/services.js"></script>
20.         <script src="js/controllers.js"></script>
21.     </head>
22.     
23.     <body  ng-controller="HMeterController">
24.                 <!--[if lt IE 7]>
25.                 <p class="browsehappy">Você está utilizando um navegador <b>desatualizado</b>. Por favor, <a href="http://browsehappy.com/">actualize o seu navegaor</a> para uma melhor experiência de usuário.</p>
26.     <![endif]-->
27.     
28.     <!-- Wrapper-->
29.     
30.     <div ui-view="main"></div>
31.     
32.     
33.     <!-- End wrapper-->
34.     </body>
35. </html>

Essa página é declarada em um JSP no mesmo webapp em que o WebSocket é criado. Nela, os scripts importados mais relevantes a serem observados são services.js e controllers.js, que podem ser conferidos nas Listagens 16 e 17.

Nestas listagens estamos declarando, respectivamente, o serviço $websocket responsável pela comunicação com o WebSocket da aplicação web e o controlador HMeterController, utilizado para realizar o controle daquilo que é exibido ao usuário na página de visualização do estado do dispositivo, em função dos comandos recebidos pelo serviço $websocket.

Listagem 16. Declaração do serviço AngularJS $websocket.


1.  'use strict';
2. 
3.  angular.module('iot')
4.    .service('$websocket', [function(){
5.      return {
6.        hmeter: null,
7.        callbacks: null,
8.        calibration: new Number("0.0"),
9.        restartCalibration: function() {
10.        this.calibration = new Number("0.0");
11.       },
12.       init: function(callbacks) {
13.        this.callbacks = callbacks;
14.        this.hmeter = new WebSocket("ws://localhost:8080/websocket/hmeter");
15.        this.hmeter.onopen = this.onOpen;
16.        this.hmeter.onclose = this.onClose;
17.        this.hmeter.onmessage = this.onMessage;
18.        this.hmeter.socketHandler = this;
19.        var _this = this;
20.        angular.element(document).bind("HSHC", function(evt) {
21.           _this.hmeter.send("CLIENT:WEB");
22.        });
23.        angular.element(document).bind("BGNC", function(evt) {
24.          _this.sendCalibrateMessage();
25.        });
26.       },
27.       beginCalibrate: function() {
28.         this.callbacks.beginCalibrate();
29.       },
30.       calibrate: function(height) {
31.         this.callbacks.calibrate(height);
32.         this.calibration = new Number(height.replace(",", "."));
33.       },
34.       keepAlive: function(height) {
35.        if(new Number(height.replace(",", ".")) < this.calibration - 0.03) {
36.        this.callbacks.keepAlive((this.calibration - new Number(height.replace(",", "."))).toFixed(2));
37.       } else {
38.        this.callbacks.waitForVisitors();
39.       }
40.      },
41.      hwLost: function(){
42.        this.callbacks.hwLost();
43.      },
44.      connectionClosed: function() {
45.        this.callbacks.connectionClosed();
46.      },
47.      sendCalibrateMessage: function() {
48.        this.hmeter.send("WEB:Calibrate");
49.      },
50.      onOpen: function() {
51.        console.log("conexão aberta...");
52.        angular.element(document).trigger('HSHC');
53.      },
54.      onClose: function() {
55.       console.log("conexão encerrada...");
56.       this.socketHandler.connectionClosed();
57.      },
58.      onMessage: function(evt) {
59.        console.log("mensagem recebida: " + evt.data);
60.        var protocol = evt.data.split(":");
71.        if("BeginCalibrate" == protocol[0]) {
72.          this.socketHandler.beginCalibrate();
73.        } else if("Calibrate" == protocol[0]) {
74.           this.socketHandler.calibrate(protocol[1]);
75.        } else if("KeepAlive" == protocol[0]) {
76.             if(this.socketHandler.calibration == null ||
77.               this.socketHandler.calibration == 0) {
78.                 angular.element(document).trigger('BGNC');
79.             } else {
80.                this.socketHandler.keepAlive(protocol[1]);
81.             }
82.         } else if("HWLost" == protocol[0]) {
83.             this.socketHandler.hwLost();
84.         } else if("HWAvailable" == protocol[0]) {
85.             this.socketHandler.sendCalibrateMessage();
86.         }
87.        }
88.     };  
89.    }]);

O serviço $websocket encapsula toda a complexidade de comunicação do WebSocket, implementando o protocolo simples criado neste artigo em sua função onMessage(), enquanto o controlador HMeterController realiza o controle da camada de visualização de acordo com os comandos recebidos através do serviço, de acordo com a mudança de estado do sensor.

Para cada alteração neste estado, o servidor WebSocket envia uma mensagem ao serviço AngularJS $websocket, atualizando o seu estado no front-end. Repare que, na declaração do $websocket, temos métodos como beginCalibrate(), calibrate(), keepAlive(), entre outros, sendo declarados.

Esses métodos encapsulam as funções reais através da variável callback, fornecida ao serviço pelo controlador na linha 54 da Listagem 17. Com isso, todas as mudanças de estado na página serão regidas pelas chamadas de funções declaradas nessa variável callback e acionadas pelo $websocket através do mecanismo de recebimento de mensagens.

Na Figura 1 temos o estado inicial da visualização do medidor de altura no navegador.

Listagem 17. Declaração do controlador AngularJS HMeterController.


1. 'use strict';
2.
3. angular.module('iot')
4.          .controller('HMeterController', ['$scope',
5.                                                            '$state',
6.                                                            '$websocket',
7.                                                            function($scope, $state, $websocket) {
8.                   $scope.sendCalibrate = function (){
9.                               $websocket.restartCalibration();
10.                 };
11.                     var callback = {
12.                 beginCalibrate: function() {
13.                             $scope.title = "Calibrando...";
14.                             $scope.message = "Aguarde...";
15.                             $scope.cssColor = "yellow";
16.                             $scope.$apply();
17.                 },
18.                 calibrate: function(height) {
19.                             $scope.title = "Equipamento calibrado!";
20.                             $scope.message = "Altura Máxima: " + height;
21.                             $scope.cssColor = "blue";
22.                             $scope.$apply();
23.                 },
24.                 keepAlive: function(height) {
25.                             $scope.title = "Olá visitante!";
26.                             $scope.message = "Sua altura: " + height;
27.                             $scope.cssColor = "green";
28.                             $scope.$apply();
29.                 },
30.                 hwLost: function(){
31.                             $scope.title = "Impossível medir a altura";
32.                             $scope.message = "Fora de Serviço";
33.                             $scope.cssColor = "red";
34.                             $scope.$apply();
35.                 },
36.                 connectionClosed: function() {
37.                             $scope.title = "Impossível medir a altura!";
38.                             $scope.message = "Fora de Serviço";
39.                             $scope.cssColor = "red";
40.                             $scope.$apply();
41.                 },
42.                 waitForVisitors: function() {
43.                             $scope.title = "Aguardando por visitantes!";
44.                             $scope.message = "Aguardando...";
45.                             $scope.cssColor = "gray";
46.                             $scope.$apply();
47.                 }
48.                            };
49.                     angular.element(document).ready(function() {
50.                            $scope.title = "Procurando o serviço...";
51.                            $scope.message = "Aguarde...";
52.                            $scope.cssColor = "yellow";
53.                            try {
54.                                        $websocket.init(callback);
55.                            } catch(e) {
56.                                        console.log(e);
57.                                        $scope.title = e.message;
58.                                        $scope.message = "#ERRO";
59.                                        $scope.cssColor = "red";
60.                            }
61.                            $scope.$apply();
62.                 });
63.     }]);

Visualização inicial do medidor de altura
Figura 1. Visualização inicial do medidor de altura.

Enquanto o sensor estiver desconectado do WebSocket, a camada de visualização não sofrerá nenhuma alteração. No entanto, uma vez que a aplicação com o Sketch seja executada e consiga se comunicar com o WebSocket, uma mensagem é enviada do hardware para o front-end informando que o dispositivo está disponível (“HWAvailable”).

O front-end, então, envia uma mensagem ao WebSocket solicitando a calibragem do dispositivo. Deste modo o diálogo é estabelecido, de forma que em todas as interações a camada de visualização seja atualizada para informar aos usuários sobre o que está acontecendo.

A atualização seguinte no front-end (vide Figura 2) informa ao usuário que o dispositivo está sendo calibrado, e depois da calibragem, é enviada a mensagem com a altura máxima que o dispositivo consegue informar (vide Figura 3).

Visualização de dispositivo calibrando
Figura 2. Visualização de dispositivo calibrando.
Visualização de altura máxima lida pelo
dispositivo
Figura 3. Visualização de altura máxima lida pelo dispositivo.

Após a calibragem do dispositivo, o evento KeepAlive entra em loop até que alguma outra ação seja solicitada. Com isso, a aplicação no navegador fica em modo de espera, aguardando o posicionamento de alguém abaixo do dispositivo (vide Figura 4) e após a passagem do corpo pelo sensor, a tela exibida informa a altura do indivíduo (vide Figura 5).

Visualização de dispositivo aguardando por
visitantes
Figura 4. Visualização de dispositivo aguardando por visitantes.
Visualização da altura do visitante
Figura 5. Visualização da altura do visitante.

Ainda na camada de visualização, o usuário tem a opção de clicar na mensagem no canto inferior direito para solicitar que o sensor inicie novamente o processo de calibração, útil quando o sensor se perde por algum motivo.

Nesta ação ocorre o envio de uma mensagem do cliente WebSocket do navegador ao servidor. Assim, através da chamada à função sendCalibrateMessage() do serviço Angular $websocket, a mensagem “WEB:Calibrate” é enviada.

De volta ao servidor, na classe ActionFactory vista anteriormente na Listagem 7, podemos verificar que ela também associa uma Action às mensagens do tipo web por meio da classe br.com.pontoclass.iot.websocket.WebAction, apresentada na Listagem 18.

Essa classe possui apenas um comando: WebCalibrateCommand, exposto na Listagem 19, responsável por fazer o caminho inverso da comunicação, enviando ao hardware o pedido de Calibração. Isto inicia novamente todo o fluxo de mensagens, resultando na nova calibragem do dispositivo.

Ademais, esse processo também permite que a visualização do estado do dispositivo possa ser atualizada no navegador.

Listagem 18. Código da classe WebAction.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.HashMap;
4.  import java.util.List;
5.  import java.util.Map;
6.  import java.util.Optional;
7.  import java.util.logging.Logger;
8.
9.  import javax.websocket.Session;
10.
11. public class WebAction implements Action {
12.
13.     private static final Logger LOGGER = Logger.getLogger(WebAction.class.getName());
14.     private static Map<String, Command> commands =  new HashMap<>();
15.     
16.     static {
17.                 commands.put("Calibrate", new WebCalibrateCommand());
18.     }
19.     
20.     @Override
21.     public void handle(String[] protocol,
22.                                 Optional<Session> hWSession,
23.                                 List<Session> webSession) {
24.                 Command command = commands.get(protocol[1]);
25.                 if(command == null) {
26.                            LOGGER.warning(String.format("Unknown command was asked by" +
27.                                                                           " the web client: [%s]", protocol[1]));
28.                 } else {
29.                            command.execute(protocol, hWSession, webSession);
30.                 }
31.     }
32. }

Listagem 19. Declaração da classe WebCalibrateCommand.


1.  package br.com.pontoclass.iot.websocket;
2. 
3.  import java.util.List;
4.  import java.util.Optional;
5.  import java.util.logging.Logger;
6. 
7.  import javax.websocket.Session;
8. 
9.  public class WebCalibrateCommand implements Command {
10. 
11.     @Override
12.     public void execute(String[] protocol,
13.                                  Optional<Session> hWSession,
14.                                  List<Session> webSession) {
15.                 hWSession.ifPresent(session -> {
16.                            try {
17.                                        session.getBasicRemote().sendText("Calibrate");
18.                            } catch (Exception e) {
19.                                        Logger.getLogger(this.getClass().getName())
20.                                              .warning("Something went wrong by trying " + 
21.                                                                "to send the Calibrate message to the Hardware...");
22.                            }
23.                 });
24.     }
                                                                                                                      
25. }

Carregando o projeto no Raspberry Pi

Após todo o processo de desenvolvimento e testes em um computador comum, é hora de instalar o servidor WebSocket como uma aplicação web no Raspberry Pi e também o Sketch JArduino, como um programa que executará ininterruptamente por meio da execução da classe Bootstrap, vista na Listagem 3. Para tanto, é necessário que seja instalada a versão do Java Embedded Suite no dispositivo, uma vez que esta versão do Java é a apropriada para a arquitetura do Raspberry Pi.

Se o Sistema Operacional instalado no dispositivo for uma distribuição Linux, é muito provável que a instalação do JDK possa ser feita de forma simples, através da execução do comando sudo apt-get install oracle-java8-jdk. Em seguida, todo o processo de instalação do container web pode ser feito como se fosse em uma distribuição Linux de um computador qualquer.

É possível também realizar o download do pacote JDK apropriado diretamente no site da Oracle e acompanhar os passos de instalação quando o Sistema Operacional não possuir gerenciadores de pacotes ou quando os mesmos estiverem desatualizados.

Após a instalação do JDK e do container Web, basta seguir o processo de instalação do WebSocket (geralmente a instalação é tão simples quanto jogar o pacote “.war” da aplicação na pasta webapp do servidor) e iniciar o programa .jar controlador do Arduino (normalmente a execução se dá através do comando java –jar nomeDoPacote.jar). Este último procedimento necessita da conexão direta entre o Arduino e o Raspberry Pi para que não haja falha na sua inicialização.

Tal conexão é realizada via porta USB e a alimentação elétrica desta porta já é suficiente para a execução do Arduino. Já o Raspberry Pi precisará de uma fonte externa de eletricidade de pelo menos 2A (dois amperes) na versão mais nova do dispositivo (Raspberry Pi 2 Model B, até a data de escrita deste artigo).

Essa fonte elétrica pode ser adquirida facilmente (provavelmente no mesmo local de venda aonde o Raspberry Pi tenha sido comprado) e ser ligada ao dispositivo através de sua entrada universal.

Como podemos verificar, a utilização de Java para controle de componentes eletrônicos já é uma realidade e está madura o suficiente para que qualquer desenvolvedor bem-aventurado a explore.

Esta possibilidade abre espaços para que o programador Java possa mergulhar no universo da Internet das Coisas (IoT) sem a necessidade de aprender novas linguagens ou eletrônica avançada; bastam alguns sensores em mãos, um dispositivo controlador, Java e um pouco de criatividade para que o leitor possa se tornar um inventor de novos dispositivos utilitários que mais tarde possam até se tornar produtos inovadores.

Com os conhecimentos apresentados no artigo, o aprofundamento nos detalhes não abordados se tornará mais fácil e intuitivo. O leitor poderá buscar a compreensão de quais são os próximos passos para transformar um protótipo em um produto acabado e terminar descobrindo-se como criador de uma nova startup que pode nascer a partir de uma ideia com testes em protótipos; mas para seguir em frente é preciso profissionalizar e otimizar não somente o processo, como também o próprio modelo (dispositivo).

Muitas destas prototipações, inclusive como a realizada neste artigo, podem ser feitas sem a necessidade de Arduino e Sketches, pois é possível comunicar-se diretamente com a GPIO do próprio Raspberry Pi configurando outro modelo de comunicação e controle de sensores via Java de forma mais simplificada, quando a solução não requerer a utilização de muitos pinos.

O emprego do Arduino neste artigo serviu para mostrar que mesmo placas micro controladas através de códigos nativos, que não utilizam Java em seu Firmware, podem ser facilmente integradas a soluções desta linguagem através de bibliotecas intermediárias, como o JArduino.

A aplicação do JArduino nos projetos de Internet das Coisas facilita bastante a descoberta deste novo nicho para os programadores Java, mas pode se tornar dispensável com o tempo, quando o desenvolvedor perceber que mesmo as aplicações intermediárias, escritas em outras linguagens, podem ser facilmente escritas para se integrar através de protocolos criados pelo próprio desenvolvedor para a troca de dados entre dispositivos.

Links Úteis

  • Vagas de programação: Precisamos falar sobre isso:
    Neste DevCast vamos bater um papo sobre a vagas para trabalhar como programador e como os candidatos devem se preparar para enfrentar os processos seletivos.
  • Android InputMask e Saripaar 2:
    Aprenda a inserir máscaras nos campos do formulário com a biblioteca InputMask e validação através da Android Saripaar 2.
  • O que é Grunt?:
    Aprenda neste curso o que é o Grunt e como utilizá-lo em seus projetos para automatizar tarefas comuns do desenvolvimento front-end.

Saiba mais sobre Java ;)

  • Guias Java:
    Encontre aqui os Guias de estudo que vão ajudar você a aprofundar seu conhecimento na linguagem Java. Desde o básico ao mais avançado. Escolha o seu!
  • Formulário de cadastro com JSF e Bootstrap:
    Aprenda neste exemplo como criar interfaces ricas com Bootstrap e JSF. Saiba como o Pass-through elements pode te ajudar a ter mais controle sobre o HTML gerado pelos componentes nativos.
  • WebLogic Multitenant:
    Consolidando suas aplicações Java EE com controle e isolamento na mesma JVM.

Refrências:

Designing the Internet of Things (Livro).
http://www.wiley.com/WileyCDA/WileyTitle/productCd-111843062X.html

Explained: The ABCs of the Internet of Things.
http://www.computerworld.com/article/2488872/emerging-technology-explained-the-abcs-of-the-internet-of-things.html

JSR 356, Java API for WebSocket.
http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html

Micro-Electro-MechanicalSystems.
http://www.tecmundo.com.br/nanotecnologia/3254-o-que-sao-mems-.htm

Como funcionam os sensores ultrassônicos (ART691).
http://www.newtoncbraga.com.br/index.php/como-funciona/5273-art691

IoT: Do you really should have knowledge in eletronics?
http://blogs.msdn.com/b/cdndevs/archive/2015/04/17/iot-do-you-really-should-have-knowledge-in-electronics.aspx

Eletrônica Didática - Protoboard.
http://www.eletronicadidatica.com.br/protoboard.html

Designing the Internet of Things (Livro).
http://www.wiley.com/WileyCDA/WileyTitle/productCd-111843062X.html

Explained: The ABCs of the Internet of Things.
http://www.computerworld.com/article/2488872/
emerging-technology-explained-the-abcs-of-the-internet-of-things.html

JSR 356, Java API for WebSocket.
http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html

Micro-Electro-MechanicalSystems.
http://www.tecmundo.com.br/nanotecnologia/3254-o-que-sao-mems-.htm

Como funcionam os sensores ultrassônicos (ART691).
http://www.newtoncbraga.com.br/index.php/como-funciona/5273-art691

GitHub – Jarduino.
https://github.com/romerorsp/JArduino

Eletrônica Didática - Protoboard.
http://www.eletronicadidatica.com.br/protoboard.html