Esp32 Micropython Enviar leituras do sensor para o ThingsPeak (BME280)

Tempo de leitura: 11 minutes

Neste guia do usuário, aprenderemos a publicar leituras de sensores no ThingsPeak usando o Micropython e o ESP32. Usaremos o THONNY IDE para programar nossa placa ESP32, que será conectada a um sensor de temperatura, umidade e pressão. Nosso principal objetivo é transmitir essas leituras de sensores para as coisas que as coisas as demonstram com facilidade e interativamente. Qualquer sensor adequado pode ser usado como DS18B20, BME680, LM35 e MPU6050, mas para este artigo, usaremos um sensor BME280 que é usado para medir a temperatura, pressão e umidade.

 

Pré-requisitos

Antes de começar esta lição, certifique-se de estar familiarizado e ter a versão mais recente do firmware MicroPython instalado em sua placa ESP32 e ter um Ambiente de Desenvolvimento Integrado (IDE) em execução no qual faremos a programação, como uPyCraft IDE ou Thonny IDE.

 

Sensor BME280 Introdução

O sensor BME280 é usado para medir leituras relacionadas à temperatura ambiente, pressão barométrica e umidade relativa. É usado principalmente em aplicativos da Web e móveis, onde o baixo consumo de energia é fundamental. Este sensor usa I2C ou SPI para comunicar dados com os microcontroladores. Embora existam várias versões diferentes do BME280 disponíveis no mercado, a que estudaremos usa o protocolo de comunicação I2C e SPI.

 

Conectando o sensor BME280 com a placa de desenvolvimento ESP32

A conexão do BME280 com as placas ESP32 é muito fácil. Temos que conectar o terminal VCC com 3,3V, terra com o terra (terra comum), SCL do sensor com SCL do módulo e SDA do sensor com o pino SDA dos módulos ESP.

O pino I2C no ESP32 para SDA é GPIO21 e para SCL é GPIO22.

Componentes Necessários:

Vamos precisar dos seguintes componentes para conectar nossa placa ESP32 com o sensor BME280.

  • Placa ESP32
  • Sensor BME280
  • Fios de conexão
  • Protoboard

Siga o diagrama esquemático abaixo para o módulo ESP32 e conecte-os de acordo.

Em alguns sensores BME280, conforme visto no diagrama de conexão acima, o terminal SCK significa o pino SCL e está conectado com seu respectivo pino GPIO na placa ESP. Da mesma forma, o terminal SDI significa o pino SDA e está conectado com seu respectivo pino GPIO na placa. Vin é conectado com um pino de 3,3 V no módulo e tanto a placa ESP quanto o sensor são comumente aterrados.

 

Biblioteca BME280 MicroPython

Por padrão, o MicroPython não possui uma implementação da biblioteca BME280. Porém, o MicroPyhon fornece API I2C de ESP32 e ESP8266, que pode ser usado para ler os valores de temperatura, umidade e pressão do sensor BME280. Felizmente, existe uma biblioteca disponível que foi desenvolvida por Robert e pode ser baixada neste link.

Usaremos a IDE Thonny para programar nosso ESP32. Certifique-se de ter a versão mais recente do IDE instalada em seu sistema. Abra um novo arquivo no Thonny. Copie a biblioteca fornecida abaixo.

Salve-o no ESP32 com o nome BME280.py na pasta lib.

from machine import I2C
import time

# BME280 default address.
BME280_I2CADDR = 0x76

# Operating Modes
BME280_OSAMPLE_1 = 1
BME280_OSAMPLE_2 = 2
BME280_OSAMPLE_4 = 3
BME280_OSAMPLE_8 = 4
BME280_OSAMPLE_16 = 5

# BME280 Registers

BME280_REGISTER_DIG_T1 = 0x88  # Trimming parameter registers
BME280_REGISTER_DIG_T2 = 0x8A
BME280_REGISTER_DIG_T3 = 0x8C

BME280_REGISTER_DIG_P1 = 0x8E
BME280_REGISTER_DIG_P2 = 0x90
BME280_REGISTER_DIG_P3 = 0x92
BME280_REGISTER_DIG_P4 = 0x94
BME280_REGISTER_DIG_P5 = 0x96
BME280_REGISTER_DIG_P6 = 0x98
BME280_REGISTER_DIG_P7 = 0x9A
BME280_REGISTER_DIG_P8 = 0x9C
BME280_REGISTER_DIG_P9 = 0x9E

