Eu devo admitir que este artigo é apenas uma desculpa para brincar com Grafana e InfluxDb. O InfluxDB é um banco de dados legal especialmente projetado para trabalhar com séries temporais. Grafana é uma ferramenta open source para análise de séries temporais. Eu quero construir um protótipo simples; a ideia é:
- Um dispositivo Arduino (esp32) emite um evento MQTT para um servidor Mosquitto. Usarei um potenciômetro para emular um sensor (imagine aqui, por exemplo, um sensor de temperatura em vez de um potenciômetro). Eu já usei esse circuito antes em outros projetos.
- Um script Python estará escutando o evento MQTT no meu Raspberry Pi e persistirá o valor para o banco de dados InfluxDB.
- Vou monitorar o estado da série temporal dada pelo potenciômetro com Grafana.
- Vou criar um alerta no Grafana (por exemplo, quando o valor médio dentro de 10 segundos estiver acima de um limite) e disparar um webhook quando o alerta mudar seu estado.
- Um microsserviço (um servidor Python Flask) estará ouvindo o webhook e emitirá um evento MQTT dependendo do estado.
- Outro dispositivo do Arduino (um NodeMcu, nesse caso) estará ouvindo esse evento MQTT e ativará um LED – vermelho se o alerta estiver ligado, e verde se o alerta estiver desligado.
O servidor
Como eu disse antes, precisaremos de três servidores:
- Servidor MQTT (mosquitto)
- Servidor InfluxDB
- Servidor Grafana
Vamos usar o Docker. Eu tenho um host Docker rodando em um Raspberry Pi3. O Raspberry Pi é um dispositivo ARM, por isso precisamos de imagens docker para essa arquitetura.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<span style="color: #007700;"><pre</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"background:#000;color:#f8f8f8"</span><span style="color: #007700;">></span>version<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>'2'<span style="color: #007700;"></span></span> services<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> mosquitto<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> image<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> pascaldevink<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>rpi<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>mosquitto container_name<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> moquitto ports<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>"9001:9001"<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>"1883:1883"<span style="color: #007700;"></span></span> restart<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> always influxdb<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> image<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> hypriot<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>rpi<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>influxdb container_name<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> influxdb restart<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> always environment<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#3387cc"</span><span style="color: #007700;">></span>INFLUXDB_INIT_PWD<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>=<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>"password"<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#3387cc"</span><span style="color: #007700;">></span>PRE_CREATE_DB<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>=<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>"iot"<span style="color: #007700;"></span></span> ports<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>"8083:8083"<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>"8086:8086"<span style="color: #007700;"></span></span> volumes<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>~/<span style="color: #007700;"></span></span>docker<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>rpi<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>influxdb<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>data<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:/<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>data<span style="color: #007700;"></span></span> grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> image<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> fg2it<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>armhf<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span>v4<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>.<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#3387cc"</span><span style="color: #007700;">></span>6.3<span style="color: #007700;"></span></span> container_name<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> grafana restart<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> always ports<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#65b042"</span><span style="color: #007700;">></span>"3000:3000"<span style="color: #007700;"></span></span> volumes<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>db<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:/<span style="color: #007700;"></span></span>var<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>lib<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>grafana <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#dad085"</span><span style="color: #007700;">></span>log<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:/<span style="color: #007700;"></span></span>var<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#dad085"</span><span style="color: #007700;">></span>log<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>grafana <span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span> grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>conf<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:/<span style="color: #007700;"></span></span>etc<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>/<span style="color: #007700;"></span></span>grafana volumes<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>db<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> driver<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> local grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#dad085"</span><span style="color: #007700;">></span>log<span style="color: #007700;"></span><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> driver<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> local grafana<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>-<span style="color: #007700;"></span></span>conf<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> driver<span style="color: #007700;"><span</span> <span style="color: #0000cc;">style=</span><span style="background-color: #fff0f0;">"color:#e28964"</span><span style="color: #007700;">></span>:<span style="color: #007700;"></span></span> local <span style="color: #007700;"></pre></span> |
Esp32
A parte do Esp32 é muito simples. Nós só precisamos conectar nosso potenciômetro ao Esp32. O potenciômetro possui três pinos: Gnd, Signal e Vcc. Para sinal, vamos usar o pino 32.
Nós precisamos apenas configurar nossa rede Wi-Fi, conectar ao nosso servidor MQTT e emitir o valor do potenciômetro dentro de cada loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<span style="color: #557799;">#include <PubSubClient.h></span> <span style="color: #557799;">#include <WiFi.h></span> <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">int</span> potentiometerPin <span style="color: #333333;">=</span> <span style="color: #0000dd; font-weight: bold;">32</span>; <span style="color: #888888;">// Wifi configuration</span> <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> ssid <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"my_wifi_ssid"</span>; <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> password <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"my_wifi_password"</span>; <span style="color: #888888;">// MQTT configuration</span> <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> server <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"192.168.1.111"</span>; <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> topic <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"/pot"</span>; <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> clientName <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"com.gonzalo123.esp32"</span>; String payload; WiFiClient wifiClient; PubSubClient <span style="color: #0066bb; font-weight: bold;">client</span>(wifiClient); <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">wifiConnect</span>() { Serial.println(); Serial.print(<span style="background-color: #fff0f0;">"Connecting to "</span>); Serial.println(ssid); WiFi.begin(ssid, password); <span style="color: #008800; font-weight: bold;">while</span> (WiFi.status() <span style="color: #333333;">!=</span> WL_CONNECTED) { delay(<span style="color: #0000dd; font-weight: bold;">500</span>); Serial.print(<span style="background-color: #fff0f0;">"."</span>); } Serial.println(<span style="background-color: #fff0f0;">""</span>); Serial.print(<span style="background-color: #fff0f0;">"WiFi connected."</span>); Serial.print(<span style="background-color: #fff0f0;">"IP address: "</span>); Serial.println(WiFi.localIP()); } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">mqttReConnect</span>() { <span style="color: #008800; font-weight: bold;">while</span> (<span style="color: #333333;">!</span>client.connected()) { Serial.print(<span style="background-color: #fff0f0;">"Attempting MQTT connection..."</span>); <span style="color: #008800; font-weight: bold;">if</span> (client.connect(clientName)) { Serial.println(<span style="background-color: #fff0f0;">"connected"</span>); } <span style="color: #008800; font-weight: bold;">else</span> { Serial.print(<span style="background-color: #fff0f0;">"failed, rc="</span>); Serial.print(client.state()); Serial.println(<span style="background-color: #fff0f0;">" try again in 5 seconds"</span>); delay(<span style="color: #0000dd; font-weight: bold;">5000</span>); } } } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">mqttEmit</span>(String topic, String value) { client.publish((<span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span>) topic.c_str(), (<span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span>) value.c_str()); } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">setup</span>() { Serial.begin(<span style="color: #0000dd; font-weight: bold;">115200</span>); wifiConnect(); client.setServer(server, <span style="color: #0000dd; font-weight: bold;">1883</span>); delay(<span style="color: #0000dd; font-weight: bold;">1500</span>); } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">loop</span>() { <span style="color: #008800; font-weight: bold;">if</span> (<span style="color: #333333;">!</span>client.connected()) { mqttReConnect(); } <span style="color: #333399; font-weight: bold;">int</span> current <span style="color: #333333;">=</span> (<span style="color: #333399; font-weight: bold;">int</span>) ((analogRead(potentiometerPin) <span style="color: #333333;">*</span> <span style="color: #0000dd; font-weight: bold;">100</span>) <span style="color: #333333;">/</span> <span style="color: #0000dd; font-weight: bold;">4095</span>); mqttEmit(topic, (String) current); delay(<span style="color: #0000dd; font-weight: bold;">500</span>); } |
Ouvinte do MQTT
O Esp32 emite um evento (“/ pot”) com o valor do potenciômetro. Então, vamos criar um ouvinte do MQTT que ouça o MQTT e persista o valor para o InfluxDB.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<span style="color: #008800; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">paho.mqtt.client</span> <span style="color: #008800; font-weight: bold;">as</span> <span style="color: #0e84b5; font-weight: bold;">mqtt</span> <span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">influxdb</span> <span style="color: #008800; font-weight: bold;">import</span> InfluxDBClient <span style="color: #008800; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">datetime</span> <span style="color: #008800; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">logging</span> <span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">persists</span>(msg): current_time <span style="color: #333333;">=</span> datetime<span style="color: #333333;">.</span>datetime<span style="color: #333333;">.</span>utcnow()<span style="color: #333333;">.</span>isoformat() json_body <span style="color: #333333;">=</span> [ { <span style="background-color: #fff0f0;">"measurement"</span>: <span style="background-color: #fff0f0;">"pot"</span>, <span style="background-color: #fff0f0;">"tags"</span>: {}, <span style="background-color: #fff0f0;">"time"</span>: current_time, <span style="background-color: #fff0f0;">"fields"</span>: { <span style="background-color: #fff0f0;">"value"</span>: <span style="color: #007020;">int</span>(msg<span style="color: #333333;">.</span>payload) } } ] logging<span style="color: #333333;">.</span>info(json_body) influx_client<span style="color: #333333;">.</span>write_points(json_body) logging<span style="color: #333333;">.</span>basicConfig(level<span style="color: #333333;">=</span>logging<span style="color: #333333;">.</span>INFO) influx_client <span style="color: #333333;">=</span> InfluxDBClient(<span style="background-color: #fff0f0;">'docker'</span>, <span style="color: #0000dd; font-weight: bold;">8086</span>, database<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">'iot'</span>) client <span style="color: #333333;">=</span> mqtt<span style="color: #333333;">.</span>Client() client<span style="color: #333333;">.</span>on_connect <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">lambda</span> <span style="color: #007020;">self</span>, mosq, obj, rc: <span style="color: #007020;">self</span><span style="color: #333333;">.</span>subscribe(<span style="background-color: #fff0f0;">"/pot"</span>) client<span style="color: #333333;">.</span>on_message <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">lambda</span> client, userdata, msg: persists(msg) client<span style="color: #333333;">.</span>connect(<span style="background-color: #fff0f0;">"docker"</span>, <span style="color: #0000dd; font-weight: bold;">1883</span>, <span style="color: #0000dd; font-weight: bold;">60</span>) client<span style="color: #333333;">.</span>loop_forever() |
Grafana
No Grafana, precisamos fazer duas coisas. Primeiro, criar uma fonte de dados a partir do nosso servidor InfluxDB. É bem simples.
Por fim, criaremos um painel. Nós só temos uma série temporal com o valor do potenciômetro. Devo admitir que meu painel tem muitas coisas que eu criei apenas por diversão.
Esta é a consulta que estou usando para traçar o gráfico principal:
1 2 3 4 5 6 |
<span style="color: #008800; font-weight: bold;">SELECT</span> <span style="color: #008800; font-weight: bold;">last</span>(<span style="color: #aa6600;">"value"</span>) <span style="color: #008800; font-weight: bold;">FROM</span> <span style="color: #aa6600;">"pot"</span> <span style="color: #008800; font-weight: bold;">WHERE</span> time <span style="color: #333333;">>=</span> now() <span style="color: #333333;">-</span> <span style="color: #0000dd; font-weight: bold;">5</span>m <span style="color: #008800; font-weight: bold;">GROUP</span> <span style="color: #008800; font-weight: bold;">BY</span> time(<span style="color: #ff0000; background-color: #ffaaaa;">lt;/span><span style="color: #007020;">interval</span>) fill(previous) |
Aqui podemos ver o painel:
E aqui minha configuração de alerta:
Eu também criei um canal de notificação com um webhook. O Grafana utilizará esse webhook para notificar quando o estado do alerta mudar.
Ouvinte do Webhook
O Grafana emitirá um webhook, então precisaremos de um endpoint REST para coletar as chamadas do webhook. Eu normalmente uso PHP/Lumen para criar servidores REST, mas nesse projeto eu vou usar Python e Flask.
Precisamos manipular o HTTP Basic Auth e emitir um evento MQTT. O MQTT é um protocolo muito simples, mas tem um recurso muito interessante que se encaixa como uma luva aqui. Deixe-me explicar isso.
Imagine que temos o nosso sistema em funcionamento e o estado está “ok”. Agora nós conectamos um dispositivo (por exemplo, uma grande luz vermelha/verde). Como o evento “ok” foi acionado antes de conectarmos as luzes, nossa luz verde não será ligada. Precisamos aguardar o evento “alert” se quisermos ver alguma luz. Isso não é legal.
MQTT nos permite “reter” mensagens. Isso significa que podemos emitir mensagens com a flag “retain” para um tópico e, quando conectarmos um dispositivo posteriormente a esse tópico, ele receberá a mensagem. Aqui está exatamente do que precisamos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">flask</span> <span style="color: #008800; font-weight: bold;">import</span> Flask <span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">flask</span> <span style="color: #008800; font-weight: bold;">import</span> request <span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">flask_httpauth</span> <span style="color: #008800; font-weight: bold;">import</span> HTTPBasicAuth <span style="color: #008800; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">paho.mqtt.client</span> <span style="color: #008800; font-weight: bold;">as</span> <span style="color: #0e84b5; font-weight: bold;">mqtt</span> <span style="color: #008800; font-weight: bold;">import</span> <span style="color: #0e84b5; font-weight: bold;">json</span> client <span style="color: #333333;">=</span> mqtt<span style="color: #333333;">.</span>Client() app <span style="color: #333333;">=</span> Flask(__name__) auth <span style="color: #333333;">=</span> HTTPBasicAuth() <span style="color: #888888;"># http basic auth credentials</span> users <span style="color: #333333;">=</span> { <span style="background-color: #fff0f0;">"user"</span>: <span style="background-color: #fff0f0;">"password"</span> } <span style="color: #555555; font-weight: bold;">@auth.get_password</span> <span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">get_pw</span>(username): <span style="color: #008800; font-weight: bold;">if</span> username <span style="color: #000000; font-weight: bold;">in</span> users: <span style="color: #008800; font-weight: bold;">return</span> users<span style="color: #333333;">.</span>get(username) <span style="color: #008800; font-weight: bold;">return</span> <span style="color: #007020;">None</span> <span style="color: #555555; font-weight: bold;">@app.route</span>(<span style="background-color: #fff0f0;">'/alert'</span>, methods<span style="color: #333333;">=</span>[<span style="background-color: #fff0f0;">'POST'</span>]) <span style="color: #555555; font-weight: bold;">@auth.login_required</span> <span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">alert</span>(): client<span style="color: #333333;">.</span>connect(<span style="background-color: #fff0f0;">"docker"</span>, <span style="color: #0000dd; font-weight: bold;">1883</span>, <span style="color: #0000dd; font-weight: bold;">60</span>) data <span style="color: #333333;">=</span> json<span style="color: #333333;">.</span>loads(request<span style="color: #333333;">.</span>data<span style="color: #333333;">.</span>decode(<span style="background-color: #fff0f0;">'utf-8'</span>)) <span style="color: #008800; font-weight: bold;">if</span> data[<span style="background-color: #fff0f0;">'state'</span>] <span style="color: #333333;">==</span> <span style="background-color: #fff0f0;">'alerting'</span>: client<span style="color: #333333;">.</span>publish(topic<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"/alert"</span>, payload<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"1"</span>, retain<span style="color: #333333;">=</span><span style="color: #007020;">True</span>) <span style="color: #008800; font-weight: bold;">elif</span> data[<span style="background-color: #fff0f0;">'state'</span>] <span style="color: #333333;">==</span> <span style="background-color: #fff0f0;">'ok'</span>: client<span style="color: #333333;">.</span>publish(topic<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"/alert"</span>, payload<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"0"</span>, retain<span style="color: #333333;">=</span><span style="color: #007020;">True</span>) client<span style="color: #333333;">.</span>disconnect() <span style="color: #008800; font-weight: bold;">return</span> <span style="background-color: #fff0f0;">"ok"</span> <span style="color: #008800; font-weight: bold;">if</span> __name__ <span style="color: #333333;">==</span> <span style="background-color: #fff0f0;">"__main__"</span>: app<span style="color: #333333;">.</span>run(host<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">'0.0.0.0'</span>) |
NodeMcu
Finalmente, o Nodemcu. Essa parte é semelhante à do Esp32. Nossos leds estão nos pinos 4 e 5. Também precisamos configurar o Wi-Fi e conectar ao servidor MQTT. Nodemcu e Esp32 são dispositivos semelhantes, mas não são os mesmos. Por exemplo, precisamos usar bibliotecas diferentes para conectar ao Wi-Fi.
Esse dispositivo estará ouvindo o evento MQTT e acionará o led ou outro, dependendo do estado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
<span style="color: #557799;">#include <PubSubClient.h></span> <span style="color: #557799;">#include <ESP8266WiFi.h></span> <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">int</span> ledRed <span style="color: #333333;">=</span> <span style="color: #0000dd; font-weight: bold;">4</span>; <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">int</span> ledGreen <span style="color: #333333;">=</span> <span style="color: #0000dd; font-weight: bold;">5</span>; <span style="color: #888888;">// Wifi configuration</span> <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> ssid <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"my_wifi_ssid"</span>; <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> password <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"my_wifi_password"</span>; <span style="color: #888888;">// mqtt configuration</span> <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> server <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"192.168.1.111"</span>; <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> topic <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"/alert"</span>; <span style="color: #008800; font-weight: bold;">const</span> <span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> clientName <span style="color: #333333;">=</span> <span style="background-color: #fff0f0;">"com.gonzalo123.nodemcu"</span>; <span style="color: #333399; font-weight: bold;">int</span> value; <span style="color: #333399; font-weight: bold;">int</span> percent; String payload; WiFiClient wifiClient; PubSubClient <span style="color: #0066bb; font-weight: bold;">client</span>(wifiClient); <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">wifiConnect</span>() { Serial.println(); Serial.print(<span style="background-color: #fff0f0;">"Connecting to "</span>); Serial.println(ssid); WiFi.begin(ssid, password); <span style="color: #008800; font-weight: bold;">while</span> (WiFi.status() <span style="color: #333333;">!=</span> WL_CONNECTED) { delay(<span style="color: #0000dd; font-weight: bold;">500</span>); Serial.print(<span style="background-color: #fff0f0;">"."</span>); } Serial.println(<span style="background-color: #fff0f0;">""</span>); Serial.print(<span style="background-color: #fff0f0;">"WiFi connected."</span>); Serial.print(<span style="background-color: #fff0f0;">"IP address: "</span>); Serial.println(WiFi.localIP()); } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">mqttReConnect</span>() { <span style="color: #008800; font-weight: bold;">while</span> (<span style="color: #333333;">!</span>client.connected()) { Serial.print(<span style="background-color: #fff0f0;">"Attempting MQTT connection..."</span>); <span style="color: #008800; font-weight: bold;">if</span> (client.connect(clientName)) { Serial.println(<span style="background-color: #fff0f0;">"connected"</span>); client.subscribe(topic); } <span style="color: #008800; font-weight: bold;">else</span> { Serial.print(<span style="background-color: #fff0f0;">"failed, rc="</span>); Serial.print(client.state()); Serial.println(<span style="background-color: #fff0f0;">" try again in 5 seconds"</span>); delay(<span style="color: #0000dd; font-weight: bold;">5000</span>); } } } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">callback</span>(<span style="color: #333399; font-weight: bold;">char</span><span style="color: #333333;">*</span> topic, byte<span style="color: #333333;">*</span> payload, <span style="color: #333399; font-weight: bold;">unsigned</span> <span style="color: #333399; font-weight: bold;">int</span> length) { Serial.print(<span style="background-color: #fff0f0;">"Message arrived ["</span>); Serial.print(topic); String data; <span style="color: #008800; font-weight: bold;">for</span> (<span style="color: #333399; font-weight: bold;">int</span> i <span style="color: #333333;">=</span> <span style="color: #0000dd; font-weight: bold;">0</span>; i <span style="color: #333333;"><</span> length; i<span style="color: #333333;">++</span>) { data <span style="color: #333333;">+=</span> (<span style="color: #333399; font-weight: bold;">char</span>)payload[i]; } cleanLeds(); <span style="color: #333399; font-weight: bold;">int</span> value <span style="color: #333333;">=</span> data.toInt(); <span style="color: #008800; font-weight: bold;">switch</span> (value) { <span style="color: #008800; font-weight: bold;">case</span> <span style="color: #0000dd; font-weight: bold;">1</span>: digitalWrite(ledRed, HIGH); <span style="color: #008800; font-weight: bold;">break</span>; <span style="color: #008800; font-weight: bold;">case</span> <span style="color: #0000dd; font-weight: bold;">0</span>: digitalWrite(ledGreen, HIGH); <span style="color: #008800; font-weight: bold;">break</span>; } Serial.print(<span style="background-color: #fff0f0;">"] value:"</span>); Serial.println((<span style="color: #333399; font-weight: bold;">int</span>) value); } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">cleanLeds</span>() { digitalWrite(ledRed, LOW); digitalWrite(ledGreen, LOW); } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">setup</span>() { Serial.begin(<span style="color: #0000dd; font-weight: bold;">9600</span>); pinMode(ledRed, OUTPUT); pinMode(ledGreen, OUTPUT); cleanLeds(); Serial.println(<span style="background-color: #fff0f0;">"start"</span>); wifiConnect(); client.setServer(server, <span style="color: #0000dd; font-weight: bold;">1883</span>); client.setCallback(callback); delay(<span style="color: #0000dd; font-weight: bold;">1500</span>); } <span style="color: #333399; font-weight: bold;">void</span> <span style="color: #0066bb; font-weight: bold;">loop</span>() { Serial.print(<span style="background-color: #fff0f0;">"."</span>); <span style="color: #008800; font-weight: bold;">if</span> (<span style="color: #333333;">!</span>client.connected()) { mqttReConnect(); } client.loop(); delay(<span style="color: #0000dd; font-weight: bold;">500</span>); } |
Aqui você pode ver o protótipo de trabalho em ação:
E aqui o código-fonte.
***
Gonzalo Ayuso faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela Redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://gonzalo123.com/2018/06/04/playing-with-docker-mqtt-grafana-influxdb-python-and-arduino/