ESP32 ESP-IDF MQTT Publish BME680 Sensor Readings

In this tutorial, we will learn to publish BME680 sensor readings with ESP32 MQTT using ESP-IDF. The ESP32 will act as an MQTT client publisher and it will publish sensor readings constituting temperature, pressure, humidity, and gas on different topics. Any MQTT client can receive these sensor values by subscribing to these topics.

  • esp32/bme680/temperature
  • esp32/bme680/humidity
  • esp32/bme680/pressure
  • esp32/bme680/gas

If you are using multiple sensors, you can also create JSON string and publish sensor values under one topic.

We will use Mosquitto MQTT Broker on Raspberry Pi but you can install it on your Windows and Linux machine also if you are using them instead. The publisher and subscriber will make connections with the MQTT broker installed on Raspberry Pi. After that, the ESP32 will publish the sensor readings on the specified topics after every few seconds.

Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

BME680 Introduction

BME680 is a four in one low power-driven sensor, which has integrated temperature, pressure, humidity, and gas sensors. It runs on an operating voltage of 1.8-3.8V and communicates with other microcontrollers through I2C and SPI protocol. This sensor is used in areas such as tracking the quality of air, humidity indicators, weather trends, home automation, and controlling GPS enhancements.

Pinout Diagram

The pinout diagram of BME680 sensor is shown below:

BME680 pinout diagram
Pin NameDescription
VCCPin to power up sensor
GNDGND pin for power supply
SCLBME680 provides 2 interfaces such as SPI and I2C to get sensor readings. This pin is serial clock signal for both I2C and SPI interfaces.
SDAWith I2C protocol this pin acts as SDA or Serial Data pin and when using SPI protocol this pin acts as SDI or Serial Data In also known as MOSI (‘Master out Slave in’).
SDOThis is the SDO (Slave data out) /MISO (Master in slave out) pin for SPI communication. This pin is used for SPI interface only.
CSThis is the Chip Select pin used in SPI communication. Acts as an input to the sensor.

ESP32 I2C Pins

We will be using I2C interface to get BME680 sensor readings with ESP32 and ESP-IDF. Now let’s discuss I2C pins of ESP32. The diagram below shows the pinout for the ESP32, where the default I2C pins are highlighted in red. These are the default I2C pins of the ESP32 DEVKIT module. However, if you want to use other GPIO pins as I2C pins then you will have to set them in code.

ESP32 Pinout I2C Pins

Refer to the following article to know more about ESP32 GPIO pins:

You can check this complete guide on ESP32 I2C communication:

Interface BME680 with ESP32 ESP-IDF

BME680 sensor with ESP32 ESP-IDF

We will need the following components to connect ESP32 board with BME680.

  1. ESP32 board
  2. BME680 Sensor
  3. Connecting Wires
  4. Breadboard

The connection of BME680 with the ESP32 board is straightforward as it just involves the connection of the 4 pins (GND, VCC, SDA, and SCL) with ESP32. We have to connect the VCC terminal with 3.3V pin, ground with the ground (common ground), SCL of the sensor with SCL of ESP32 board, and SDA of the sensor with the SDA pin of the ESP32 board.

In ESP32, the default I2C pin for SDA is GPIO21 and for SCL is GPIO22.

The connections between the devices are specified in the table below:

BME680 SensorESP32
GNDGND
VCC3.3V
SDAGPIO21(I2C SDA)
SCLGPIO22 (I2C SCL)
interface BME680 with ESP32 using ESP-IDF

ESP32 MQTT Publish BME680 with ESP-IDF

We will build and create a project in VS Code with ESP-IDF extension to measure ambient temperature, barometric pressure, relative humidity, and gas (VOC) or Indoor air quality (IAQ) from BME60 environmental sensor and publish readings on MQTT topics.

This code takes BME680 sensor readings, make connection with MQTT broker and publish sensor readings on MQTT topics. We will use the BME680 ESP-IDF library provided by UncleRus on GitHub.

Create Example Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory, along with the ESP-IDF board and target. For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens the project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.

  • First, let’s add the necessary header files for the BME680 libraries required for this project. Create a new folder called ‘components’ and Now add the bme680, esp_idf_lib_helpers, and i2cdev folders in ‘components’ folder by copying from this link as listed below:
bme680 library esp-idf

Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the c

ESP32 ESP-IDF MQTT BME680 Code

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
// #include "protocol_examples_common.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"

#include "esp_log.h"
#include "mqtt_client.h"

#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_err.h"

#include <bme680.h>

static const char *TAG = "MQTT_EXAMPLE";
#define EXAMPLE_ESP_WIFI_SSID "HUAWEI-u67E"
#define EXAMPLE_ESP_WIFI_PASS "4uF77R2n"
#define MAX_RETRY 10
static int retry_cnt = 0;


#define MQTT_PUB_TEMP_BME680 "esp32/bme680/temperature"
#define MQTT_PUB_HUM_BME680 "esp32/bme680/humidity"
#define MQTT_PUB_PRES_BME680 "esp32/bme680/pressure"
#define MQTT_PUB_GAS_BME680 "esp32/bme680/gas"

#define BME680_I2C_ADDR 0x76
#define PORT 0
#define CONFIG_EXAMPLE_I2C_MASTER_SDA 21
#define CONFIG_EXAMPLE_I2C_MASTER_SCL 22

bme680_t sensor;
bme680_values_float_t values;
uint32_t duration;

uint32_t MQTT_CONNECTED = 0;

void init_bme680(void)
{
    memset(&sensor, 0, sizeof(bme680_t));

    ESP_ERROR_CHECK(bme680_init_desc(&sensor, BME680_I2C_ADDR, PORT, CONFIG_EXAMPLE_I2C_MASTER_SDA, CONFIG_EXAMPLE_I2C_MASTER_SCL));

    // init the sensor
    ESP_ERROR_CHECK(bme680_init_sensor(&sensor));

    // Changes the oversampling rates to 4x oversampling for temperature
    // and 2x oversampling for humidity. Pressure measurement is skipped.
    bme680_set_oversampling_rates(&sensor, BME680_OSR_4X, BME680_OSR_2X, BME680_OSR_2X);

    // Change the IIR filter size for temperature and pressure to 7.
    bme680_set_filter_size(&sensor, BME680_IIR_SIZE_7);

    // Change the heater profile 0 to 200 degree Celsius for 100 ms.
    bme680_set_heater_profile(&sensor, 0, 200, 100);
    bme680_use_heater_profile(&sensor, 0);

    // Set ambient temperature to 10 degree Celsius
    bme680_set_ambient_temperature(&sensor, 10);

    // as long as sensor configuration isn't changed, duration is constant
    bme680_get_measurement_duration(&sensor, &duration);
}
void get_bme680_readings(void)
{

    // trigger the sensor to start one TPHG measurement cycle
    if (bme680_force_measurement(&sensor) == ESP_OK)
    {
        // passive waiting until measurement results are available
        vTaskDelay(duration);

        // get the results and do something with them
        if (bme680_get_results_float(&sensor, &values) == ESP_OK)
            printf("BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f KOhm\n",
                   values.temperature, values.humidity, values.pressure, values.gas_resistance);
    }
}

static void mqtt_app_start(void);

static esp_err_t wifi_event_handler(void *arg, esp_event_base_t event_base,
                                    int32_t event_id, void *event_data)
{
    switch (event_id)
    {
    case WIFI_EVENT_STA_START:
        esp_wifi_connect();
        ESP_LOGI(TAG, "Trying to connect with Wi-Fi\n");
        break;

    case WIFI_EVENT_STA_CONNECTED:
        ESP_LOGI(TAG, "Wi-Fi connected\n");
        break;

    case IP_EVENT_STA_GOT_IP:
        ESP_LOGI(TAG, "got ip: startibg MQTT Client\n");
        mqtt_app_start();
        break;

    case WIFI_EVENT_STA_DISCONNECTED:
        ESP_LOGI(TAG, "disconnected: Retrying Wi-Fi\n");
        if (retry_cnt++ < MAX_RETRY)
        {
            esp_wifi_connect();
        }
        else
            ESP_LOGI(TAG, "Max Retry Failed: Wi-Fi Connection\n");
        break;

    default:
        break;
    }
    return ESP_OK;
}

void wifi_init(void)
{
    esp_event_loop_create_default();
    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    esp_netif_init();
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
    esp_wifi_start();
}

