MicroPython: MQTT – Publish BME280 Sensor Readings (ESP32/ESP8266)

Learn how to program the ESP32 or ESP8266 boards with MicroPython to publish BME280 sensor readings (temperature, humidity and pressure) via MQTT to any platform that supports MQTT or any MQTT client. As an example, we’ll publish sensor readings to Node-RED Dashboard.

MicroPython MQTT Publish BME280 Sensor Readings Temperature Humidity Pressure with ESP32 ESP8266

Recommended reading: What is MQTT and How It Works

Note: this tutorial is compatible with both the ESP32 and ESP8266 development boards.

Project Overview

The following diagram shows a high-level overview of the project we’ll build.

BME280 ESP32 ESP8266 MicroPython MQTT Project Overview
  • The ESP requests temperature and humidity readings from the BME280 sensor;
  • Temperature readings are published in the esp/bme280/temperature topic;
  • Humidity readings are published in the esp/bme280/humidity topic;
  • Pressure readings are published in the esp/bme280/pressure topic;
  • Node-RED is subscribed to those topics;
  • Node-RED receives the sensor readings and displays them on gauges;
  • You can receive the readings in any other platform that supports MQTT and handle the readings as you want.

Prerequisites

Before continuing with this tutorial, make sure you complete the following prerequisites:

To follow this tutorial you need MicroPython firmware installed in your ESP32 or ESP8266 boards. You also need an IDE to write and upload the code to your board. We suggest using Thonny IDE or uPyCraft IDE:

MQTT Broker

Mosquitto MQTT Broker

To use MQTT, you need a broker. We’ll be using Mosquitto broker installed on a Raspberry Pi. Read How to Install Mosquitto Broker on Raspberry Pi.

If you’re not familiar with MQTT make sure you read our introductory tutorial: What is MQTT and How It Works

Parts Required

For this tutorial you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

umqtttsimple Library

To use MQTT with the ESP32/ESP8266 and MicroPython, we’ll use the umqttsimple.py library. Follow the next set of instructions for the IDE you’re using:

  • A. Upload umqttsimple library with uPyCraft IDE
  • B. Upload umqttsimple library with Thonny IDE
try:
    import usocket as socket
except:
    import socket
import ustruct as struct
from ubinascii import hexlify

class MQTTException(Exception):
    pass

