MicroPython: BME680 with ESP32 and ESP8266 (Temperature, Humidity, Pressure, Gas)

In this guide you’ll learn how to use the BME680 sensor module with the ESP32 and ESP8266 to get temperature, humidity, pressure, and gas (air quality) readings using MicroPython firmware. We’ll build a simple example to get you familiar with the sensor and a web server to display your sensor readings.

MicroPython BME680 with ESP32 and ESP8266 Temperature Humidity Pressure Gas Air Quality

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:

Learn more about MicroPython: MicroPython Programming with ESP32 and ESP8266 eBook.

You might also like reading other BME680 guides:

Introducing BME680 Environmental Sensor Module

The BME680 is an environmental sensor that combines gas, pressure, humidity and temperature sensors. The gas sensor can detect a broad range of gases like volatile organic compounds (VOC). For this reason, the BME680 can be used in indoor air quality control.

BME680 Gas sensor humidity barometric pressure ambient temperature gas air quality front

BME680 Measurements

The BME680 is a 4-in-1 digital sensor that measures:

  • Temperature
  • Humidity
  • Barometric pressure
  • Gas: Volatile Organic Compounds (VOC) like ethanol and carbon monoxide

Gas Sensor

The BME680 contains a MOX (Metal-oxide) sensor that detects VOCs in the air. This sensor gives you a qualitative idea of the sum of VOCs/contaminants in the surrounding air – it is not specific for a specific gas molecule.

MOX sensors are composed of a metal-oxide surface, a sensing chip to measure changes in conductivity, and a heater. It detects VOCs by adsorption of oxygen molecules on its sensitive layer. The BME680 reacts to most VOCs polluting indoor air (except CO2).

When the sensor comes into contact with the reducing gases, the oxygen molecules react and increase the conductivity across the surface. As a raw signal, the BME680 outputs resistance values. These values change due to variations in VOC concentrations:

BME680 Gas Environmental Air Quality Sensor Resistance How It Works
  • Higher concentration of VOCs » Lower resistance
  • Lower concentration of VOCs » Higher resistance

The reactions that occur on the sensor surface (thus, the resistance) are influenced by parameters other than VOC concentration like temperature and humidity.

Relevant Information Regarding Gas Sensor

The gas sensor gives you a qualitative idea of VOCs gasses in the surrounding air. So, you can get trends, compare your results and see if the air quality is increasing or decreasing. To get precise measurements, you need to calibrate the sensor against knows sources and build a calibration curve.

When you first get the sensor, it is recommended to run it for 48 hours before start collecting “real” data. After that, it is also recommend to run the sensor for 30 minutes before getting a gas reading.

BME680 Accuracy

Here’s the accuracy of the temperature, humidity and pressure sensors of the BME680:

SensorAccuracy
Temperature+/- 1.0ºC
Humidity+/- 3%
Pressure+/- 1 hPa

BME680 Operation Range

The following table shows the operation range for the temperature, humidity and pressure sensors for the BME680.

SensorOperation Range
Temperature-40 to 85 ºC
Humidity0 to 100 %
Pressure300 to 1100 hPa

BME680 Pinout

Here’s the BME680 Pinout:

VCCPowers the sensor
GNDCommon GND
SCLSCL pin for I2C communication
SCK pin for SPI communication
SDASDA pin for I2C communication
SDI (MOSI) pin for SPI communication
SDOSDO (MISO) pin for SPI communication
CSChip select pin for SPI communication

BME680 Interface

The BME680 supports I2C and SPI Interfaces.

BME680 Gas sensor humidity barometric pressure ambient temperature gas air quality back

BME680 I2C

This sensor communicates using I2C communication protocol, so the wiring is very simple. You can use the default ESP32 or ESP8266 I2C pins as shown in the following table:

BME680ESP32ESP8266
Vin3.3V3.3V
GNDGNDGND
SCLGPIO 22GPIO 5 (D1)
SDAGPIO 21GPIO 4 (D2)

Parts Required

ESP32 Board BME680 Gas sensor circuit wiring diagram schematics

For this project you need to wire the BME680 sensor module to the ESP32 or ESP8266 I2C pins. Here’s a list of parts you need for this tutorial:

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!

Schematic – ESP32

Follow the next schematic diagram if you’re using an ESP32 board:

ESP32 BME680 Wiring Diagram I2C

Recommended reading: ESP32 Pinout Reference Guide

Schematic – ESP8266

Follow the next schematic diagram if you’re using an ESP8266 board:

ESP8266 NodeMCU BME680 Environmental Sensor Wiring Diagram I2C

Recommended reading: ESP8266 Pinout Reference Guide

BME680 MicroPython Library

The library to read from the BME680 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 bme680.py).

# Spaces, comments and some functions have been removed from the original file to save memory
# Original source: https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/master/adafruit_bme680.py
import time
import math
from micropython import const
from ubinascii import hexlify as hex
try:
  import struct
except ImportError:
  import ustruct as struct
_BME680_CHIPID = const(0x61)
_BME680_REG_CHIPID = const(0xD0)
_BME680_BME680_COEFF_ADDR1 = const(0x89)
_BME680_BME680_COEFF_ADDR2 = const(0xE1)
_BME680_BME680_RES_HEAT_0 = const(0x5A)
_BME680_BME680_GAS_WAIT_0 = const(0x64)
_BME680_REG_SOFTRESET = const(0xE0)
_BME680_REG_CTRL_GAS = const(0x71)
_BME680_REG_CTRL_HUM = const(0x72)
_BME280_REG_STATUS = const(0xF3)
_BME680_REG_CTRL_MEAS = const(0x74)
_BME680_REG_CONFIG = const(0x75)
_BME680_REG_PAGE_SELECT = const(0x73)
_BME680_REG_MEAS_STATUS = const(0x1D)
_BME680_REG_PDATA = const(0x1F)
_BME680_REG_TDATA = const(0x22)
_BME680_REG_HDATA = const(0x25)
_BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16)
_BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127)
_BME680_RUNGAS = const(0x10)
_LOOKUP_TABLE_1 = (2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0,
  2126008810.0, 2147483647.0, 2130303777.0, 2147483647.0, 2147483647.0,
  2143188679.0, 2136746228.0, 2147483647.0, 2126008810.0, 2147483647.0,
  2147483647.0)
_LOOKUP_TABLE_2 = (4096000000.0, 2048000000.0, 1024000000.0, 512000000.0, 255744255.0, 127110228.0,
  64000000.0, 32258064.0, 16016016.0, 8000000.0, 4000000.0, 2000000.0, 1000000.0,
  500000.0, 250000.0, 125000.0)
def _read24(arr):
  ret = 0.0
  for b in arr:
    ret *= 256.0
    ret += float(b & 0xFF)
  return ret