/*
 * @brief Event handler registered to receive MQTT events
 *
 *  This function is called by the MQTT client event loop.
 *
 * @param handler_args user data registered to the event.
 * @param base Event base for the handler(always MQTT Base in this example).
 * @param event_id The id for the received event.
 * @param event_data The data for the event, esp_mqtt_event_handle_t.
 */
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id)
    {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        MQTT_CONNECTED = 1;
        break;

    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        MQTT_CONNECTED = 0;
        break;

    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

esp_mqtt_client_handle_t client = NULL;

void Publisher_Task(void *params)
{
    char temperature[12];
    char pressure[12];
    char humidity[12]; 
    char gas_resistance[12];
    while (1)
    {
        

        get_bme680_readings();
        sprintf(temperature, "%.2f", values.temperature);
        sprintf(humidity, "%.2f", values.humidity);
        sprintf(pressure, "%.2f", values.pressure);
        sprintf(gas_resistance, "%.2f", values.gas_resistance);

        if (MQTT_CONNECTED)
        {
            esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME680, temperature, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME680, humidity, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME680, pressure, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_GAS_BME680, gas_resistance, 0, 0, 0);
        }
        else
        {
            ESP_LOGE(TAG, "MQTT Not connected");
        }
        vTaskDelay(10000 / portTICK_PERIOD_MS);
    }
}

static void mqtt_app_start(void)
{
    ESP_LOGI(TAG, "STARTING MQTT");
    esp_mqtt_client_config_t mqttConfig = {
        .uri = "mqtt://192.168.18.8:1883"};

    client = esp_mqtt_client_init(&mqttConfig);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
    esp_mqtt_client_start(client);
    ESP_ERROR_CHECK(i2cdev_init());
    init_bme680();
    xTaskCreate(Publisher_Task, "Publisher_Task", 1024 * 5, NULL, 5, NULL);
    ESP_LOGI(TAG, "MQTT Publisher_Task is up and running\n");
}

void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    wifi_init();
   
}  

How does the Code Works?

Firstly, we will start by including the necessary libraries that includes the i2c driver, gpio driver, BME680 library, FreeRTOS libraries to generate delays, esp_wifi.h to enable Wi-Fi connectivity, esp_event.h to monitor the Wi-Fi and MQTT events, mqtt_client.h to setup MQTT protocol for publish/subscribe and esp_log.h as the logging library. The driver/i2c.h library will enable the ESP32 to communicate with other I2C devices, in our case it is the BME680 sensor.

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
// #include "protocol_examples_common.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"

#include "esp_log.h"
#include "mqtt_client.h"

#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_err.h"

#include <bme680.h>

This code uses Informational Logging. The log function takes in two arguments. The first argument is the tag and the second argument is a formatted string. Therefore this global variable will be useful while calling the ESP_LOGI() functions. Here, “MQTT_EXAMPLE” is a tag that will be used while logging.

static const char *TAG = "MQTT_EXAMPLE";

Next, we have defined the parameters for the Wi-Fi SSID and password. Specify your own network credentials to successfully connect your ESP32 board with the Wi-Fi network.

#define  EXAMPLE_ESP_WIFI_SSID "YOUR_SSID"
#define  EXAMPLE_ESP_WIFI_PASS "YOUR_PASSWORD"

We will define some variables to monitor the Wi-Fi connection including the maximum retry value that is set as 10 and the retry count that initially holds the value 0.

#define MAX_RETRY 10
static int retry_cnt = 0;

After that we define the MQTT topics the ESP32 board will publish sensors data to:

#define MQTT_PUB_TEMP_BME680 "esp32/bme680/temperature"
#define MQTT_PUB_HUM_BME680 "esp32/bme680/humidity"
#define MQTT_PUB_PRES_BME680 "esp32/bme680/pressure"
#define MQTT_PUB_GAS_BME680 "esp32/bme680/gas"

Reading BME680 Sensor Readings

Then we have the definitions for the SCL and SDA pins that will be used for I2C communication between the ESP32 board and the BM6280 sensor. We are using the default I2C pins, however you can define other GPIO pins as SDA/SCL pins as well.

#define BME680_I2C_ADDR 0x77
#define PORT 0
#define CONFIG_EXAMPLE_I2C_MASTER_SDA 21
#define CONFIG_EXAMPLE_I2C_MASTER_SCL 22