class MQTTClient:

    def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0,
                 ssl=False, ssl_params={}):
        if port == 0:
            port = 8883 if ssl else 1883
        self.client_id = client_id
        self.sock = None
        self.server = server
        self.port = port
        self.ssl = ssl
        self.ssl_params = ssl_params
        self.pid = 0
        self.cb = None
        self.user = user
        self.pswd = password
        self.keepalive = keepalive
        self.lw_topic = None
        self.lw_msg = None
        self.lw_qos = 0
        self.lw_retain = False

    def _send_str(self, s):
        self.sock.write(struct.pack("!H", len(s)))
        self.sock.write(s)

    def _recv_len(self):
        n = 0
        sh = 0
        while 1:
            b = self.sock.read(1)[0]
            n |= (b & 0x7f) << sh
            if not b & 0x80:
                return n
            sh += 7

    def set_callback(self, f):
        self.cb = f

    def set_last_will(self, topic, msg, retain=False, qos=0):
        assert 0 <= qos <= 2
        assert topic
        self.lw_topic = topic
        self.lw_msg = msg
        self.lw_qos = qos
        self.lw_retain = retain

    def connect(self, clean_session=True):
        self.sock = socket.socket()
        addr = socket.getaddrinfo(self.server, self.port)[0][-1]
        self.sock.connect(addr)
        if self.ssl:
            import ussl
            self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
        premsg = bytearray(b"\x10\0\0\0\0\0")
        msg = bytearray(b"\x04MQTT\x04\x02\0\0")

        sz = 10 + 2 + len(self.client_id)
        msg[6] = clean_session << 1
        if self.user is not None:
            sz += 2 + len(self.user) + 2 + len(self.pswd)
            msg[6] |= 0xC0
        if self.keepalive:
            assert self.keepalive < 65536
            msg[7] |= self.keepalive >> 8
            msg[8] |= self.keepalive & 0x00FF
        if self.lw_topic:
            sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
            msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
            msg[6] |= self.lw_retain << 5

        i = 1
        while sz > 0x7f:
            premsg[i] = (sz & 0x7f) | 0x80
            sz >>= 7
            i += 1
        premsg[i] = sz

        self.sock.write(premsg, i + 2)
        self.sock.write(msg)
        #print(hex(len(msg)), hexlify(msg, ":"))
        self._send_str(self.client_id)
        if self.lw_topic:
            self._send_str(self.lw_topic)
            self._send_str(self.lw_msg)
        if self.user is not None:
            self._send_str(self.user)
            self._send_str(self.pswd)
        resp = self.sock.read(4)
        assert resp[0] == 0x20 and resp[1] == 0x02
        if resp[3] != 0:
            raise MQTTException(resp[3])
        return resp[2] & 1

    def disconnect(self):
        self.sock.write(b"\xe0\0")
        self.sock.close()

    def ping(self):
        self.sock.write(b"\xc0\0")

    def publish(self, topic, msg, retain=False, qos=0):
        pkt = bytearray(b"\x30\0\0\0")
        pkt[0] |= qos << 1 | retain
        sz = 2 + len(topic) + len(msg)
        if qos > 0:
            sz += 2
        assert sz < 2097152
        i = 1
        while sz > 0x7f:
            pkt[i] = (sz & 0x7f) | 0x80
            sz >>= 7
            i += 1
        pkt[i] = sz
        #print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt, i + 1)
        self._send_str(topic)
        if qos > 0:
            self.pid += 1
            pid = self.pid
            struct.pack_into("!H", pkt, 0, pid)
            self.sock.write(pkt, 2)
        self.sock.write(msg)
        if qos == 1:
            while 1:
                op = self.wait_msg()
                if op == 0x40:
                    sz = self.sock.read(1)
                    assert sz == b"\x02"
                    rcv_pid = self.sock.read(2)
                    rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
                    if pid == rcv_pid:
                        return
        elif qos == 2:
            assert 0

    def subscribe(self, topic, qos=0):
        assert self.cb is not None, "Subscribe callback is not set"
        pkt = bytearray(b"\x82\0\0\0")
        self.pid += 1
        struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
        #print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt)
        self._send_str(topic)
        self.sock.write(qos.to_bytes(1, "little"))
        while 1:
            op = self.wait_msg()
            if op == 0x90:
                resp = self.sock.read(4)
                #print(resp)
                assert resp[1] == pkt[2] and resp[2] == pkt[3]
                if resp[3] == 0x80:
                    raise MQTTException(resp[3])
                return

    # Wait for a single incoming MQTT message and process it.
    # Subscribed messages are delivered to a callback previously
    # set by .set_callback() method. Other (internal) MQTT
    # messages processed internally.
    def wait_msg(self):
        res = self.sock.read(1)
        self.sock.setblocking(True)
        if res is None:
            return None
        if res == b"":
            raise OSError(-1)
        if res == b"\xd0":  # PINGRESP
            sz = self.sock.read(1)[0]
            assert sz == 0
            return None
        op = res[0]
        if op & 0xf0 != 0x30:
            return op
        sz = self._recv_len()
        topic_len = self.sock.read(2)
        topic_len = (topic_len[0] << 8) | topic_len[1]
        topic = self.sock.read(topic_len)
        sz -= topic_len + 2
        if op & 6:
            pid = self.sock.read(2)
            pid = pid[0] << 8 | pid[1]
            sz -= 2
        msg = self.sock.read(sz)
        self.cb(topic, msg)
        if op & 6 == 2:
            pkt = bytearray(b"\x40\x02\0\0")
            struct.pack_into("!H", pkt, 2, pid)
            self.sock.write(pkt)
        elif op & 6 == 4:
            assert 0

    # Checks whether a pending message from server is available.
    # If not, returns immediately with None. Otherwise, does
    # the same processing as wait_msg.
    def check_msg(self):
        self.sock.setblocking(False)
        return self.wait_msg()