class Adafruit_BME680:
  def __init__(self, *, refresh_rate=10):
    self._write(_BME680_REG_SOFTRESET, [0xB6])
    time.sleep(0.005)
    chip_id = self._read_byte(_BME680_REG_CHIPID)
    if chip_id != _BME680_CHIPID:
      raise RuntimeError('Failed 0x%x' % chip_id)
    self._read_calibration()
    self._write(_BME680_BME680_RES_HEAT_0, [0x73])
    self._write(_BME680_BME680_GAS_WAIT_0, [0x65])
    self.sea_level_pressure = 1013.25
    self._pressure_oversample = 0b011
    self._temp_oversample = 0b100
    self._humidity_oversample = 0b010
    self._filter = 0b010
    self._adc_pres = None
    self._adc_temp = None
    self._adc_hum = None
    self._adc_gas = None
    self._gas_range = None
    self._t_fine = None
    self._last_reading = 0
    self._min_refresh_time = 1000 / refresh_rate
  @property
  def pressure_oversample(self):
    return _BME680_SAMPLERATES[self._pressure_oversample]
  @pressure_oversample.setter
  def pressure_oversample(self, sample_rate):
    if sample_rate in _BME680_SAMPLERATES:
      self._pressure_oversample = _BME680_SAMPLERATES.index(sample_rate)
    else:
      raise RuntimeError("Invalid")
  @property
  def humidity_oversample(self):
    return _BME680_SAMPLERATES[self._humidity_oversample]
  @humidity_oversample.setter
  def humidity_oversample(self, sample_rate):
    if sample_rate in _BME680_SAMPLERATES:
      self._humidity_oversample = _BME680_SAMPLERATES.index(sample_rate)
    else:
      raise RuntimeError("Invalid")
  @property
  def temperature_oversample(self):
      return _BME680_SAMPLERATES[self._temp_oversample]
  @temperature_oversample.setter
  def temperature_oversample(self, sample_rate):
    if sample_rate in _BME680_SAMPLERATES:
      self._temp_oversample = _BME680_SAMPLERATES.index(sample_rate)
    else:
      raise RuntimeError("Invalid")
  @property
  def filter_size(self):
    return _BME680_FILTERSIZES[self._filter]
  @filter_size.setter
  def filter_size(self, size):
    if size in _BME680_FILTERSIZES:
      self._filter = _BME680_FILTERSIZES[size]
    else:
      raise RuntimeError("Invalid")
  @property
  def temperature(self):
    self._perform_reading()
    calc_temp = (((self._t_fine * 5) + 128) / 256)
    return calc_temp / 100
  @property
  def pressure(self):
    self._perform_reading()
    var1 = (self._t_fine / 2) - 64000
    var2 = ((var1 / 4) * (var1 / 4)) / 2048
    var2 = (var2 * self._pressure_calibration[5]) / 4
    var2 = var2 + (var1 * self._pressure_calibration[4] * 2)
    var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536)
    var1 = (((((var1 / 4) * (var1 / 4)) / 8192) *
      (self._pressure_calibration[2] * 32) / 8) +
      ((self._pressure_calibration[1] * var1) / 2))
    var1 = var1 / 262144
    var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768
    calc_pres = 1048576 - self._adc_pres
    calc_pres = (calc_pres - (var2 / 4096)) * 3125
    calc_pres = (calc_pres / var1) * 2
    var1 = (self._pressure_calibration[8] * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096
    var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192
    var3 = (((calc_pres / 256) ** 3) * self._pressure_calibration[9]) / 131072
    calc_pres += ((var1 + var2 + var3 + (self._pressure_calibration[6] * 128)) / 16)
    return calc_pres/100
  @property
  def humidity(self):
    self._perform_reading()
    temp_scaled = ((self._t_fine * 5) + 128) / 256
    var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) -
      ((temp_scaled * self._humidity_calibration[2]) / 200))
    var2 = (self._humidity_calibration[1] *
      (((temp_scaled * self._humidity_calibration[3]) / 100) +
       (((temp_scaled * ((temp_scaled * self._humidity_calibration[4]) / 100)) /
         64) / 100) + 16384)) / 1024
    var3 = var1 * var2
    var4 = self._humidity_calibration[5] * 128
    var4 = (var4 + ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16
    var5 = ((var3 / 16384) * (var3 / 16384)) / 1024
    var6 = (var4 * var5) / 2
    calc_hum = (((var3 + var6) / 1024) * 1000) / 4096
    calc_hum /= 1000
    if calc_hum > 100:
      calc_hum = 100
    if calc_hum < 0:
      calc_hum = 0
    return calc_hum
  @property
  def altitude(self):
    pressure = self.pressure
    return 44330 * (1.0 - math.pow(pressure / self.sea_level_pressure, 0.1903))
  @property
  def gas(self):
    self._perform_reading()
    var1 = ((1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._gas_range])) / 65536
    var2 = ((self._adc_gas * 32768) - 16777216) + var1
    var3 = (_LOOKUP_TABLE_2[self._gas_range] * var1) / 512
    calc_gas_res = (var3 + (var2 / 2)) / var2
    return int(calc_gas_res)
  def _perform_reading(self):
    if (time.ticks_diff(self._last_reading, time.ticks_ms()) * time.ticks_diff(0, 1)
        < self._min_refresh_time):
      return
    self._write(_BME680_REG_CONFIG, [self._filter << 2])
    self._write(_BME680_REG_CTRL_MEAS,
      [(self._temp_oversample << 5)|(self._pressure_oversample << 2)])
    self._write(_BME680_REG_CTRL_HUM, [self._humidity_oversample])
    self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS])
    ctrl = self._read_byte(_BME680_REG_CTRL_MEAS)
    ctrl = (ctrl & 0xFC) | 0x01
    self._write(_BME680_REG_CTRL_MEAS, [ctrl])
    new_data = False
    while not new_data:
      data = self._read(_BME680_REG_MEAS_STATUS, 15)
      new_data = data[0] & 0x80 != 0
      time.sleep(0.005)
    self._last_reading = time.ticks_ms()
    self._adc_pres = _read24(data[2:5]) / 16
    self._adc_temp = _read24(data[5:8]) / 16
    self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0]
    self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64)
    self._gas_range = data[14] & 0x0F
    var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2)
    var2 = (var1 * self._temp_calibration[1]) / 2048
    var3 = ((var1 / 2) * (var1 / 2)) / 4096
    var3 = (var3 * self._temp_calibration[2] * 16) / 16384
    self._t_fine = int(var2 + var3)
  def _read_calibration(self):
    coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25)
    coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16)
    coeff = list(struct.unpack('<hbBHhbBhhbbHhhBBBHbbbBbHhbb', bytes(coeff[1:39])))
    coeff = [float(i) for i in coeff]
    self._temp_calibration = [coeff[x] for x in [23, 0, 1]]
    self._pressure_calibration = [coeff[x] for x in [3, 4, 5, 7, 8, 10, 9, 12, 13, 14]]
    self._humidity_calibration = [coeff[x] for x in [17, 16, 18, 19, 20, 21, 22]]
    self._gas_calibration = [coeff[x] for x in [25, 24, 26]]
    self._humidity_calibration[1] *= 16
    self._humidity_calibration[1] += self._humidity_calibration[0] % 16
    self._humidity_calibration[0] /= 16
    self._heat_range = (self._read_byte(0x02) & 0x30) / 16
    self._heat_val = self._read_byte(0x00)
    self._sw_err = (self._read_byte(0x04) & 0xF0) / 16
  def _read_byte(self, register):
    return self._read(register, 1)[0]
  def _read(self, register, length):
    raise NotImplementedError()
  def _write(self, register, values):
    raise NotImplementedError()
