IOT FEITO FÁCIL: ESP-MICROPYTHON-MQTT-THINGSPEAK
Com a ajuda do protocolo MQTT, enviaremos dados capturados de sensores, à um serviço de IoT, o ThingSpeak.com e a um aplicativo móvel, o Thingsview.
1. Introdução
Em meu post anterior, Programando MicroPython no ESP8266, aprendemos como instalar e executar MicroPython em um dispositivo ESP (tanto o ESP8266 quanto o ESP32). Utilizando o Jupyter Notebook como ambiente de desenvolvimento, também aprendemos a ler a partir de sensores (Temperatura, Umidade e Luminosidade), utilizando vários protocolos de comunicação e métodos como: Analógico, Digital, 1-Wire e I2C, este último para exibir os dados capturados em um display do tipo OLED.
Este novo tutorial, utilizando-se do protocolo MQTT, enviaremos os dados capturados, à um serviço the IoT, o ThingSpeak.com e para um aplicativo móvel, o Thingsview.
Aqui, uma visão geral reavaliando nosso projeto:
2. O Hw
O Hw que será utilizado é basicamente o mesmo do projeto anterior: Programando MicroPython no ESP8266 . Por favor, refira-se ao post para uma explicação mais detalhada do HW.
A exceção é o Servo, que não será usado neste projeto.
Acima você poderá ver o HW completo. Conecte os dispositivos conforme mostrado.
3. Micropython, REPL e Jupyter Notebook
Neste ponto, você já deverá ter um interpretador MicroPython carregado em seu dispositivo ESP e, portanto, é possível programá-lo utilizando-se de qualquer um dos IDEs disponíveis, tais como:
- REPL / Ampy
- Jupyter Notebook(*)
- Mu
- ESPCut (somente Windows)
- … Etc
Em meu post Programando MicroPython no ESP8266 , detalho como baixar e instalar o interpretador MicroPython, o ESPTool para gerenciar dispositivos ESP bem como utilizar o Jupyter Notebook como um Ambiente de Desenvolvimento. Sinta-se à vontade para usar o que for mais confortável para você.
(*) Eu costumo fazer todo o desenvolvimento no Jupyter Notebook, e uma vez que tenho o código final, eu o copio para o Geany, salvando-o como um script python. O script é carregado no ESP utilizando o Ampy. Eu achei este método muito fácil para criar projetos mais complexos e profissionais.
4. Sensores
Instalemos as bibliotecas, definamos GPIOs e criemos objetos e funções para todos os sensores individualmente:
A. DHT (temperatura e umidade)
Instale a biblioteca DHT e crie um objeto:
from dht import DHT22 from machine import Pin dht22 = DHT22(Pin(12))
Crie uma função para ler o sensor DHT:
def readDht():
<strong> dht22.measure()</strong> <strong> return dht22.temperature(), dht22.humidity()</strong>
Teste a função:
<strong>print (readDht())</strong>
O resultado deve ser um tuple como abaixo:
<strong>(17.7, 43.4)</strong>
B. DS18B20 (temperatura externa)
[/php]
Instale as bibliotecas e crie um objeto:
import onewire, ds18x20 import time # Define the pin to be used with 1-wire bus ==> pin 2 (D4) dat = Pin(2) # create the onewire object ds = ds18x20.DS18X20(onewire.OneWire(dat))
Procure dispositivos no barramento:
sensors = ds.scan() print('found devices:', sensors)
O resultado da função print não é realmente importante, o que vamos precisar é do primeiro sensor detectado: sensors [0]. E agora criemos uma função para ler os dados do sensor:
def readDs(): ds.convert_temp() time.sleep_ms(750) return ds.read_temp(sensors[0])
É sempre importante testar o sensor usando a função criada:
print(readDs())
Se você obtiver um valor coerente para a temperatura, seu código estará correto. Por exemplo:
17.5
C. LDR (Luminosidade)
O LDR usará o pino analógico de nosso ESP (Lembre-se que o NodeMCU (ESP8266) possui apenas uma entrada analógica, sendo que no caso do ESP32, existem várias).
Repetindo o que foi feito anteriormente:
# import library from machine import ADC # Define object adc = ADC(0)
Uma simples função adc.read () poderá ser usada para ler o valor do ADC. Mas lembre-se que o ADC interno irá converter tensões entre 0 e 3.3V, em valores digitais correspondentes (variando de 0 a 1023). Uma vez que estamos interessados em “Luminosidade”, consideraremos o valor máximo capturado pelo sensor (no meu caso 900) e o valor para “minima luz”, que em meu caso é 40. Com estes valores a mão, podemos “mapear” o valor de [40 a 900] para [0 a 100%] de luminosidade. Para isso, criemos uma nova função:
def readLdr(): lumPerct = (adc.read()-40)*(10/86) return round(lumPerct)
Você deve testar a função usando print (readLDR ()) . O resultado deverá ser um inteiro entre 0 e 100.
D. Botão de pressão (entrada digital)
Aqui estamos usando um botão de pressão (“Push-Button”) atuando como um “sensor digital”, o qual poderia ser o “eco” de um atuador, informando que uma bomba foi ligada ou desligada, por exemplo.
# define pin 13 as an input and activate an internal Pull-up resistor: button = Pin(13, Pin.IN, Pin.PULL_UP) # Function to read button state: def readBut(): return button.value()
Você pode testar o botão usando a função print (readBut ()) . Sem pressioná-lo, o resultado deve ser “1”. Pressionando o botão, o resultado deve ser “0”.
5. Capturando e Exibindo Localmente os Dados do Sensor
Agora que criamos uma função para cada sensor, criemos outra, responsável por ler todas eles ao mesmo tempo:
def colectData(): temp, hum, = readDht() extTemp = readDs() lum = readLdr() butSts = readBut() return temp, hum, extTemp, lum, butSts
Agora, usando:
print(colectData())
Resultará em um tuple, o qual inclui todos os dados capturados dos sensores:
(17.4, 45.2, 17.3125, 103, 1)
Podemos também, opcionalmente, mostrar esses dados localmente no OLED:
# import library and create object i2c from machine import I2C i2c = I2C(scl=Pin(5), sda=Pin(4)) # import library and create object oled import ssd1306 i2c = I2C(scl=Pin(5), sda=Pin(4)) oled = ssd1306.SSD1306_I2C(128, 64, i2c, 0x3c) # create a function: def displayData(temp, hum, extTemp, lum, butSts): oled.fill(0) oled.text("Temp: " + str(temp) + "oC", 0, 4) oled.text("Hum: " + str(hum) + "%",0, 16) oled.text("ExtTemp: " + str(extTemp) + "oC", 0, 29) oled.text("Lumin: " + str(lum) + "%", 0, 43) oled.text("Button: " + str(butSts), 0, 57 oled.show() # display data using the function displayData(temp, hum, extTemp, lum, butSts)
Como opção, poderemos usar o LED para acender quando começarmos a ler os sensores, apagando logo depois que os dados sejam exibidos. Isso ajudará a confirmar que o programa está funcionando quando o ESP for desconectado do PC e executado de forma autônoma.
Então, a “função principal ficaria:
def main(): led.on() temp, hum, extTemp, lum, butSts = colectData() displayData(temp, hum, extTemp, lum, butSts) led.off()
Executando main (), obteremos os dados do sensor exibidos no OLED como mostrado na imagem anterior.
6. Executar o código da estação local no ESP Start-up
e você usou o Jupyter Notebook para criar e testar as funções, então é hora de juntar tudo o que foi desenvolvido até o momento em um único script a ser executado pelo ESP autonomamente.
Abra qualquer editor de texto e “cole” todo o código desenvolvido (eu gosto de usar o Geany):
from machine import Pin import time # LED led = Pin(0, Pin.OUT) # DHT from dht import DHT22 dht22 = DHT22(Pin(12)) def readDht(): dht22.measure() return dht22.temperature(), dht22.humidity() # DS18B20 import onewire, ds18x20 dat = Pin(2) ds = ds18x20.DS18X20(onewire.OneWire(dat)) sensors = ds.scan() def readDs(): ds.convert_temp() time.sleep_ms(750) return round(ds.read_temp(sensors[0]), 1) # LDR from machine import ADC adc = ADC(0) def readLdr(): lumPerct = (adc.read()-40)*(10/86) return round(lumPerct) # Push Button button = Pin(13, Pin.IN, Pin.PULL_UP) def readBut(): return button.value() # Read all data: def colectData(): temp, hum, = readDht() extTemp = readDs() lum = readLdr() butSts = readBut() from machine import Pin import time # LED led = Pin(0, Pin.OUT) # DHT from dht import DHT22 dht22 = DHT22(Pin(12)) def readDht(): dht22.measure() return dht22.temperature(), dht22.humidity() # DS18B20 import onewire, ds18x20 dat = Pin(2) ds = ds18x20.DS18X20(onewire.OneWire(dat)) sensors = ds.scan() return temp, hum, extTemp, lum, butSts # I2C / OLED from machine import I2C import ssd1306 i2c = I2C(scl=Pin(5), sda=Pin(4)) oled = ssd1306.SSD1306_I2C(128, 64, i2c, 0x3c) def displayData(temp, hum, extTemp, lum, butSts): oled.fill(0) oled.text("Temp: " + str(temp) + "oC", 0, 4) oled.text("Hum: " + str(hum) + "%",0, 16) oled.text("ExtTemp: " + str(extTemp) + "oC", 0, 29) oled.text("Lumin: " + str(lum) + "%", 0, 43) oled.text("Button: " + str(butSts), 0, 57) oled.show() # Main function def main(): led.on() temp, hum, extTemp, lum, butSts = colectData() displayData(temp, hum, extTemp, lum, butSts) led.off() '''------ run main function --------''' main()
Salve-o, por exemplo, como localData.py.
Para executar este código diretamente no terminal, você precisará do Ampy.
Primeiro, no terminal, informemos ao Ampy qual é porta serial em que está conectado o ESP:
export AMPY_PORT=/dev/tty.SLAB_USBtoUART
Agora, podemos por exemplo ver os arquivos que estão dentro do diretório raiz do ESP, simplesmente digitando:
ampy ls
Como resposta, obteremos pelo menos o boot.py (e qualquer outro script que esteja gravado no ESP) , que é o primeiro arquivo que será executado no sistema.
Agora, usemos o Ampy para carregar o script python LocalData.py como /main.py, assim nosso script será executado logo após a inicialização do ESP:
<strong>ampy put localData.py /main.py</strong>
Se usarmos o comando amp ls agora, você verá dois arquivos dentro do ESP: boot.py e main.py
Um Reset no seu ESP fará com que o programa localData.py (carregado como /main.py) seja executado automaticamente, exibindo assim os dados do sensor na tela.
A tela do Terminal acima, mostra o que discutimos.
Utilizando o código acima, os dados serão capturados e mostrados apenas uma vez. Definindo um loop na função main (), se poderá obter novos dados para um dado intervalo de tempo que deve ser definido por uma constante de tempo (no código abaixo: PUB_TIME_SEC). A captura continuaria infinitamente ou por exemplo, até pressionarmos o botão:
# loop getting data until button is pressed while button.value(): led.on() temp, hum, extTemp, lum, butSts = colectData() displayData(temp, hum, extTemp, lum, butSts) led.off() time.sleep(PUB_TIME_SEC)
A variável PUB_TIME_SEC deve ser declarada com um tempo em segundos, o qual você deseja obter suas amostras.
Para melhorar mais ainda nosso código, seria bom informar que “saímos do loop”, para isso definiremos 2 novas funções gerais, uma para limpar a tela do OLED e outra para piscar o LED em um determinado número de vezes.
# Clear display : def displayClear(): oled.fill(0) oled.show() # Blink LED "num" times def blinkLed(num): for i in range(0, num): led.on() sleep(0.5) led.off() sleep(0.5)
Reescrevemos assim, nossa função main ():
while button.value(): led.on() temp, hum, extTemp, lum, butSts = colectData() displayData(temp, hum, extTemp, lum, butSts) led.off() time.sleep(PUB_TIME_SEC) blinkLed(3) displayClear()
O código final pode ser baixado do meu GitHub: localData.py e também o Jupyter Notebookutilizado para o desenvolvimento do código completo: Jupyter Local Data Development.ipynb.
7: Conectando o ESP ao WiFi Local
O módulo “Network” é usado para configurar a conexão WiFi. Existem duas interfaces WiFi, uma para modo “estação” (quando o ESP8266 se conecta a um roteador) e outra para modo “ponto de acesso” (quando outros dispositivos se conectam ao ESP8266). Aqui, nosso ESP será conectado à rede local através de um roteador. Chamemos a biblioteca e definamos nossas credenciais para conexão à rede:
import network WiFi_SSID = "YOUR SSID" WiFi_PASS = "YOUR PASSWORD"
A função abaixo pode ser usada para conectar o ESP à sua rede local:
def do_connect(): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') wlan.connect(WiFi_SSID, WiFi_SSID) while not wlan.isconnected(): pass print('network config:', wlan.ifconfig()) Executando a função acima, você obterá como resultado o endereço IP: do_connect()
configuração de rede: (‘10.0.1.2 ‘,’ 255.255.255.0 ‘, ‘10.0.1.1’, ‘10.0.1.1 ‘)
Em meu caso, 10.0.1.2, é o endereço IP atribuído ao ESP, pelo meu router.
8. O ThingSpeak
Neste ponto, já aprendemos como capturar dados dos sensores, exibindo-os em nosso OLED. Agora, é hora de ver como enviar esses dados para uma plataforma de IoT, o ThingSpeak.com.
ThingSpeak.com.
“O ThingSpeak é um aplicativo de Internet of Things (IoT) de código-fonte aberto para armazenar e recuperar dados de “coisas”, utilizando APIs REST e MQTT. O ThingSpeak permite a criação de aplicativos para registro de sensores, aplicativos de rastreamento de localização e uma rede social de coisas com atualizações de status ”
Para começar, você deve ter uma conta no ThinkSpeak.com. Em seguida, siga as instruções para criar um canal e anote sua ID de canal e sua chave de API de gravação.
Acima você poderá ver os 5 campos (“fields”) que serão utilizados em nosso canal.
9. Protocolo MQTT e Conexão ThingSpeak
O MQTT é uma arquitetura de publicação / assinatura desenvolvida principalmente para conectar dispositivos com restrições de largura de banda e de energia em redes sem fio. É um protocolo simples e leve que é executado em soquetes TCP / IP ou WebSockets. O MQTT sobre WebSockets pode ser protegido com SSL. A arquitetura de publicação / assinatura permite que as mensagens sejam enviadas aos dispositivos do cliente sem que o dispositivo precise pesquisar continuamente o servidor.
O MQTT “Broker” é o ponto central de comunicação e é responsável pelo envio de todas as mensagens entre os corretos remetentes e destinatários. Um cliente é qualquer dispositivo que se conecta ao broker e pode publicar ou assinar tópicos (“topics”) para acessar as informações. Um tópico contém as informações de roteamento para o broker. Cada cliente que deseja enviar mensagens, as publica em um determinado tópico, e cada cliente que deseja receber mensagens se inscreve em um determinado tópico. O intermediário entrega todas as mensagens com o tópico correspondente aos clientes apropriados.
O ThingSpeak ™ possui um broker MQTT na URL mqtt.thingspeak.com e na porta 1883. O broker ThingSpeak suporta tanto a publicação do MQTT quanto a assinatura do MQTT.
No nosso caso, usaremos: MQTT Publish.
A figura acima descreve a estrutura do tópico. A chave da API de gravação é necessária para publicar. O broker confirma uma solicitação CONNECT correta com CONNACK.
O protocolo MQTT é suportado por uma biblioteca integrada ao MicroPython. Este protocolo pode ser usado para enviar dados do seu ESP8266, via WIFI, para um banco de dados na nuvem, como o ThingSpeak.
Usaremos a biblioteca umqtt.simple :
from umqtt.simple import MQTTClient
E conhecendo nosso ID de servidor, é possível criar nosso objeto de cliente MQTT:
SERVER = "mqtt.thingspeak.com" client = MQTTClient("umqtt_client", SERVER)
Agora, com as credenciais do ThingSpeak à mão:
CHANNEL_ID = "YOUR CHANNEL ID" WRITE_API_KEY = "YOUR KEY HERE"
Vamos criar nosso “Tópico”:
topic = "channels/" + CHANNEL_ID + "/publish/" + WRITE_API_KEY
Obteremos os dados a serem enviados para o ThingSpeak IoT Service, utilizando a função criada anteriormente, associando sua resposta a variáveis de dados específicas:
temp, hum, extTemp, lum, butSts = colectData()
Com as variáveis já atualizadas com os dados provenientes dos sensores, poderemos criar nossa “carga útil”:
payload "field1="+str(temp)+"&field2="+str(hum)+"&field3="+str(extTemp)+"&field4="+str(lum)+"&field5="+str(butSts)
E é somente isso! Estamos prontos para enviar dados para o ThinsSpeak, simplesmente utilizando as 3 linhas de código abaixo:
client.connect() client.publish(topic, payload) client.disconnect()
Agora, se você for para a página de seu canal (como a minha abaixo), verá que cada um dos cinco campos estará mostrando dados relacionados aos seus sensores.
10. registrador de dados do sensor
Agora que já sabemos que com apenas algumas linhas de código é possível fazer o upload de dados para um serviço de IoT, vamos criar uma função de loop para fazê-lo de maneira automática em um intervalo de tempo regular (semelhante ao que fizemos anteriormente com os “Dados locais ”).
Utilizando a mesma variável (PUB_TIME_SEC) declarada anteriormente, uma função main() pode ser criada para capturar dados continuamente, registrando-os em nosso canal:
while True: temp, hum, extTemp, lum, butSts = colectData() payload = "field1="+str(temp)+"&field2="+str(hum)+"&field3="+str(extTemp)+"&field4="+str(lum)+"&field5="+str(butSts) client.connect() client.publish(topic, payload) client.disconnect() time.sleep(PUB_TIME_SEC)
Observe que somente a “carga útil” (payload) deve ser atualizada, pois “tópico” está relacionado à credencial do nosso canal e não será alterado.
Acompanhando a página de seu canal no ThingSpeak.com, você observará que os dados serão carregados continuamente.
Cubra o LDR, coloque sua mão nos sensores temp / hum, pressione o botão, etc. e veja como o canal estará automaticamente “registrando” esses dados para análise futura.
Normalmente, para um projeto de Data Logging real, devemos tentar usar o menor consumo de energia possível, portanto, não usaríamos o LED ou um display local. Além disso, é comum com dispositivos ESP colocá-los em “deep sleep”, onde o microprocessador deverá permanecer em seu estado de energia mínima até o momento de “acordar” para capturar dados e enviá-los para a plataforma IoT.
Porém, uma vez que aqui a ideia é aprender, também incluiremos o display e o LED como fizemos anteriormente. Fazendo isso, nossa nova função “logger” será:
while button.value(): led.on() temp, hum, extTemp, lum, butSts = colectData() displayData(temp, hum, extTemp, lum, butSts) led.off() payload = "field1="+str(temp)+"&field2="+str(hum)+"&field3="+str(extTemp)+"&field4="+str(lum)+"&field5="+str(butSts) client.connect() client.publish(topic, payload) client.disconnect() time.sleep(PUB_TIME_SEC) blinkLed(3) displayClear()
O script microPython completo pode ser encontrado aqui: dataLoggerTS_EXT.py e o Jupyter Notebbok utilizado para desenvolvimento também pode ser encontrado aqui: IoT ThingSpeak Data Logger EXT.ipynb .
Para carregar o script no ESP, uma vez em seu terminal, use o comando:
ampy put dataLoggerTS.py /main.py
E pressione o botão de reset do ESP.
Você terá o ESP capturando dados e registrando-os no ThingSpeak.com até que o botão seja pressionado (espere que o LED pisque 3 vezes e o OLED apague).
11. O aplicativo ThingView
Os dados registrados podem ser visualizados diretamente no site ThingSpeak.com ou por meio de um app móvel, por exemplo, o ThingsView!
O ThingView é uma app desenvolvido pela CINETICA , a qual permite visualizar os canais do ThingSpeak de maneira bem fácil, bastando digitar o ID do canal.
Para canais públicos, o aplicativo respeitará as configurações das “janelas”: cor, escala de tempo, tipo de gráfico e número de resultados. A versão atual suporta gráficos de linha e coluna, os gráficos “spline” são exibidos como gráficos de linha.
Para canais privados, os dados serão exibidos usando as configurações padrão, pois não há como ler as configurações das janelas privadas apenas com a chave da API.
O aplicativo ThingView pode ser baixado tanto para ANDROID como para IPHONE .
12: Conclusão
Como sempre, espero que este projeto possa ajudar outras pessoas a encontrar seu caminho emocionante mundo da eletrônica!
Para detalhes e código final, por favor visite o meu GitHub: IoT_TS_MQTT
Saludos desde el sur del mundo!
Até meu próximo post!