View raw code

A. Upload umqttsimple library with uPyCraft IDE

1. Create a new file by pressing the New File button.

2. Copy the umqttsimple library code into it. You can access the umqttsimple library code in the following link:

3. Save the file by pressing the Save button.

4. Call this new file “umqttsimple.py” and press ok.

save new umqttsimple.py upycraft IDE

5. Click the Download and Run button.

6. The file should be saved on the device folder with the name “umqttsimple.py” as highlighted in the figure below.

save umqttsimple.py upycraft IDE

Now, you can use the library functionalities in your code by importing the library.

B. Upload umqttsimple library with Thonny IDE

1. Copy the library code to a new file. The umqttsimple library code can be found here.

2. Go to File > Save as…

Thonny IDE ESP32 ESP8266 MicroPython Save file library to device save as

3. Select save to “MicroPython device“:

Thonny IDE ESP32 ESP8266 MicroPython Save file library to device select

4. Name your file as umqttsimple.py and press the OK button:

umqttsimple library new MicroPython file Thonny IDE

And that’s it. The library was uploaded to your board. To make sure that it was uploaded successfully, go to File > Save as… and select the MicroPython device. Your file should be listed there:

umqttsimple library MicroPython file created Thonny IDE

After uploading the library to your board, you can use the library functionalities in your code by importing the library.

BME280 MicroPython Library

The library to read from the BME280 sensor isn’t part of the standard MicroPython library by default. So, you need to upload the following library to your ESP32/ESP8266 board (save it with the name BME280.py).

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)

View raw code

Schematic: ESP32 with BME280

Wire the BME280 sensor to the ESP32 development board as shown in the following schematic diagram.

ESP32 wiring to BME280 Schematic Diagram

Learn how to use the ESP32 GPIOs with our guide: ESP32 Pinout Reference: Which GPIO pins should you use?

Schematic: ESP8266 NodeMCU with BME280

If you’re using an ESP8266 NodeMCU, follow the next diagram instead.

ESP8266 BME280 Wiring Schematic Diagram

Learn how to use the ESP8266 GPIOs with our guide: ESP8266 Pinout Reference: Which GPIO pins should you use?

Code

After uploading the libraries to the ESP32 or ESP8266, copy the following code to the main.py file. It publishes the temperature, humidity and pressure on the esp/bme280/temperature, esp/bme280/humidity and esp/bme280/pressure topics every 5 seconds.

# Complete project details at https://RandomNerdTutorials.com/micropython-mqtt-publish-bme280-esp32-esp8266/

import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
import BME280
from machine import Pin, I2C

esp.osdebug(None)
import gc
gc.collect()

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = '192.168.1.XXX'
#EXAMPLE IP ADDRESS
#mqtt_server = '192.168.1.106'

client_id = ubinascii.hexlify(machine.unique_id())

topic_pub_temp = b'esp/bme280/temperature'
topic_pub_hum = b'esp/bme280/humidity'
topic_pub_pres = b'esp/bme280/pressure'

last_message = 0
message_interval = 5

station = network.WLAN(network.STA_IF)

station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
  pass

print('Connection successful')

# ESP32 - Pin assignment
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=10000)
# ESP8266 - Pin assignment
#i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)
bme = BME280.BME280(i2c=i2c)

def connect_mqtt():
  global client_id, mqtt_server
  client = MQTTClient(client_id, mqtt_server)
  #client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
  client.connect()
  print('Connected to %s MQTT broker' % (mqtt_server))
  return client