BME280_REGISTER_DIG_H1 = 0xA1
BME280_REGISTER_DIG_H2 = 0xE1
BME280_REGISTER_DIG_H3 = 0xE3
BME280_REGISTER_DIG_H4 = 0xE4
BME280_REGISTER_DIG_H5 = 0xE5
BME280_REGISTER_DIG_H6 = 0xE6
BME280_REGISTER_DIG_H7 = 0xE7

BME280_REGISTER_CHIPID = 0xD0
BME280_REGISTER_VERSION = 0xD1
BME280_REGISTER_SOFTRESET = 0xE0

BME280_REGISTER_CONTROL_HUM = 0xF2
BME280_REGISTER_CONTROL = 0xF4
BME280_REGISTER_CONFIG = 0xF5
BME280_REGISTER_PRESSURE_DATA = 0xF7
BME280_REGISTER_TEMP_DATA = 0xFA
BME280_REGISTER_HUMIDITY_DATA = 0xFD


class Device:
  """Class for communicating with an I2C device.

  Allows reading and writing 8-bit, 16-bit, and byte array values to
  registers on the device."""

  def __init__(self, address, i2c):
    """Create an instance of the I2C device at the specified address using
    the specified I2C interface object."""
    self._address = address
    self._i2c = i2c

  def writeRaw8(self, value):
    """Write an 8-bit value on the bus (without register)."""
    value = value & 0xFF
    self._i2c.writeto(self._address, value)

  def write8(self, register, value):
    """Write an 8-bit value to the specified register."""
    b=bytearray(1)
    b[0]=value & 0xFF
    self._i2c.writeto_mem(self._address, register, b)

  def write16(self, register, value):
    """Write a 16-bit value to the specified register."""
    value = value & 0xFFFF
    b=bytearray(2)
    b[0]= value & 0xFF
    b[1]= (value>>8) & 0xFF
    self.i2c.writeto_mem(self._address, register, value)

  def readRaw8(self):
    """Read an 8-bit value on the bus (without register)."""
    return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') & 0xFF

  def readU8(self, register):
    """Read an unsigned byte from the specified register."""
    return int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 1),'little') & 0xFF

  def readS8(self, register):
    """Read a signed byte from the specified register."""
    result = self.readU8(register)
    if result > 127:
      result -= 256
    return result

  def readU16(self, register, little_endian=True):
    """Read an unsigned 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 2),'little') & 0xFFFF
    if not little_endian:
      result = ((result << 8) & 0xFF00) + (result >> 8)
    return result

  def readS16(self, register, little_endian=True):
    """Read a signed 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = self.readU16(register, little_endian)
    if result > 32767:
      result -= 65536
    return result

  def readU16LE(self, register):
    """Read an unsigned 16-bit value from the specified register, in little
    endian byte order."""
    return self.readU16(register, little_endian=True)

  def readU16BE(self, register):
    """Read an unsigned 16-bit value from the specified register, in big
    endian byte order."""
    return self.readU16(register, little_endian=False)

  def readS16LE(self, register):
    """Read a signed 16-bit value from the specified register, in little
    endian byte order."""
    return self.readS16(register, little_endian=True)

  def readS16BE(self, register):
    """Read a signed 16-bit value from the specified register, in big
    endian byte order."""
    return self.readS16(register, little_endian=False)