class BME680_I2C(Adafruit_BME680):
  def __init__(self, i2c, address=0x77, debug=False, *, refresh_rate=10):
    self._i2c = i2c
    self._address = address
    self._debug = debug
    super().__init__(refresh_rate=refresh_rate)
  def _read(self, register, length):
    result = bytearray(length)
    self._i2c.readfrom_mem_into(self._address, register & 0xff, result)
    if self._debug:
      print("\t${:x} read ".format(register), " ".join(["{:02x}".format(i) for i in result]))
    return result
  def _write(self, register, values):
    if self._debug:
      print("\t${:x} write".format(register), " ".join(["{:02x}".format(i) for i in values]))
    for value in values:
      self._i2c.writeto_mem(self._address, register, bytearray([value & 0xFF]))
      register += 1

View raw code

Follow the next set of instructions for the IDE you’re using:

  • A. Upload BME680 library with uPyCraft IDE
  • B. Upload BME680 library with Thonny IDE

A. Upload BME680 library with uPyCraft IDE

This section shows how to upload a library using uPyCraft IDE. If you’re using Thonny IDE, read the next section.

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

2. Copy the BME680 library code into that file. The BME680 library code can be found here.

3. After copying the code, save the file by pressing the Save button (2).

Install BME280 library MicroPython ESP32 ESP8266 uPyCraft IDE step 1

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

bme680.py new MicroPython file

5. Click the Download and Run button.

Install BME280 library MicroPython ESP32 ESP8266 uPyCraft IDE step 3

The file should be saved on the device folder with the name “bme680.py” as highlighted in the following figure.

uPyCraft IDE Save library file to device ESP32 ESP8266

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

B. Upload BME680 library with Thonny IDE

If you’re using Thonny IDE, follow the next steps:

1. Copy the library code to a new file. The BME680 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 bme680.py and press the OK button:

Thonny IDE ESP32 ESP8266 MicroPython Save file library to device name file

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:

Thonny IDE ESP32 ESP8266 MicroPython Save file library to device saved

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

Code – BME680 Temperature, Humidity, Pressure, and Gas Air Quality

After uploading the library to the ESP32 or ESP8266, copy the following code to the main.py or boot.py file. It simply prints the temperature, humidity, pressure, and gas resistance into the shell every 5 seconds.

# Complete project details at https://RandomNerdTutorials.com/micropython-bme680-esp32-esp8266/