def restart_and_reconnect():
  print('Failed to connect to MQTT broker. Reconnecting...')
  time.sleep(10)
  machine.reset()

def read_bme_sensor():
  try:
    temp = b'%s' % bme.temperature[:-1]
    #temp = (b'{0:3.1f},'.format((bme.read_temperature()/100) * (9/5) + 32))
    hum = b'%s' % bme.humidity[:-1]
    pres = b'%s'% bme.pressure[:-3]

    return temp, hum, pres
    #else:
    #  return('Invalid sensor readings.')
  except OSError as e:
    return('Failed to read sensor.')

try:
  client = connect_mqtt()
except OSError as e:
  restart_and_reconnect()

while True:
  try:
    if (time.time() - last_message) > message_interval:
      temp, hum, pres = read_bme_sensor()
      print(temp)
      print(hum)
      print(pres)
      client.publish(topic_pub_temp, temp)
      client.publish(topic_pub_hum, hum)      
      client.publish(topic_pub_pres, pres)

      last_message = time.time()
  except OSError as e:
    restart_and_reconnect()

View raw code

How the Code Works

Import the following libraries:

import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
import BME280
from machine import Pin, I2C

In the following variables, you need to enter your network credentials and your broker IP address.

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = 'REPLACE_WITH_YOUR_MQTT_BROKER_IP'

For example, our broker IP address is: 192.168.1.106.

mqtt_server = '192.168.1.106'

Note: read this tutorial to see how to get your broker IP address.

To create an MQTT client, we need to get the ESP unique ID. That’s what we do in the following line (it is saved on the client_id variable).

client_id = ubinascii.hexlify(machine.unique_id())

Next, create the topics you want your ESP to be publishing in. In our example, it will publish temperature on the esp/bme280/temperature topic, humidity on the esp/bme280/humidity topic and the pressure on the esp/bme280/pressure topic.

topic_pub_temp = b'esp/bme280/temperature'
topic_pub_hum = b'esp/bme280/humidity'
topic_pub_pres = b'esp/bme280/pressure'

Then, create the following variables:

last_message = 0
message_interval = 5

The last_message variable will hold the last time a message was sent. The message_interval is the time between each message sent. Here, we’re setting it to 5 seconds (this means a new message will be sent every 5 seconds). You can change it, if you want.

After that, connect the ESP to your local network.

station = network.WLAN(network.STA_IF)

station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
  pass

print('Connection successful')

Create an i2c instance on the ESP32 I2C pins to communicate with the BME280 sensor:

i2c = I2C(scl=Pin(22), sda=Pin(21), freq=10000)

If you’re using an ESP8266, use the following line instead (to use the ESP8266 default I2C pins).

#i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)

Create a BME280 instance on the ESP I2C pins:

bme = BME280.BME280(i2c=i2c)

Connect to MQTT Broker

The connect_mqtt() function creates an MQTT Client and connects to your broker.

def connect_mqtt():
  global client_id, mqtt_server
  client = MQTTClient(client_id, mqtt_server)
  #client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
  client.connect()
  print('Connected to %s MQTT broker' % (mqtt_server))
  return client

If your MQTT broker requires username and password, you should use the following line to pass your broker username and password as arguments.

client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)

Restart and Reconnect

The restart_and_reconnect() function resets the ESP32/ESP8266 board. This function will be called if we’re not able to publish the readings via MQTT in case the broker disconnects.

def restart_and_reconnect():
  print('Failed to connect to MQTT broker. Reconnecting...')
  time.sleep(10)
  machine.reset()

Read BME280 Sensor

We created a function called read_bme_sensor() that returns the current temperature, humidity and pressure readings from the BME280 sensor and handles any exceptions, in case we’re not able to get readings from the sensor.