Initialize the i2c library and it should be called before calling any bme680 esp-idf library function.

ESP_ERROR_CHECK(i2cdev_init());

BME680 Initialization Function

init_bme680() task initialize BME680 sensor, defines I2C port to which sensor is connected, sets sampling rates for temperature, humidity, pressure and gas readings, set filers size for temperature and pressure, sets the measurement duration, etc.

void init_bme680(void)
{
    memset(&sensor, 0, sizeof(bme680_t));

    ESP_ERROR_CHECK(bme680_init_desc(&sensor, BME680_I2C_ADDR, PORT, CONFIG_EXAMPLE_I2C_MASTER_SDA, CONFIG_EXAMPLE_I2C_MASTER_SCL));

    // init the sensor
    ESP_ERROR_CHECK(bme680_init_sensor(&sensor));

    // Changes the oversampling rates to 4x oversampling for temperature
    // and 2x oversampling for humidity. Pressure measurement is skipped.
    bme680_set_oversampling_rates(&sensor, BME680_OSR_4X, BME680_OSR_2X, BME680_OSR_2X);

    // Change the IIR filter size for temperature and pressure to 7.
    bme680_set_filter_size(&sensor, BME680_IIR_SIZE_7);

    // Change the heater profile 0 to 200 degree Celsius for 100 ms.
    bme680_set_heater_profile(&sensor, 0, 200, 100);
    bme680_use_heater_profile(&sensor, 0);

    // Set ambient temperature to 10 degree Celsius
    bme680_set_ambient_temperature(&sensor, 10);

    // as long as sensor configuration isn't changed, duration is constant
    bme680_get_measurement_duration(&sensor, &duration);

}

Now let’s see function of each line one by one.

Here we create an instance of bme680_t struct which is used to configure and initialize sensor. After that initialize all members of sensor struct to zero with memset.

bme680_t sensor;
memset(&sensor, 0, sizeof(bme680_t));
typedef struct
{
    i2c_dev_t i2c_dev;              //!< I2C device descriptor

    bool meas_started;              //!< Indicates whether measurement started
    uint8_t meas_status;            //!< Last sensor status (for internal use only)

    bme680_settings_t settings;     //!< Sensor settings
    bme680_calib_data_t calib_data; //!< Calibration data of the sensor
} bme680_t; 

bme680_init_desc() function initialize bme680 device descriptor by passing sensor struct, BME680 I2C address, ESP32 I2C pin numbers.

 ESP_ERROR_CHECK(bme680_init_desc(&sensor, BME680_I2C_ADDR, PORT, CONFIG_EXAMPLE_I2C_MASTER_SDA, CONFIG_EXAMPLE_I2C_MASTER_SCL));

bme680_init_sensor() method initialize a BME680 sensor device data structure, probes the sensor, soft resets the sensor, and configures the sensor with the the following default settings:

  • Oversampling rate for temperature, pressure, humidity = osr_1x
  • Filter size for pressure and temperature = iir_size 3
  • Heater profile 0 with 320 degree C and 150 ms duration
    ESP_ERROR_CHECK(bme680_init_sensor(&sensor));

bme680_set_oversampling_rates() function set the oversampling rates for measurements of temperature, humidity, pressure and gas. With this function, we can set individual sampling rates for temperature, pressure and humidity. The bme680_init_desc() function sets the default sampling rates to 1x. But we can change them with this function. The possible oversampling rates are 1x (default), 2x, 4x, 8x, 16x.

   bme680_set_oversampling_rates(&sensor, BME680_OSR_4X, BME680_OSR_2X, BME680_OSR_2X);

bme680_set_filter_size() define the size of IRR filers because BME680 has an internal IRR filter which can be used to minimize effect of short-term changes in sensor output values that might be caused by external disturbances. IRR filer adjust the reduce the output bandwidth of sensor.

   bme680_set_filter_size(&sensor, BME680_IIR_SIZE_7);

The BME680 sensor have a built-in header for the gas measurement and it supports up to 10 heater profiles. Each profile consists of a temperature set-point (the target temperature) and a heating duration. This functions set the heater profile. The input arguments are sensor descriptor struct, heater profile, target temperature and heating duration. The bme680_set_heater_profile() function activate gas measurement with a given heater profile

 bme680_set_heater_profile(&sensor, 0, 200, 100);
 bme680_use_heater_profile(&sensor, 0);