class BME280:
  def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None,
               **kwargs):
    # Check that mode is valid.
    if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4,
                    BME280_OSAMPLE_8, BME280_OSAMPLE_16]:
        raise ValueError(
            'Unexpected mode value {0}. Set mode to one of '
            'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or '
            'BME280_ULTRAHIGHRES'.format(mode))
    self._mode = mode
    # Create I2C device.
    if i2c is None:
      raise ValueError('An I2C object is required.')
    self._device = Device(address, i2c)
    # Load calibration values.
    self._load_calibration()
    self._device.write8(BME280_REGISTER_CONTROL, 0x3F)
    self.t_fine = 0

  def _load_calibration(self):

    self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1)
    self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2)
    self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3)

    self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1)
    self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2)
    self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3)
    self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4)
    self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5)
    self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6)
    self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7)
    self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8)
    self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9)

    self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1)
    self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2)
    self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3)
    self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7)

    h4 = self._device.readS8(BME280_REGISTER_DIG_H4)
    h4 = (h4 << 24) >> 20
    self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F)

    h5 = self._device.readS8(BME280_REGISTER_DIG_H6)
    h5 = (h5 << 24) >> 20
    self.dig_H5 = h5 | (
        self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F)

  def read_raw_temp(self):
    """Reads the raw (uncompensated) temperature from the sensor."""
    meas = self._mode
    self._device.write8(BME280_REGISTER_CONTROL_HUM, meas)
    meas = self._mode << 5 | self._mode << 2 | 1
    self._device.write8(BME280_REGISTER_CONTROL, meas)
    sleep_time = 1250 + 2300 * (1 << self._mode)

    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    time.sleep_us(sleep_time)  # Wait the required time
    msb = self._device.readU8(BME280_REGISTER_TEMP_DATA)
    lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_pressure(self):
    """Reads the raw (uncompensated) pressure level from the sensor."""
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA)
    lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_humidity(self):
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA)
    lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1)
    raw = (msb << 8) | lsb
    return raw

  def read_temperature(self):
    """Get the compensated temperature in 0.01 of a degree celsius."""
    adc = self.read_raw_temp()
    var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11)
    var2 = ((
        (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) *
        self.dig_T3) >> 14
    self.t_fine = var1 + var2
    return (self.t_fine * 5 + 128) >> 8

  def read_pressure(self):
    """Gets the compensated pressure in Pascals."""
    adc = self.read_raw_pressure()
    var1 = self.t_fine - 128000
    var2 = var1 * var1 * self.dig_P6
    var2 = var2 + ((var1 * self.dig_P5) << 17)
    var2 = var2 + (self.dig_P4 << 35)
    var1 = (((var1 * var1 * self.dig_P3) >> 8) +
            ((var1 * self.dig_P2) >> 12))
    var1 = (((1 << 47) + var1) * self.dig_P1) >> 33
    if var1 == 0:
      return 0
    p = 1048576 - adc
    p = (((p << 31) - var2) * 3125) // var1
    var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25
    var2 = (self.dig_P8 * p) >> 19
    return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4)

  def read_humidity(self):
    adc = self.read_raw_humidity()
    # print 'Raw humidity = {0:d}'.format (adc)
    h = self.t_fine - 76800
    h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) +
         16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h *
                          self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) *
                          self.dig_H2 + 8192) >> 14))
    h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4)
    h = 0 if h < 0 else h
    h = 419430400 if h > 419430400 else h
    return h >> 12

  @property
  def temperature(self):
    "Return the temperature in degrees."
    t = self.read_temperature()
    ti = t // 100
    td = t - ti * 100
    return "{}.{:02d}C".format(ti, td)

  @property
  def pressure(self):
    "Return the temperature in hPa."
    p = self.read_pressure() // 256
    pi = p // 100
    pd = p - pi * 100
    return "{}.{:02d}hPa".format(pi, pd)

  @property
  def humidity(self):
    "Return the humidity in percent."
    h = self.read_humidity()
    hi = h // 1024
    hd = h * 100 // 1024 - hi * 100
    return "{}.{:02d}%".format(hi, hd)

Preparando a API do ThingSpeak

ThingSpeak é uma API de código aberto usada para armazenar ou recuperar dados usando o protocolo HTTP ou MQTT. Isso ocorre pela Internet ou pela LAN. Usaremos esta API para publicar as leituras do sensor do BME280 integrado à nossa placa ESP32. No ThingSpeak você pode acessar seus dados de qualquer lugar do mundo. Você pode exibir seus dados em plotagens e gráficos.

Para mais informações sobre a API ThingSpeak, você pode dar uma olhada em nossos tutoriais abaixo:

 

Criando conta

A API ThingSpeak é gratuita, mas teremos que criar uma conta MathWorks.
Primeiro vá para o seguinte site: https://thingspeak.com/
A seguinte janela irá aparecer. Clique no botão ‘Get Started for Free’.

Agora você será redirecionado para a janela da conta. Se você já possui uma conta MathWorks, pode usá-la para fazer login. Caso contrário, você terá que criar uma nova. Clique em ‘Create One!’ para criar uma nova conta MathWorks.