def read_bme_sensor():
  try:
    temp = b'%s' % bme.temperature[:-1]
    #temp = (b'{0:3.1f},'.format((bme.read_temperature()/100) * (9/5) + 32))
    hum = b'%s' % bme.humidity[:-1]
    pres = b'%s'% bme.pressure[:-3]

    return temp, hum, pres
    #else:
    #  return('Invalid sensor readings.')
  except OSError as e:
    return('Failed to read sensor.')

Learn more about getting readings from the BME280 sensor: MicroPython: BME280 with ESP32 and ESP8266 (Pressure, Temperature, Humidity)

Publishing MQTT Messages

In the while loop, we publish new BME280 readings every 5 seconds.

First, we check if it is time to get new readings:

if (time.time() - last_message) > message_interval:

If it is, request new readings from the BME280 sensor by calling the read_bme_sensor() function. The temperature is saved on the temp variable, the humidity is saved on the hum variable and the pressure on the pres variable.

temp, hum, pres = read_bme_sensor()

Finally, publish the readings by using the publish() method on the client object. The publish() method accepts as arguments the topic and the message, as follows:

client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
client.publish(topic_pub_pres, pres)

Finally, update the time when the last message was sent:

last_message = time.time()

In case the ESP32 or ESP8266 disconnects from the broker, and we’re not able to publish the readings, call the restart_and_reconnect() function to reset the ESP board and try to reconnect to the broker.

except OSError as e:
  restart_and_reconnect()

After uploading the code, you should get new sensor readings on the shell every 5 seconds.

Now, go to the next section to prepare Node-RED to receive the readings the ESP is publishing.

Preparing Node-RED Dashboard

The ESP32 or ESP8266 is publishing temperature readings every 10 seconds on the esp/bme280/temperature, esp/bme280/humidity and esp/bme280/pressure topics. Now, you can use any dashboard that supports MQTT or any other device that supports MQTT to subscribe to those topics and receive the readings.

As an example, we’ll create a simple flow using Node-RED to subscribe to those topics and display the readings on gauges.

If you don’t have Node-RED installed, follow the next tutorials:

Having Node-RED running on your Raspberry Pi, go to your Raspberry Pi IP address followed by :1880.

http://raspberry-pi-ip-address:1880

The Node-RED interface should open. Drag three MQTT in nodes, and three gauge nodes to the flow.

Node-RED Drag 6 Nodes MQTT In nodes and Gauges

Click the MQTT node and edit its properties.

MQTT In Node ESP32 ESP8266 Publish Temperature Humidity Pressure Node-RED Flow

The Server field refers to the MQTT broker. In our case, the MQTT broker is the Raspberry Pi, so it is set to localhost:1883. If you’re using a Cloud MQTT broker, you should change that field.

Insert the topic you want to be subscribed to and the QoS. This previous MQTT node is subscribed to the esp/bme280/temperature topic.

Click on the other MQTT in nodes and edit its properties with the same server, but for the other topics: esp/bme280/humidity and esp/bme280/pressure.

Click on the gauge nodes and edit its properties for each reading. The following node is set for the temperature readings. Edit the other chart node for the humidity readings.

ESP32 Gauge Temperature Node-RED Flow

Wire your nodes as shown below:

ESP32 MQTT Publish BME280 Temperature Humidity Pressure Node-RED Flow

Finally, deploy your flow (press the button on the upper right corner).

Deploy Node-RED button

Alternatively, you can go to Menu > Import and copy the following to your Clipboard to create your Node-RED flow.