from machine import Pin, I2C
from time import sleep
from bme680 import *

# ESP32 - Pin assignment
i2c = I2C(scl=Pin(22), sda=Pin(21))
# ESP8266 - Pin assignment
#i2c = I2C(scl=Pin(5), sda=Pin(4))

bme = BME680_I2C(i2c=i2c)

while True:
  try:
    temp = str(round(bme.temperature, 2)) + ' C'
    #temp = (bme.temperature) * (9/5) + 32
    #temp = str(round(temp, 2)) + 'F'
    
    hum = str(round(bme.humidity, 2)) + ' %'
    
    pres = str(round(bme.pressure, 2)) + ' hPa'
    
    gas = str(round(bme.gas/1000, 2)) + ' KOhms'

    print('Temperature:', temp)
    print('Humidity:', hum)
    print('Pressure:', pres)
    print('Gas:', gas)
    print('-------')
  except OSError as e:
    print('Failed to read sensor.')
 
  sleep(5)

View raw code

How the Code Works

First, you need to import the necessary libraries, including the BME680 module you’ve imported previously.

from machine import Pin, I2C
from time import sleep
from bme680 import *

Set the I2C pins. In this case, we’re using the default I2C pins. If you’re using the ESP32, set the pins as follows:

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

If you’re using the ESP8266, comment the previous line and uncomment the following so that you have:

i2c = I2C(scl=Pin(5), sda=Pin(4))

In the while loop, create a BME680 object called bme with the I2C pins defined earlier:

bme = BME680_I2C(i2c=i2c)

Reading temperature, humidity, pressure, and gas resistance is as simple as using the temperature, humidity, pressure, and gas methods on the bme object.

temp = str(round(bme.temperature, 2)) + ' C'
#temp = (bme.temperature) * (9/5) + 32
#temp = str(round(temp, 2)) + 'F'
    
hum = str(round(bme.humidity, 2)) + ' %'
    
pres = str(round(bme.pressure, 2)) + ' hPa'
    
gas = str(round(bme.gas/1000, 2)) + ' KOhms'

Finally, print the readings on the shell:

print('Temperature: ', temp)
print('Humidity: ', hum)
print('Pressure: ', pres)
print('Gas:', gas)

In the end, we add a delay of 5 seconds:

sleep(5)

Demonstration

After uploading the code to your board, press the RST button to run the code. New BME680 sensor readings should be displayed every 5 seconds.

BME680 Sensor Read Temperature, Humidity, Pressure, and Gas Air Quality

Display BME680 Readings on Web Server

Now that you know how to get temperature, humidity, pressure and gas from the BME680 sensor, we’ll display the sensor readings on a web server that you can access on your local network.

ESP32 ESP8266 MicroPython BME680 Web Server Test mobile responsive

For this example, you need three files:

  • bme680.py: this is the file that contains all the methods to use the BME680 sensor. That’s the file you’ve uploaded previously.
  • boot.py: runs when the device starts and sets up several configuration options like your network credentials, importing libraries, setting the pins, etc.
  • main.py: this is the main script where we’ll handle the web server. It executes immediately after the boot.py.

Note: It is a good practice to include the boot.py and main.py files. However, if you prefer, you can include all the code in the main.py file.

boot.py

Create a new file in your IDE called boot.py and copy the following code.

# Complete project details at https://RandomNerdTutorials.com/micropython-bme680-esp32-esp8266/

try:
  import usocket as socket
except:
  import socket
  
from time import sleep

from machine import Pin, I2C
import network

import esp
esp.osdebug(None)

import gc
gc.collect()

from bme680 import *

# ESP32 - Pin assignment
i2c = I2C(scl=Pin(22), sda=Pin(21))
# ESP8266 - Pin assignment
#i2c = I2C(scl=Pin(5), sda=Pin(4))

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'

station = network.WLAN(network.STA_IF)

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

while station.isconnected() == False:
  pass

print('Connection successful')
print(station.ifconfig())

View raw code

This file imports the necessary libraries, defines the I2C pins to connect to the sensor and connects to your network.

In the code, we’re using the ESP32 I2C pins:

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

If you’re using the ESP8266, comment the previous line and uncomment the following:

i2c = I2C(scl=Pin(5), sda=Pin(4))

Then, insert your network credentials in the following variables:

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'