The bme680_set_ambient_temperature() function set the ambient temperature for heater resistance calculation. Because heater resistance calculation algorithm takes into account the ambient temperature of the sensor.

   bme680_set_ambient_temperature(&sensor, 10);

bme680_get_measurement_duration() function determine the measurement duration from BME680 in terms of RTOS ticks.

 uint32_t duration;
 bme680_get_measurement_duration(&sensor, &duration);

BME680 Sensor Data Read Function

The get_bme680_readings() functions reads sensor readings and populate them into ‘values’ struct of type bme680_values_float_t.

This bme680_values_float_t struct will populate with sensor values when we call bme680_get_results_float() function to get temperature, humidity, pressure and gas data from the sensor.

typedef struct
{
    float temperature;    //!< temperature in degree C        (Invalid value -327.68)
    float pressure;       //!< barometric pressure in hPascal (Invalid value 0.0)
    float humidity;       //!< relative humidity in %         (Invalid value 0.0)
    float gas_resistance; //!< gas resistance in Ohm          (Invalid value 0.0)
} bme680_values_float_t;
  bme680_values_float_t values;

Inside this function, we call bme680_get_results_float() function to get BME680 sensor readings and store them to struct ‘values’. This struct is defined as a global struct and we will use it http response handler to send sensor values to web pages.

void get_bme680_readings(void)
{
    
    // trigger the sensor to start one TPHG measurement cycle
    if (bme680_force_measurement(&sensor) == ESP_OK)
    {
        // passive waiting until measurement results are available
        vTaskDelay(duration);

        // get the results and do something with them
        if (bme680_get_results_float(&sensor, &values) == ESP_OK)
            printf("BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f KOhm\n",
                   values.temperature, values.humidity, values.pressure, values.gas_resistance);
    }
}

Handling Wi-Fi Events

We have the wifi_event_handler() function which handles the Wi-Fi events. This acts as a callback function, when any Wi-Fi event occurs. There are a different events that the Wi-Fi driver posts to the event task. These include: SYSTEM_EVENT_STA_START, SYSTEM_EVENT_STA_CONNECTED, SYSTEM_EVENT_STA_GOT_IP and SYSTEM_EVENT_STA_DISCONNECTED. These events occur if Wi-Fi is connected, a station gets connected to the AP, Wi-Fi gets IP or if the station disconnects from the AP respectively.

In our wifi_event_handler() function, we will check if the Wi-Fi event id corresponds to either of these four events. If it does, then it will print a relevant message on the ESP-IDF according to the event type and call an appropriate function. If either SYSTEM_EVENT_STA_START event, the function esp_wifi_connect() will be called. This will be responsible for connecting the ESP32 Wi-Fi station to the access point. If the event SYSTEM_EVENT_STA_DISCONNECTED occurs, then the function esp_wifi_connect() will be called till the maximum retry value which we set as 10. If the Wi-Fi doesn’t connect within the number of tries, then the ESP-IDF terminal prints the message that the max retry failed. If SYSTEM_EVENT_STA_GOT_IP event occurs, then the function mqtt_app_start() will be called to initiate the MQTT client connection.

static esp_err_t wifi_event_handler(void *arg, esp_event_base_t event_base,
                                    int32_t event_id, void *event_data)
{
    switch (event_id)
    {
    case WIFI_EVENT_STA_START:
        esp_wifi_connect();
        ESP_LOGI(TAG, "Trying to connect with Wi-Fi\n");
        break;

    case WIFI_EVENT_STA_CONNECTED:
        ESP_LOGI(TAG, "Wi-Fi connected\n");
        break;

    case IP_EVENT_STA_GOT_IP:
        ESP_LOGI(TAG, "got ip: startibg MQTT Client\n");
        mqtt_app_start();
        break;

    case WIFI_EVENT_STA_DISCONNECTED:
        ESP_LOGI(TAG, "disconnected: Retrying Wi-Fi\n");
        if (retry_cnt++ < MAX_RETRY)
        {
            esp_wifi_connect();
        }
        else
            ESP_LOGI(TAG, "Max Retry Failed: Wi-Fi Connection\n");
        break;

    default:
        break;
    }
    return ESP_OK;
}