[{"id":"5a45b8da.52b0d8","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/temperature","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":310,"y":60,"wires":[["3042e15e.80a4ee"]]},{"id":"3042e15e.80a4ee","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":2,"width":0,"height":0,"gtype":"gage","title":"Temperature","label":"ºC","format":"{{value}}","min":0,"max":"40","colors":["#00b500","#f7df09","#ca3838"],"seg1":"","seg2":"","x":590,"y":60,"wires":[]},{"id":"8ff168f0.0c74a8","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/humidity","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":300,"y":140,"wires":[["29251f29.6687c"]]},{"id":"29251f29.6687c","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":2,"width":0,"height":0,"gtype":"gage","title":"Humidity","label":"%","format":"{{value}}","min":"30","max":"100","colors":["#53a4e6","#1d78a9","#4e38c9"],"seg1":"","seg2":"","x":580,"y":140,"wires":[]},{"id":"294f7eea.999d72","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/pressure","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":300,"y":220,"wires":[["58610d70.bb9764"]]},{"id":"58610d70.bb9764","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":4,"width":0,"height":0,"gtype":"gage","title":"Pressure","label":"hPa","format":"{{value}}","min":0,"max":"1200","colors":["#b366ff","#8000ff","#440088"],"seg1":"","seg2":"","x":580,"y":220,"wires":[]},{"id":"8db3fac0.99dd48","type":"mqtt-broker","z":"","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"37de8fe8.46846","type":"ui_group","z":"","name":"BME280","tab":"53b8c8f9.cfbe48","order":1,"disp":true,"width":"6","collapse":false},{"id":"53b8c8f9.cfbe48","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":2,"disabled":false,"hidden":false}]

View raw code

Demonstration

Go to your Raspberry Pi IP address followed by :1880/ui.

http://raspberry-pi-ip-address:1880/ui

You should get access to the current BME280 temperature, humidity and pressure readings on the Dashboard. You can use other dashboard-type nodes to display the readings on different ways.

ESP32 ESP8266 MQTT Publish Temperature Humidity Pressure BME280 Node-RED Dashboard

That’s it! You have your ESP32 or ESP8266 boards publishing BME280 temperature, humidity and pressure readings to Node-RED via MQTT using MicroPython.

Wrapping Up

MQTT is a great communication protocol to exchange small amounts of data between IoT devices. In this tutorial you’ve learned how to publish readings from a BME280 sensor with the ESP32 and ESP8266 using MicroPython to different MQTT topics. Then, you can use any device or home automation platform to subscribe to those topics and receive the readings.

Instead of a BME280 sensor, you can use any other sensor like DHT11 or DHT22 sensor or DS18B20 temperature sensor.

We have other projects/tutorials related with the BME280 sensor that you may also like:

Learn more about MicroPython with our eBook: MicroPython Programming using ESP32/ESP8266.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

5 thoughts on “MicroPython: MQTT – Publish BME280 Sensor Readings (ESP32/ESP8266)”

  1. I have attempted this with both an ESP32 and ESP8266. In both cases, initialization of the bme280 device on i2c fails with a timeout, and a warning that I2C is deprecated, use SoftI2C instead.
    rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    mode:DIO, clock div:2
    load:0x3fff0030,len:5656
    load:0x40078000,len:12696
    load:0x40080400,len:4292
    entry 0x400806b0
    Connection successful
    Traceback (most recent call last):
    File “main.py”, line 46, in
    File “BME280.py”, line 153, in init
    File “BME280.py”, line 159, in _load_calibration
    File “BME280.py”, line 119, in readU16LE
    File “BME280.py”, line 102, in readU16
    OSError: [Errno 116] ETIMEDOUT
    MicroPython v1.17 on 2021-09-02; ESP32 module with ESP32

    Any idea of how to solve this particular error. Thanks,
    a loyal follower
    NK

    Reply
    • Hi.
      Try initializing the sensor with SoftI2C
      i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=10000)

      instead of I2C:
      i2c = I2C(scl=Pin(22), sda=Pin(21), freq=10000)

      Let me know if this solves the issue.

      Regards,
      Sara

      Reply
  2. It works great on a ESP32 – however when I tried it on a D1mini 8266 – got out of memory errors… Any way around this – what I would like to do in >>> is to query the amount of memory/flash available

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.