Preencha as informações para o cadastro e click em Continue.

Quando você tiver feito login com sucesso, receberá a seguinte notificação:
‘Click OK’.

 

Publique em vários campos de leituras de sensores para ThingSpeak (temperatura, umidade e pressão)

Começaremos criando um novo canal para o nosso projeto. Vá para Channels > My Channels. Then click ‘New Channel’.

 

Você será solicitado a dar um nome ao seu canal. Daremos um nome, alguma descrição e marcaremos o primeiro campo. Nesta seção, mostraremos como publicar vários dados. Você pode usar qualquer nome, descrição e campo de acordo com sua preferência. Há um total de oito campos que podemos adicionar ao nosso canal ao mesmo tempo. Marcaremos os três primeiros campos e adicionaremos os nomes. Clique em ‘Save Channel’ para prosseguir.

Vá para a exibição privada e clique no ícone de lápis na parte superior de cada gráfico de campo. Isso nos permitirá personalizar os gráficos de acordo com nossa preferência. Você pode adicionar detalhes de acordo. Depois de pressionar o botão Salvar, o gráfico será atualizado com as configurações que você acabou de definir.

Depois disso, vá para a guia Chave de API e clique nela. Agora você poderá acessar sua chave de API exclusiva. Salve-o e mantenha-o seguro, pois você precisará dele mais tarde no código do programa.

 

ESP32 ThingSpeak MicroPython Sketch

Abra seu Thonny IDE e vá em File > New para abrir um novo arquivo. Copie o código fornecido abaixo nesse arquivo. Este código funcionará com sua placa ESP32. Você só precisa substituir as credenciais de rede e sua chave de API.

import machine
import urequests 
from machine import Pin, SoftI2C
import network, time
import BME280  

i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=10000)    #initializing the I2C method

HTTP_HEADERS = {'Content-Type': 'application/json'} 
THINGSPEAK_WRITE_API_KEY = '****************' 

UPDATE_TIME_INTERVAL = 5000  # in ms 
last_update = time.ticks_ms() 

ssid='YOUR_SSID'
password='YOUR_PASSWORD'

# Configure ESP32 as Station
sta_if=network.WLAN(network.STA_IF)
sta_if.active(True)

if not sta_if.isconnected():
    print('connecting to network...')
    sta_if.connect(ssid, password)
    while not sta_if.isconnected():
     pass
print('network config:', sta_if.ifconfig()) 

while True: 
    if time.ticks_ms() - last_update >= UPDATE_TIME_INTERVAL: 
         bme = BME280.BME280(i2c=i2c)          #BME280 object created
         temperature = bme.temperature         #reading the value of temperature
         humidity = bme.humidity               #reading the value of humidity
         pressure = bme.pressure               #reading the value of pressure

         bme_readings = {'field1':temperature, 'field2':pressure, 'field3':humidity} 
         request = urequests.post( 'http://api.thingspeak.com/update?api_key=' + THINGSPEAK_WRITE_API_KEY, json = bme_readings, headers = HTTP_HEADERS )  
         request.close() 
         print(bme_readings)

 

Como o Código Funciona?

Em primeiro lugar, importaremos todas as bibliotecas necessárias para este projeto. Isso inclui a classe Pin e a classe SoftI2C do módulo de máquina. Isso ocorre porque temos que especificar o pino para comunicação I2C. Também importamos o módulo de tempo para que possamos adicionar um atraso entre nossas leituras. Além disso, importe a biblioteca BME280 que carregamos anteriormente para o ESP32. Além disso, vamos exigir urequests e módulos de rede também.

import machine
import urequests 
from machine import Pin, SoftI2C
import network, time
import BME280

Agora, inicializamos o método I2C dando a ele três argumentos. O primeiro argumento especifica o pino GPIO para SCL. Isso é dado como GPIO22 para ESP32. O segundo parâmetro especifica o pino GPIO para o SDA. Isso é dado como GPIO21 para ESP32. Lembre-se de que esses são os pinos I2C padrão para SCL e SDA que usamos para a placa ESP32. O terceiro parâmetro especifica a frequência máxima para SCL a ser usado.

i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=10000)    #initializing the I2C method