main.py

In the main.py file is where we’ll create the web server and handle the requests. Copy the following code to your main.py file.

# Complete project details at https://RandomNerdTutorials.com/micropython-bme680-esp32-esp8266/

def web_page():
  bme = BME680_I2C(i2c=i2c)
  
  html = """<html><head><title>ESP with BME680</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,"><style>body { text-align: center; font-family: "Trebuchet MS", Arial;}
  table { border-collapse: collapse; margin-left:auto; margin-right:auto; }
  th { padding: 12px; background-color: #0043af; color: white; }
  tr { border: 1px solid #ddd; padding: 12px; }
  tr:hover { background-color: #bcbcbc; }
  td { border: none; padding: 12px; }
  .sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px;
  </style></head><body><h1>ESP with BME680</h1>
  <table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>
  <tr><td>Temp. Celsius</td><td><span class="sensor">""" + str(round(bme.temperature, 2)) + """ C</span></td></tr>
  <tr><td>Temp. Fahrenheit</td><td><span class="sensor">""" + str(round((bme.temperature) * (9/5) + 32, 2))  + """ F</span></td></tr>
  <tr><td>Pressure</td><td><span class="sensor">""" + str(round(bme.pressure, 2)) + """ hPa</span></td></tr>
  <tr><td>Humidity</td><td><span class="sensor">""" + str(round(bme.humidity, 2)) + """ %</span></td></tr>
  <tr><td>Gas</td><td><span class="sensor">""" + str(round(bme.gas/1000, 2)) + """ KOhms</span></td></tr></body></html>"""
  return html

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)

while True:
  try:
    if gc.mem_free() < 102000:
      gc.collect()
    conn, addr = s.accept()
    conn.settimeout(3.0)
    print('Got a connection from %s' % str(addr))
    request = conn.recv(1024)
    conn.settimeout(None)
    request = str(request)
    print('Content = %s' % request)
    response = web_page()
    conn.send('HTTP/1.1 200 OK\n')
    conn.send('Content-Type: text/html\n')
    conn.send('Connection: close\n\n')
    conn.sendall(response)
    conn.close()
  except OSError as e:
    conn.close()
    print('Connection closed')

View raw code

This code creates a socket server that sends an HTML page with the latest sensor readings when it receives a request on the ESP32 or ESP8266 IP address.

Basically, we have a function called web_page() that returns the HTML to build up the web page with the latest sensor readings. This HMTL text builds a table to display the readings:

def web_page():
  bme = BME680_I2C(i2c=i2c)
  
  html = """<html><head><title>ESP with BME680</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,"><style>body { text-align: center; font-family: "Trebuchet MS", Arial;}
  table { border-collapse: collapse; margin-left:auto; margin-right:auto; }
  th { padding: 12px; background-color: #0043af; color: white; }
  tr { border: 1px solid #ddd; padding: 12px; }
  tr:hover { background-color: #bcbcbc; }
  td { border: none; padding: 12px; }
  .sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px;
  </style></head><body><h1>ESP with BME680</h1>
  <table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>
  <tr><td>Temp. Celsius</td><td><span class="sensor">""" + str(round(bme.temperature, 2)) + """ C</span></td></tr>
  <tr><td>Temp. Fahrenheit</td><td><span class="sensor">""" + str(round((bme.temperature) * (9/5) + 32, 2))  + """ F</span></td></tr>
  <tr><td>Pressure</td><td><span class="sensor">""" + str(round(bme.pressure, 2)) + """ hPa</span></td></tr>
  <tr><td>Humidity</td><td><span class="sensor">""" + str(round(bme.humidity, 2)) + """ %</span></td></tr>
  <tr><td>Gas</td><td><span class="sensor">""" + str(round(bme.gas/1000, 2)) + """ KOhms</span></td></tr></body></html>"""
  return html

Then, we create a socket server that sends the HTML when it gets a request. The HTML text is then saved on the response variable:

response = web_page()

And sent to the client:

conn.sendall(response)

We’ve explained in great detail how these kind of web servers work in previous tutorials. So, if you want to learn how it works, you can read the following articles:

Web Server Demonstration

Upload all the previous files to your ESP32 or ESP8266 board in the following order:

  1. bme680.py
  2. boot.py
  3. main.py