Initialize Wi-Fi

The wifi_init() function will be called inside the app_main() function to initialize Wi-Fi in station mode for ESP32.

void wifi_init(void)
{
    esp_event_loop_create_default();
    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    esp_netif_init();
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
    esp_wifi_start();
}

We start off by creating the default event loop by calling the function esp_event_loop_create_default(). Afterwards, we use esp_event_handler_register() to register event handlers to the system event loop for both the WIFI_EVENT and IP_EVENT.

    esp_event_loop_create_default();
    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);

In this part, we set up the SSID and password of the network we want our ESP32 to connect with, as well as assign the security setting.

  wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
	     .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };

Here we are initializing lwIP through the function esp_netif_init() and create an IwIP task which is also known as a TCP/IP task. lwIP is a TCP/IP library stack provided by ESP-IDF that is used to perform various protocols such as TCP, UDP, DHCP etc.

esp_netif_init();

The following line is used for Wi-Fi default initialization for station mode. This API is used to initialize as well as register event handlers for the default interface which is station in our case. It creates a network interface instance binding Wi-Fi driver and TCP/IP stack. When a station is in the process of connecting to an AP, various processes automatically get handled through this function.

esp_netif_create_default_wifi_sta();

Then we initialize the Wi-Fi allocate resource for the Wi-Fi driver. It is responsible for initiating the Wi-Fi task. It takes in a single parameter cfg which is a pointer to the already initialized Wi-Fi configuration structure which is set to WIFI_INIT_CONFIG_DEFAULT() so that the initialization of the configuration is at default values. Here wifi_init_config_t is a structure that denotes the Wi-Fi stack configuration parameters that are passed inside the esp_wifi_init() function.

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
 esp_wifi_init(&cfg);

Moreover, we set Wi-Fi mode as station. To set the Wi-Fi mode as station, we use the function esp_wifi_set_mode() and pass the WiFI_MODE_STA as a parameter inside it.

esp_wifi_set_mode(WIFI_MODE_STA);

Then we assign our network’s SSID and password. This way the Wi-Fi driver gets configured with the AP network credentials and Wi-Fi mode. Then, we start the Wi-Fi connection at our assigned configuration.

esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
esp_wifi_start();

Handling MQTT Events

Next, we have the mqtt_event_handler() function which handles the MQTT events. This function is called by the MQTT client event loop. We will check if the event id corresponds to either of the three events which can be MQTT_EVENT_CONNECTED, MQTT_EVENT_DISCONNECTED or MQTT_EVENT_ERROR. If it does, then it will print a relevant message on the ESP-IDF according to the event type and call an appropriate function.

The table below shows the descriptions of the MQTT events that will be used in this example.