Especificaremos nossa chave de API de gravação do ThingSpeak na variável ‘THINGSPEAK_WRITE_API_KEY’ que conterá nossa chave de API exclusiva. Esta é a chave que salvamos anteriormente ao criar um novo canal no ThingSpeak. Além disso, criaremos outra variável ‘HTTP_HEADERS’ que será usada posteriormente durante a conexão com a API.

HTTP_HEADERS = {'Content-Type': 'application/json'} 
THINGSPEAK_WRITE_API_KEY = '****************'

Atualizaremos as leituras do sensor a cada 5 segundos. Além disso, a variável ‘last_update’ acompanhará o tempo.

UPDATE_TIME_INTERVAL = 5000  # in ms 
last_update = time.ticks_ms()

Conecte o ESP32 à rede local

A seguir, criaremos duas variáveis para salvar o SSID e os valores de senha. Você deve substituir ambos por suas credenciais de rede para se conectar com sucesso ao seu roteador.

ssid='YOUR_SSID'
password='YOUR_PASSWORD'

As linhas de código a seguir irão configurar a placa ESP32 como estação. O ESP32 se conectará com a rede local e o endereço IP será impresso no Thonny Shell. Isso é necessário porque queremos enviar as leituras do sensor para o ThingSpeak.

sta_if=network.WLAN(network.STA_IF)
sta_if.active(True)

if not sta_if.isconnected():
    print('connecting to network...')
    sta_if.connect(ssid, password)
    while not sta_if.isconnected():
     pass
print('network config:', sta_if.ifconfig())

Obtenha leituras do sensor e publique no ThingSpeak

Dentro do loop while, primeiro verificaremos se é hora de atualizar as leituras do sensor. Então vamos criar um objeto do BME280 chamado bme e acessar a temperatura, umidade e pressão através dele. Essas leituras dos sensores serão salvas em suas respectivas variáveis: temperature, humidity, e pressure.

while True: 
    if time.ticks_ms() - last_update >= UPDATE_TIME_INTERVAL: 
         bme = BME280.BME280(i2c=i2c)          #BME280 object created
         temperature = bme.temperature         #reading the value of temperature
         humidity = bme.humidity               #reading the value of humidity
         pressure = bme.pressure               #reading the value of pressure

         bme_readings = {'field1':temperature, 'field2':pressure, 'field3':humidity} 
         request = urequests.post( 'http://api.thingspeak.com/update?api_key=' + THINGSPEAK_WRITE_API_KEY, json = bme_readings, headers = HTTP_HEADERS )  
         request.close() 
         print(bme_readings)

Em seguida, criaremos uma variável ‘bme_readings’ para armazenar as leituras que serão publicadas em seus respectivos campos. Em nosso caso, configuramos o Campo 1 para leituras de temperatura, Campo 2 para leituras de pressão e Campo 3 para leituras de umidade, respectivamente.

Depois disso, postaremos essas três leituras de sensor em seus respectivos campos usando o método urequests.post() e especificando as credenciais do ThingSpeak que definimos anteriormente. Após isso feche a requisição.

Além disso, todas essas três leituras serão impressas no terminal shell após um atraso de 5 segundos.

me_readings = {'field1':temperature, 'field2':pressure, 'field3':humidity} 
         request = urequests.post( 'http://api.thingspeak.com/update?api_key=' + THINGSPEAK_WRITE_API_KEY, json = bme_readings, headers = HTTP_HEADERS )  
         request.close() 
         print(bme_readings)

Demonstração

Depois de fazer o upload do seu código para a placa de desenvolvimento ESP32, pressione o botão ENABLE.

No terminal Thonny shell você poderá visualizar a placa ESP32 sendo conectada. A cada 5 segundos, novas leituras do sensor continuarão aparecendo à medida que forem publicadas em seus respectivos campos.

Em seguida, abra a API ThingSpeak e você poderá ver as leituras de temperatura, pressão e umidade atualizadas após 5 segundos em seus gráficos.

Conclusão

Em conclusão, pudemos aprender como publicar leituras de sensores no ThingSpeak usando MicroPython e ESP32 de uma maneira bastante fácil. Da mesma forma, você pode publicar dados únicos ou múltiplos na API ThingSpeak, que poderá acessar de qualquer lugar do mundo.