Os Ampelmännchen são um símbolo popular da Alemanha Oriental, exibidos nos semáforos de pedestres em todas as esquinas. Existe até uma rede de varejo com sede em Berlim que se inspira em seu design.
Encontrei um conjunto de sinais Ampelmann de segunda mão em um mercado de pulgas e queria controlá-los pelo meu telefone. Se você gostaria de fazer o mesmo, continue lendo!
Aviso de saúde: Este projeto usa energia elétrica de 220V. Eu não sou eletricista. Siga estas instruções por sua própria conta e risco.
Eu queria construir algo com uma estética berlinense e que fosse divertido para os visitantes da minha casa interagirem. Infelizmente o nosso número de visitantes diminuiu drasticamente este ano, mas ainda esperamos uma grande reação em 2021...?
Cada dispositivo na sua rede local possui seu próprio endereço IP privado (por exemplo, 192.168.1.20
). Alguns roteadores, como o FritzBox, também permitem navegar por nomes de host locais, de modo que 192.168.1.20
também esteja acessível em mydevice.fritz.box
. Para o semáforo, o nome do host do dispositivo é traffic-light
, então podemos visitá-lo em http://traffic-light.fritz.box
.
O webapp é um aplicativo responsivo de página única muito simples. Isso mostra:
O código está no diretório /webapp. Nenhuma dependência externa é necessária, pois depende apenas de recursos padrão do navegador, como transformação CSS, WebSockets e XHR. Você pode visualizar o aplicativo em execução aqui, embora ele não controle nada, já que é claro que você não está na minha LAN. Se você o visitasse da rede local, por exemplo, http://traffic-light.fritz.box
ele funcionaria perfeitamente.
Ao carregar a página, o aplicativo faz uma solicitação GET para encontrar o status atual em /api/status
e, em seguida, abre uma conexão WebSocket com o servidor na porta 81
. As atualizações de status subsequentes sempre virão por meio do WebSocket, para manter vários clientes sincronizados. Cada vez que chega um evento websocket, aplicamos as alterações a um único objeto state
global. Pouco depois, o método updateScreen()
aplica essas alterações ao DOM.
Na inicialização, também detectamos se o usuário está em um dispositivo móvel ou desktop, para lidar com eventos de toque ou de clique. Na verdade, usamos o evento touchend
para enviar comandos ao servidor, porque isso funcionava de maneira mais confiável no iPhone X. Deslizar de baixo para cima na tela para sair do Safari estava disparando o evento touchstart
, tornando impossível sair do aplicativo sem ligar. a luz verde!
Finalmente, queremos reduzir a carga no servidor sempre que possível. Lembre-se de que o ESP8266 está rodando em um processador de 80 MHz com apenas cerca de 50kB de RAM. NÃO é um dispositivo robusto. Portanto, quando o navegador estiver inativo, desconectamos o websocket. Quando a guia ou navegador reabrir, verificamos novamente o status e reconectamos o WebSocket.
O ESP8266 está ocupado lidando com solicitações de API e código de tempo, portanto não possui os recursos necessários para atender o próprio webapp. Além disso, é difícil fazer alterações cosméticas no webapp se eu precisar me conectar fisicamente ao hardware sempre que quiser aplicar uma atualização.
O index.html do webapp segue o princípio do aplicativo de página única de que tudo deve ser renderizado por Javascript, tornando o conteúdo HTML em si muito pequeno. Tipo 550 bytes pequenos. Todo o resto é carregado pelo navegador do cliente, sem a necessidade de fazer novas chamadas ao servidor. Portanto, o webapp é totalmente hospedado no GitHub Pages, uma ferramenta gratuita de hospedagem de sites estáticos. Clicar em /index.html
na verdade faz uma solicitação de proxy para as páginas do GitHub e retorna o resultado para o navegador do cliente.
Agora podemos alterar qualquer coisa no webapp e o servidor não será afetado. Ótimo! Bom, quase...
A maior parte do código deste webapp está nos arquivos CSS e JS, não no próprio index.html
. Os navegadores armazenam em cache todos os arquivos carregados por um período indeterminado antes de solicitá-los novamente. Se index.html não mudar, mas implantarmos uma nova versão JS, como nossos clientes saberão que precisam carregar a nova versão JS?
Quando enviamos qualquer nova versão do nosso código para o branch git master
, uma ação do GitHub é executada, que executa a implantação nas páginas do GitHub onde a página é realmente veiculada ao público. O truque aqui é anexar o sufixo ?version=latest
ao final de nossos próprios arquivos CSS e JS, no index.html
. Antes de copiar o conteúdo para o branch gh-pages
, a ação usa o comando sed
para substituir esse " latest
" pelo valor da variável $GITHUB_SHA
, que é na verdade o último ID de commit no branch master
. (por exemplo, um valor como b43200422c4f5da6dd70676456737e5af46cb825
).
Então, na próxima vez que um cliente visitar o webapp, o navegador verá um valor novo e diferente após ?version=
e solicitará o novo arquivo JS ou CSS atualizado, que ainda não terá armazenado em cache.
Consulte o método setup(void)
em traffic-light-controller.ino
e a seção Código Arduino para saber como isso funciona na prática.
Decidi usar REST e WebSockets em conjunto. REST é usado principalmente por clientes para controlar o servidor. WebSockets são usados para transmitir informações de status aos clientes. Existem muitas ferramentas como o Postman que permitem experimentar facilmente APIs REST, então achei isso mais conveniente.
API HTTP: Consulte a documentação do Swagger aqui.
API WebSocket: A conexão websocket envia blobs JSON que o webapp usa para atualizar seu estado interno. Um evento websocket pode conter um ou mais campos para atualização. Um exemplo contendo informações ambientais pode ser assim:
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
Nenhum dado é enviado do cliente para o servidor por meio do websocket, embora isso seja possível.
O código do Arduino está todo em um único arquivo que inclui comentários explicativos.
Ele começa com um conjunto de definições para locais de pinos, importações de bibliotecas e valores codificados para coisas como tipos de conteúdo HTTP e valores de código de resposta. A seguir está um conjunto de variáveis que podem mudar em tempo de execução, todas prefixadas com sublinhados. Alguns objetos também são inicializados aqui, incluindo aqueles para o servidor web, servidor de soquete web, cliente WiFi e sensores de temperatura. O "relógio do sistema" é mantido pelo campo _currentMillis
.
Após a inicialização, o método setup(void)
é executado. Depois de fazer algumas configurações de pinos, ele cria os mapeamentos necessários para os endpoints REST e inicia os servidores ouvindo as solicitações dos clientes. O método loop(void)
é responsável por todo o resto. Cada ciclo processa quaisquer solicitações pendentes da web, atualiza o ciclo de ritmo e lê os sensores, se necessário. Se estivermos no modo festa, ele definirá o estado atual de flash/pulso.
O ritmo (para o modo festa) é codificado para reproduzir a sequência no campo RHYTHM_PATTERN
, mas em teoria poderia ser alterado em tempo de execução para qualquer outra coisa. Cada vez que chamamos o método rhythm()
, usamos os valores atuais _bpm
e _currentMillis
para descobrir em que posição devemos estar no padrão. Isso é armazenado no campo _rhythmStep
.
Durante o padrão rítmico, há períodos em que ambos os relés estão realmente desligados. Mas como as luzes são lâmpadas incandescentes, elas não iniciam ou param de emitir luz instantaneamente. Parece que as lâmpadas demoram cerca de 1,7 segundos para ligar ou desligar totalmente. Portanto, ao adicionar um período ao padrão em que ambos estão desligados, obtemos um padrão de pulsação suave à medida que as lâmpadas aquecem e esfriam.
No método partyFlash()
, obtemos o item padrão que deve ser exibido no momento (ou ambos devem ser desligados) e chamamos lightSwitch(...)
com os parâmetros apropriados. lightSwitch(...)
por sua vez chama sendToWebSocketClients(...)
para que todos os clientes conectados sejam atualizados para o novo estado.
Se o usuário simplesmente clicar em uma das luzes para ligá-la ou desligá-la, o processo será semelhante, mas tratado como uma solicitação REST. É chamado um dos métodos handleX
, que valida a solicitação e, por sua vez, chama lightSwitch(...)
.
Em intervalos menos frequentes, verificamos a temperatura dos dois gabinetes e também enviamos via WebSocket para todos os clientes. Atualmente, isso é usado apenas para fins informativos, mas pode ser usado para desativar as luzes quando a temperatura excede algum limite de segurança.
Agradecemos a @mrcosta por sua ajuda na revisão deste artigo.