MQTT_EVENT_CONNECTEDThis event is posted when the client successfully connects to the broker and is ready to send/receive data.
MQTT_EVENT_DISCONNECTEDThis event is posted when the client cancels the connection between the broker as the server is not available. The client is not able to send/receive data in this scenario.
MQTT_EVENT_ERRORThis events gets posted by the client when it experiences an error.
/*
 * @brief Event handler registered to receive MQTT events
 *
 *  This function is called by the MQTT client event loop.
 *
 * @param handler_args user data registered to the event.
 * @param base Event base for the handler(always MQTT Base in this example).
 * @param event_id The id for the received event.
 * @param event_data The data for the event, esp_mqtt_event_handle_t.
 */
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id)
    {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        MQTT_CONNECTED = 1;
        break;

    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        MQTT_CONNECTED = 0;
        break;

    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

If the event MQTT_EVENT_CONNECTED is posted by the client, the “MQTT_CONNECTED” variable is set to the value 1, which initially held the value 0.

case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        MQTT_CONNECTED = 1;
        break;

If the event MQTT_EVENT_DISCONNECTED is posted by the client, the MQTT_CONNECTED variable is set to the value 0 and the terminal prints the message indicating that this event occurred. Similarly, for the rest of the events a relevant message is printed on the terminal along with the event_id incase of a different event.

case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        MQTT_CONNECTED = 0;
        break;

    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;

mqtt_app_start()

Inside the mqtt_app_start() function, an MQTT client handle is created according to the configuration parameters set using esp_mqtt_client_init(). This function takes in a single parameter which is the pointer to the MQTT configuration structure. The MQTT configuration structure holds the MQTT Broker IP address which acts as the mqtt server. Make sure to specify the IP address of your Raspberry Pi which has Mosquitto broker running on it, inside the MQTT configuration structure.

After that we will call the function esp_mqtt_client_register_event() which is used to register the MQTT event. It takes in four parameters. The first parameter is the MQTT client handle. The second parameter is the event type. The third parameter is the handler callback and the last parameter is the handle context which is mqtt_client_handle, ‘client’ in our case. Finally, we will start the MQTT client using the function esp_mqtt_client_start() and specify the already created client handle as a parameter inside it.

static void mqtt_app_start(void)
{
    ESP_LOGI(TAG, "STARTING MQTT");
    esp_mqtt_client_config_t mqttConfig = {
        .uri = "mqtt://192.168.18.8:1883"};

    client = esp_mqtt_client_init(&mqttConfig);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
    esp_mqtt_client_start(client);
    ESP_ERROR_CHECK(i2cdev_init());
    init_bme680();
    xTaskCreate(Publisher_Task, "Publisher_Task", 1024 * 5, NULL, 5, NULL);
    ESP_LOGI(TAG, "MQTT Publisher_Task is up and running\n");
}

Publisher_Task()

Inside the Publisher_Task() function, the BME680 sensor is initialized and sensor data is acquired and published to the corresponding topics after every 5 seconds using MQTT protocol.

void Publisher_Task(void *params)
{
    char temperature[12];
    char pressure[12];
    char humidity[12]; 
    char gas_resistance[12];
    while (1)
    {
        

        get_bme680_readings();
        sprintf(temperature, "%.2f", values.temperature);
        sprintf(humidity, "%.2f", values.humidity);
        sprintf(pressure, "%.2f", values.pressure);
        sprintf(gas_resistance, "%.2f", values.gas_resistance);

        if (MQTT_CONNECTED)
        {
            esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME680, temperature, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME680, humidity, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME680, pressure, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_GAS_BME680, gas_resistance, 0, 0, 0);
        }
        else
        {
            ESP_LOGE(TAG, "MQTT Not connected");
        }
        vTaskDelay(10000 / portTICK_PERIOD_MS);
    }
}

If the BME680 sensor is measuring correctly and if MQTT_CONNECTED is true, then the sensor readings will get published to the corresponding topics after every 5 seconds. Incase, of an error in measuring or initialization of the sensor, a relevant message will be printed on the console.

The esp_mqtt_client_publish() function is responsible for publishing the message. It takes in six parameters which include the MQTT client handle, the topic string, the data to be published, data length, qos and the retain flag respectively.

if (MQTT_CONNECTED)
        {
            esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME680, temperature, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME680, humidity, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME680, pressure, 0, 0, 0);
            esp_mqtt_client_publish(client, MQTT_PUB_GAS_BME680, gas_resistance, 0, 0, 0);
        }
        else
        {
            ESP_LOGE(TAG, "MQTT Not connected");
        }
        vTaskDelay(10000 / portTICK_PERIOD_MS);

app_main()

Inside the app_main() function, first NVS is initialized by calling the nvs_flash_init() function. Then we will check if the NVS partition does not contain any empty pages or if it contains data in a format which is different from the current version of code. If any of these condition is true then we will erase the NVS flash using nvs_flash_erase() and initialize it again. Then, wifi_init() function is called which will initialize Wi-Fi in station mode for ESP32. After that, we use xTaskCreate() to create the Publisher_Task that will publish the sensor readings on the corresponding topics after every 5 seconds.

esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    wifi_init();

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code is successfully flashed, you can view different messages on the ESP-IDF terminal. First, the Wi-Fi gets successfully connected. Let us set up our Node-Red dashboard to view the sensor data.

Code Output

Now use any MQTT client Desktop application and subscribe to these topics:

  • esp32/bme680/temperature
  • esp32/bme680/humidity
  • esp32/bme680/pressure
  • esp32/bme680/gas
ESP32 ESP-IDF MQTT Publish BME680 Sensor Readings

You may also like to read:

1 thought on “ESP32 ESP-IDF MQTT Publish BME680 Sensor Readings”

Leave a Comment