If you don’t know how to upload code, you can read our getting started guides with uPyCraft IDE, or Thonny IDE:

After uploading the code, your ESP32 or ESP8266 IP address should be displayed on the Serial Monitor.

ESP32 ESP8266 MicroPython IP Address

Open a web browser in your local network and type your ESP IP address (in our example the IP is http://192.168.1.114). You should get a page with the latest sensor readings as shown in the following figure.

ESP32 ESP8266 MicroPython BME680 Web Server Test Demonstration

Auto refresh web page

With the web server script provided in this project, you need to refresh the web page to see the latest readings. If you add the next meta tag inside the HTML <head></head> tags, your web page will auto refresh every 10 seconds:

<meta http-equiv="refresh" content="10">

Wrapping Up

We hope you’ve found this tutorial useful. We have other projects and tutorials with MicroPython that you may like:

If you want to learn more about programming the ESP32 and ESP8266 boards with MicroPython, get access to our eBook: MicroPython Programming with ESP32 and ESP8266.

Thanks for reading.



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!

16 thoughts on “MicroPython: BME680 with ESP32 and ESP8266 (Temperature, Humidity, Pressure, Gas)”

      • What I mean is that it does not work as a stand alone with external power supply.

        My aim is to use it in a project using a separate power supply such as battery. However, it does not allow me do that. The script only runs when the board is not disconnected from the IDE.

        Thanks

        Reply
    • Make sure you upload the code to your board. You’re probably just “running” the code.
      After uploading make sure you press the RST button to restart the board.
      Regards,
      Sara

      Reply
  1. I remember that in the previous version of the BME sensor, the pressure reading changes when the temperature changes, even for a constant pressure. I don’t know if it is taken into account in the library.

    Reply
  2. Can you explain to me, how I can convert the gas-data from kOhm to PPM.
    I know that the air is bad, when CO² is bigger then 1.000 PPM.

    400 PPM is fresh air
    1.000 PPM is bad air
    2.000 PPM means death

    Reply
    • BME680 is not CO2 sensor.
      It meassure VOC, and from VOC it calculate relative CO2, it is mainly used in controled envierment.
      For example if you use it in your room, after 48h burnin process, it will remember average VOC and convert it to 400 ppm CO2.
      Basicly, you will need to burn it outside compare your data with some pollution sensor close to you, calculate average per hour and then put those numbers into program and get VOC polution in your space.
      Try put it in room where you stock your house chemichals, cleaning stuff etc, you will see jump in results, that will prove that sensor works.
      If you want to check CO2 you need dedicated sensor for that, BME680 is not so good for that.
      Sorry for spelling.

      Reply
  3. Hello iI try your code but I has a problem
    exec(open(‘./testbme680.py’).read(),globals())
    Traceback (most recent call last):
    File “”, line 1, in
    File “”, line 35, in
    here >> bme = BME680_I2C(i2c=i2c)
    File “bme680.py”, line 217, in init
    File “bme680.py”, line 46, in init
    File “bme680.py”, line 228, in _write
    OSError: [Errno 19] ENODEV
    I use this bme680
    GY-MCU680V1
    https://wiki.liutyi.info/display/ARDUINO/BME680
    Do you ave an idea *???
    Thank you !

    Reply
    • I have the same exact issue with CJMCU-680. At first I had a problem:
      “NameError: name ‘BME680_I2C’ isn’t defined”
      Then I BurnFirmware and now I have a problem like Richard. And usual memory allocating problem on second run and above. It just won’t work.

      Reply
  4. I’m still running the BME680 with Arduino C++ as I’m using the Bosch BSEC library to get air quality (IAQ), VOC and CO2 (github.com/BoschSensortec/BSEC-Arduino-library). The BSEC library calculates this values by using gas resistance, humidity and temperature (as shown above).

    Is there any chance to use this C++ library with MicroPython, i.e. call the library functions and get their values? The used calculation seems to be closed source.

    Reply
  5. This project worked pretty well. However, I do have one problem. If my WiFi network goes away (which it frequently does) the connection is lost and the main.py will not recover. A brief Google on “MicroPython Auto-reconnect” did not reveal a solution. I did see that other people have the same problem with there code.

    Is there a way to add an auto-reconnect function to MicroPython?

    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.