ESP32 MQTT Publish BME280 Sensor Readings to Node-Red using ESP-IDF

In this project, we will learn to publish BME280 sensor readings to Node-Red with ESP32 MQTT using ESP-IDF. We will perform MQTT communication with ESP32 and Node-Red and publish and subscribe to MQTT topics. The ESP32 will act as an MQTT client publisher and Node-Red will act as a subscriber. Using MQTT protocol, the ESP32 will publish BME280 sensor readings on the Node-Red Dashboard which will subscribe to the corresponding BME280 readings topics. The sensor data constituting of temperature, pressure and humidity will be interactively displayed on the Node-Red Dashboard.

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 sensor data to the Node-Red dashboard on the specified topics.

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

ESP32 ESP-IDF MQTT Client: Publish BME280 Sensor Readings to Node-Red

Our aim is to create a project that uses ESP32 MQTT protocol using ESP-IDF to publish BME280 sensor readings to Node-Red. Let us look the steps that are involved in this project.

  1. The ESP32 board is interfaced with a BME280 sensor which is used to measure ambient temperature, barometric pressure, and relative humidity. This ESP32 board will connect to the MQTT broker. In our case, we will use Mosquitto broker on Raspberry Pi. If you want to know how to successfully install the broker on your Raspberry Pi, you can refer to the following guide: (Install Mosquitto MQTT Broker on Raspberry Pi
  2. After acquiring the sensor data from BME280, the ESP32 board publishes these readings on individual MQTT topics.: esp32/bme280/temperature is for publishing BME280 temperature, esp32/bme280/humidity is for publishing humidity. Similarly, esp32/bme280/pressure is for publishing pressure.
  3. Node-Red will subscribe to these three topics. Therefore, it will be able to receive the sensor readings and interactively display them in its dashboard with the help of gauges and line charts etc.
ESP-IDF MQTT BME280 with Node-Red Overview

Interfacing BME280 with ESP32

BME280 sensor is widely used in electronic projects to measure ambient temperature, barometric pressure, and relative humidity where low power consumption is required. Both I2C and SPI communication protocols can be used to send data over to the microcontroller. Although the BME280 sensor comes in various versions, we will use I2C communication to communicate the sensor data with the ESP32 board. This protocol is based on a synchronous, multi-master multi-slave system where the ESP32 board acts as the master and the BME280 sensor acts as the slave.

The diagram below shows the pinout of the BME280 sensor that we will use.

BME280 Sensor Pinout

We will need the following components to connect our ESP32 board with the I2C LCD.

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

The connection of I2C LCD 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:

BME280 SensorESP32
GNDGND
VCC3.3V
SDAGPIO21(I2C SDA)
SCLGPIO22 (I2C SCL)
ESP32 with BME280 sensor connection diagram

ESP32 MQTT BME280 Readings with Node-Red and ESP-IDF

We will build and create a project in VS Code with ESP-IDF extension. The code will read temperature, humidity, and pressure values by using the I2C master driver provided by ESP-IDF and then use mqtt_client library to connect ESP32 with the MQTT broker, and publish the BME280 sensor data on different topics. We will use BME280 library written by BoschSensortec.

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 libraries required for this project. Create a new folder called ‘components/bme280’ and add the following files present at this link. This includes all the files listed below:
ESP-IDF MQTT BME280 with Node-Red Add BME280 Libraries
  • 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 code given below in that file and save it.

ESP32 MQTT BME280 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 "bme280.h"

static const char *TAG = "MQTT_EXAMPLE";
#define TAG_BME280 "BME280"

#define EXAMPLE_ESP_WIFI_SSID "YOUR_SSID"
#define EXAMPLE_ESP_WIFI_PASS "YOUR_PASSWORD"
#define MAX_RETRY 10
static int retry_cnt = 0;

#define MQTT_PUB_TEMP_BME280 "esp32/bme280/temperature"
#define MQTT_PUB_HUM_BME280 "esp32/bme280/humidity"
#define MQTT_PUB_PRES_BME280 "esp32/bme280/pressure"

#define SDA_PIN GPIO_NUM_21
#define SCL_PIN GPIO_NUM_22
#define I2C_MASTER_ACK 0
#define I2C_MASTER_NACK 1

void i2c_master_init()
{
    i2c_config_t i2c_config = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = SDA_PIN,
        .scl_io_num = SCL_PIN,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = 1000000};
    i2c_param_config(I2C_NUM_0, &i2c_config);
    i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);
}

s8 BME280_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
    s32 iError = BME280_INIT_VALUE;

    esp_err_t espRc;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();

    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);

    i2c_master_write_byte(cmd, reg_addr, true);
    i2c_master_write(cmd, reg_data, cnt, true);
    i2c_master_stop(cmd);

    espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS);
    if (espRc == ESP_OK)
    {
        iError = SUCCESS;
    }
    else
    {
        iError = FAIL;
    }
    i2c_cmd_link_delete(cmd);

    return (s8)iError;
}

s8 BME280_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
    s32 iError = BME280_INIT_VALUE;
    esp_err_t espRc;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();

    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);

    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);

    if (cnt > 1)
    {
        i2c_master_read(cmd, reg_data, cnt - 1, I2C_MASTER_ACK);
    }
    i2c_master_read_byte(cmd, reg_data + cnt - 1, I2C_MASTER_NACK);
    i2c_master_stop(cmd);

    espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS);
    if (espRc == ESP_OK)
    {
        iError = SUCCESS;
    }
    else
    {
        iError = FAIL;
    }

    i2c_cmd_link_delete(cmd);

    return (s8)iError;
}

void BME280_delay_msek(u32 msek)
{
    vTaskDelay(msek / portTICK_PERIOD_MS);
}

uint32_t MQTT_CONNECTED = 0;

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;
static void mqtt_app_start(void)
{
    ESP_LOGI(TAG, "STARTING MQTT");
    esp_mqtt_client_config_t mqttConfig = {
        .uri = "mqtt://YOUR_RPI_IP_ADDRESS: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);
}

void Publisher_Task(void *params)
{
    struct bme280_t bme280 = {
        .bus_write = BME280_I2C_bus_write,
        .bus_read = BME280_I2C_bus_read,
        .dev_addr = BME280_I2C_ADDRESS1,
        .delay_msec = BME280_delay_msek};

    s32 com_rslt;
    s32 v_uncomp_pressure_s32;
    s32 v_uncomp_temperature_s32;
    s32 v_uncomp_humidity_s32;

    com_rslt = bme280_init(&bme280);

    com_rslt += bme280_set_oversamp_pressure(BME280_OVERSAMP_16X);
    com_rslt += bme280_set_oversamp_temperature(BME280_OVERSAMP_2X);
    com_rslt += bme280_set_oversamp_humidity(BME280_OVERSAMP_1X);

    com_rslt += bme280_set_standby_durn(BME280_STANDBY_TIME_1_MS);
    com_rslt += bme280_set_filter(BME280_FILTER_COEFF_16);

    com_rslt += bme280_set_power_mode(BME280_NORMAL_MODE);
    if (com_rslt == SUCCESS)
    {
        while (true)
        {
            vTaskDelay(40 / portTICK_PERIOD_MS);

            com_rslt = bme280_read_uncomp_pressure_temperature_humidity(
                &v_uncomp_pressure_s32, &v_uncomp_temperature_s32, &v_uncomp_humidity_s32);

            double temp = bme280_compensate_temperature_double(v_uncomp_temperature_s32);
            char temperature[12];
            sprintf(temperature, "%.2f degC", temp);

            double press = bme280_compensate_pressure_double(v_uncomp_pressure_s32) / 100; // Pa -> hPa
            char pressure[10];
            sprintf(pressure, "%.2f hPa", press);

            double hum = bme280_compensate_humidity_double(v_uncomp_humidity_s32);
            char humidity[10];
            sprintf(humidity, "%.2f %%", hum);

            if (com_rslt == SUCCESS)
            {
                if (MQTT_CONNECTED)
                {
                    esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME280, temperature, 0, 0, 0);
                    esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME280, pressure, 0, 0, 0);
                    esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME280, humidity, 0, 0, 0);
                    vTaskDelay(5000 / portTICK_PERIOD_MS);
                }
            }
            else
            {
                ESP_LOGE(TAG_BME280, "measure error. code: %d", com_rslt);
            }
        }
    }
    else
    {
        ESP_LOGE(TAG_BME280, "init or setting error. code: %d", com_rslt);
    }
}

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();
    i2c_master_init();
    xTaskCreate(Publisher_Task, "Publisher_Task", 1024 * 5, NULL, 5, NULL);
}

How does the Code Works?

Firstly, we will start by including the necessary libraries that includes the i2c driver, gpio driver, BME280 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 BME280 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 "bme280.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” and “BME280” are two tags that will be used while logging.

static const char *TAG = "MQTT_EXAMPLE";
#define TAG_BME280 "BME280"

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 topics the ESP32 board will publish to. These include: esp32/bme280/temperature for temperature readings, esp32/bme280/humidity for humidity readings and esp32/bme280/pressure for pressure readings.

#define MQTT_PUB_TEMP_BME280 "esp32/bme280/temperature"
#define MQTT_PUB_HUM_BME280 "esp32/bme280/humidity"
#define MQTT_PUB_PRES_BME280 "esp32/bme280/pressure"

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

#define SDA_PIN GPIO_NUM_21
#define SCL_PIN GPIO_NUM_22

Initializing I2C Master (ESP32)

The following function is responsible for initializing the I2C master.

void i2c_master_init()
{
	i2c_config_t i2c_config = {
		.mode = I2C_MODE_MASTER,
		.sda_io_num = SDA_PIN,
		.scl_io_num = SCL_PIN,
		.sda_pullup_en = GPIO_PULLUP_ENABLE,
		.scl_pullup_en = GPIO_PULLUP_ENABLE,
		.master.clk_speed = 1000000
	};
	i2c_param_config(I2C_NUM_0, &i2c_config);
	i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);
}

First, the parameters of the i2c_config_t structure are defined. This includes the mode of operation which is master in this case, the I2C communication pins, configure the internal pull-up resistors of ESP32 and setting the I2C clock speed.

	i2c_config_t i2c_config = {
		.mode = I2C_MODE_MASTER,
		.sda_io_num = SDA_PIN,
		.scl_io_num = SCL_PIN,
		.sda_pullup_en = GPIO_PULLUP_ENABLE,
		.scl_pullup_en = GPIO_PULLUP_ENABLE,
		.master.clk_speed = 1000000
	};

After the parameters are set, then the I2C configuration is initialized for the particular port. This is achieved by calling the function i2c_param_config() which takes in two parameters. The first parameter is the I2C port and the second parameter is the pointer to a constant configuration structure of type i2c_config_t. At this point, the I2C port gets configured.

i2c_param_config(I2C_NUM_0, &i2c_config);

The next step is to install the I2C driver. It takes in several parameters which are listed below:

  • The I2C port number.
  • The configuration mode that we set (master/slave)
  • RX buffer disable. This is the receiving buffer size which is applicable for the slave.
  • TX buffer disable. This is the sending buffer size which is also applicable for the slave.
  • The last parameter is ‘intr_alloc_flags’ which is the flags for the allocated interrupt. It is ‘0’ in our case as we are not using interrupts in this example.
i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);

BME280 I2C Bus Write/Read Functions

s8 BME280_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
	s32 iError = BME280_INIT_VALUE;

	esp_err_t espRc;
	i2c_cmd_handle_t cmd = i2c_cmd_link_create();

	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);

	i2c_master_write_byte(cmd, reg_addr, true);
	i2c_master_write(cmd, reg_data, cnt, true);
	i2c_master_stop(cmd);

	espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
	if (espRc == ESP_OK) {
		iError = SUCCESS;
	} else {
		iError = FAIL;
	}
	i2c_cmd_link_delete(cmd);

	return (s8)iError;
}

Inside the BME280_I2C_bus_write() function, we start off by creating and initializing the I2C command link using i2c_cmd_link_create() function. This function will return an I2C command link handler.

i2c_cmd_handle_t cmd = i2c_cmd_link_create();

After we create the command link, we will call i2c_master_start() and pass the command link as a parameter inside it. This will enable the I2C master to generate a starting signal. It will transmit all queued commands.

Note: This function is only called when the device acts as a master.

i2c_master_start(cmd);

Then we call i2c_master_write_byte() function is used to specify the slave address. This function takes in three parameters. The I2C command link, I2C one byte command which will be written to the bus and the enable ack check for the master device. Here “(dev_addr << 1) | I2C_MASTER_WRITE” is the salve address that specifies the master will write to the slave.

i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);

Next, we call i2c_master_write() function for the I2C master to write the buffer to the I2C bus. This function takes in four parameters which are the I2C command handler, the data to be transferred, the length of the data and enable ack check for the master device.

i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_write(cmd, reg_data, cnt, true);

Then we call the function i2c_master_cmd_begin() which will enable the I2C master to start sending the queued commands. This function takes in three parameters. The first parameter is the I2C port number, the second parameter is the I2C command handler and the third parameter is the maximum wait ticks which is set to 0.01 second in this case.

	espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
	if (espRc == ESP_OK) {
		iError = SUCCESS;
	} else {
		iError = FAIL;
	}

Afterwards, we will free the command link when we are done sending the commands. This is achieved by calling the i2c_cmd_link() function and passing the I2C command handler as a parameter inside it.

i2c_cmd_link_delete(cmd);

Inside the BME280_I2C_bus_read() function, all the same steps are followed to create the link and start the I2C master bus and write byte just as we did for BME280_I2C_bus_write(). This time the command link sent by the master to read data from the slave contains the address ESP_SLAVE_ADDR << 1) | I2C_MASTER_READ which is specified as the second parameter in the i2c_master_write_byte() function will However, now we will populate the command link inside the if condition which checks if the length of data is greater than 1, then it calls i2c_master_read() to read multiple bytes on the I2C bus. It takes in four parameters. The first parameter is the I2C command handler. The second parameter is the data to be transferred. The third parameter is the length of the data buffer in bytes. Lastly, the fourth parameter is to enable ACK signal for the master.

Next, call i2c_master_read_byte() to read a single byte on the I2C bus. Also, it is configured such that the master does not provide the ACK bit. After that queue a “STOP signal” to the given commands list by calling i2c_master_stop().

Then we call the function i2c_master_cmd_begin() which will enable the I2C master to start sending the queued commands. Afterwards, we will free the command link when we are done sending the commands.

s8 BME280_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
	s32 iError = BME280_INIT_VALUE;
	esp_err_t espRc;

	i2c_cmd_handle_t cmd = i2c_cmd_link_create();

	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
	i2c_master_write_byte(cmd, reg_addr, true);

	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);

	if (cnt > 1) {
		i2c_master_read(cmd, reg_data, cnt-1, I2C_MASTER_ACK);
	}
	i2c_master_read_byte(cmd, reg_data+cnt-1, I2C_MASTER_NACK);
	i2c_master_stop(cmd);

	espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
	if (espRc == ESP_OK) {
		iError = SUCCESS;
	} else {
		iError = FAIL;
	}

	i2c_cmd_link_delete(cmd);

	return (s8)iError;
}

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.
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.

esp_mqtt_client_handle_t client = NULL;
static void mqtt_app_start(void)
{
    ESP_LOGI(TAG, "STARTING MQTT");
    esp_mqtt_client_config_t mqttConfig = {
        .uri = "mqtt://YOUR_RPI_IP_ADDRESS: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);
}

Publisher_Task()

Inside the Publisher_Task() function, the BME280 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)
{
    struct bme280_t bme280 = {
        .bus_write = BME280_I2C_bus_write,
        .bus_read = BME280_I2C_bus_read,
        .dev_addr = BME280_I2C_ADDRESS1,
        .delay_msec = BME280_delay_msek};

    s32 com_rslt;
    s32 v_uncomp_pressure_s32;
    s32 v_uncomp_temperature_s32;
    s32 v_uncomp_humidity_s32;

    com_rslt = bme280_init(&bme280);

    com_rslt += bme280_set_oversamp_pressure(BME280_OVERSAMP_16X);
    com_rslt += bme280_set_oversamp_temperature(BME280_OVERSAMP_2X);
    com_rslt += bme280_set_oversamp_humidity(BME280_OVERSAMP_1X);

    com_rslt += bme280_set_standby_durn(BME280_STANDBY_TIME_1_MS);
    com_rslt += bme280_set_filter(BME280_FILTER_COEFF_16);

    com_rslt += bme280_set_power_mode(BME280_NORMAL_MODE);
    if (com_rslt == SUCCESS)
    {
        while (true)
        {
            vTaskDelay(40 / portTICK_PERIOD_MS);

            com_rslt = bme280_read_uncomp_pressure_temperature_humidity(
                &v_uncomp_pressure_s32, &v_uncomp_temperature_s32, &v_uncomp_humidity_s32);

            double temp = bme280_compensate_temperature_double(v_uncomp_temperature_s32);
            char temperature[12];
            sprintf(temperature, "%.2f degC", temp);

            double press = bme280_compensate_pressure_double(v_uncomp_pressure_s32) / 100; // Pa -> hPa
            char pressure[10];
            sprintf(pressure, "%.2f hPa", press);

            double hum = bme280_compensate_humidity_double(v_uncomp_humidity_s32);
            char humidity[10];
            sprintf(humidity, "%.2f %%", hum);

            if (com_rslt == SUCCESS)
            {
                if (MQTT_CONNEECTED)
                {
                    esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME280, temperature, 0, 0, 0);
                    esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME280, pressure, 0, 0, 0);
                    esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME280, humidity, 0, 0, 0);
                    vTaskDelay(5000 / portTICK_PERIOD_MS);
                }
            }
            else
            {
                ESP_LOGE(TAG_BME280, "measure error. code: %d", com_rslt);
            }
        }
    }
    else
    {
        ESP_LOGE(TAG_BME280, "init or setting error. code: %d", com_rslt);
    }
}

First, the parameters of the bme280_t structure are defined. This includes the I2C write, I2C read, slave address and delay.

	struct bme280_t bme280 = {
		.bus_write = BME280_I2C_bus_write,
		.bus_read = BME280_I2C_bus_read,
		.dev_addr = BME280_I2C_ADDRESS1,
		.delay_msec = BME280_delay_msek
	};

Declare the following variables to holds the BME280 sensor data:

	s32 com_rslt;
	s32 v_uncomp_pressure_s32;
	s32 v_uncomp_temperature_s32;
	s32 v_uncomp_humidity_s32;

Initialize the BME280 sensor by calling bme280_init() and specifying the pointer to the bme280_t structure as a parameter inside it.

com_rslt = bme280_init(&bme280);

Next, we set the pressure oversampling setting in the register 0xF4 bits from 2 to 4 by calling bme280_set_overamp_pressure(). This function takes in a single parameter inside it which is the value of the pressure over sampling. It can take any value listed below:

  • Skipped: value is 0x00
  • BME280_OVERSAMP_1X: Value is 0x01
  • BME280_OVERSAMP_2X: Value is 0x02
  • BME280_OVERSAMP_4X: Value is 0x03
  • BME280_OVERSAMP_8X: Value is 0x04
  • BME280_OVERSAMP_16X: Value is 0x05, 0x06 and 0x07
com_rslt += bme280_set_oversamp_pressure(BME280_OVERSAMP_16X);

Similarly, we set the temperature oversampling setting in the register 0xF4 bits from 5 to 7 by calling bme280_set_overamp_temperature(). This function takes in a single parameter inside it which is the value of the temperature over sampling. It can take any value listed below:

  • Skipped: value is 0x00
  • BME280_OVERSAMP_1X: Value is 0x01
  • BME280_OVERSAMP_2X: Value is 0x02
  • BME280_OVERSAMP_4X: Value is 0x03
  • BME280_OVERSAMP_8X: Value is 0x04
  • BME280_OVERSAMP_16X: Value is 0x05, 0x06 and 0x07
com_rslt += bme280_set_oversamp_temperature(BME280_OVERSAMP_2X);

Likewise, we set the humidity oversampling setting in the register 0xF2 bits from 0 to 2 by calling bme280_set_overamp_humidity(). This function takes in a single parameter inside it which is the value of the humidity over sampling. It can take any value listed below:

  • Skipped: value is 0x00
  • BME280_OVERSAMP_1X
  • BME280_OVERSAMP_2X
  • BME280_OVERSAMP_4X
  • BME280_OVERSAMP_8X
  • BME280_OVERSAMP_16X
com_rslt += bme280_set_oversamp_humidity(BME280_OVERSAMP_1X);

After that we will write the standby duration time from the sensor in the register 0xF5 bit 5 to 7 by calling bme280_set_standby_durn(). This function takes in a single parameter which is the value of the standby duration time. It can take any value listed below:

  • BME280_STANDBY_TIME_1_MS
  • BME280_STANDBY_TIME_63_MS
  • BME280_STANDBY_TIME_125_MS
  • BME280_STANDBY_TIME_250_MS
  • BME280_STANDBY_TIME_500_MS
  • BME280_STANDBY_TIME_1000_MS
  • BME280_STANDBY_TIME_2000_MS
  • BME280_STANDBY_TIME_4000_MS
com_rslt += bme280_set_standby_durn(BME280_STANDBY_TIME_1_MS);

Then we will specify the filter setting in the register 0xF5 bit 3 and 4 by calling bme280_set_filter() function. This function also takes in a single parameter which is the value of IIR filter coefficient. It can take any value listed below:

  • BME280_FILTER_COEFF_OFF
  • BME280_FILTER_COEFF_2
  • BME280_FILTER_COEFF_4
  • BME280_FILTER_COEFF_8
  • BME280_FILTER_COEFF_16
com_rslt += bme280_set_filter(BME280_FILTER_COEFF_16);

Finally, we will set the power mode of the sensor by calling bme280_set_power_mode(). This function takes in a single parameter which denotes the operational mode in the register 0xF4 bit 0 and 1. It can take any value listed below:

  • BME280_SLEEP_MODE
  • BME280_FORCED_MODE
  • BME280_NORMAL_MODE
com_rslt += bme280_set_power_mode(BME280_NORMAL_MODE);

If all the configurations of the sensor are set successfully, then we will start the infinite loop which reads and publishes the sensor data. To read the uncompensated pressure, temperature and humidity readings we call the function bme280_read_uncomp_pressure_temperature_humidity().

com_rslt = bme280_read_uncomp_pressure_temperature_humidity(&v_uncomp_pressure_s32, &v_uncomp_temperature_s32, &v_uncomp_humidity_s32);

The bme280_compensate_temperature_double() outputs the actual temperature from uncompensated temperature. The bme280_compensate_pressure_double() outputs the actual pressure from the uncompensated pressure. Similarly, the bme280_compensate_humidity_double() outputs the actual humidity from the uncompensated humidity. We will first convert the double data type to an array of characters, round off to two decimal places and add the units for each reading.

double temp = bme280_compensate_temperature_double(v_uncomp_temperature_s32);
char temperature[12];
sprintf(temperature, "%.2f degC", temp);

double press = bme280_compensate_pressure_double(v_uncomp_pressure_s32)/100; // Pa -> hPa
char pressure[10];
sprintf(pressure, "%.2f hPa", press);

double hum = bme280_compensate_humidity_double(v_uncomp_humidity_s32);
char humidity[10];
sprintf(humidity, "%.2f %%", hum);

If the BME280 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 (com_rslt == SUCCESS)
            {
                if (MQTT_CONNECTED)
                {
                    esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME280, temperature, 0, 0, 0);
                    esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME280, pressure, 0, 0, 0);
                    esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME280, humidity, 0, 0, 0);
                    vTaskDelay(5000 / portTICK_PERIOD_MS);
                }
            }
            else
            {
                ESP_LOGE(TAG_BME280, "measure error. code: %d", com_rslt);
            }

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.

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();
    xTaskCreate(Publisher_Task, "Publisher_Task", 1024 * 5, NULL, 5, NULL);
}

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.

Node-Red Dashboard as MQTT BME280 Readings Subscriber

Our ESP32 publisher is sending the individual BME280 readings to the specific topics after every 5 seconds. In this section, we will show you how to setup Node-Red as an MQTT subscriber to our ESP32 BME280 topics. By using Node-Red dashboard, we will be able to view the sensor readings on our smart phone/laptop/computer. Lets first learn how to build the dashboard.

You can use any other MQTT subscription service as well to subscribe to the MQTT topics such as MQTTx Desktop Client which we previously used.

Refer to the getting started guide with Node-Red on Raspberry Pi:

To access Node-RED, we require the IP address of our Raspberry Pi and the port number on which Node-RED is accessible. By default, it starts on port 1880. Open any web browser and enter the IP address of your Raspberry Pi followed by the port number.

For example:

192.168.18.8:1880

Create Flow

This will open the Node-RED interface. Now we will start creating the flow.

ESP-IDF MQTT BME280 with Node-Red Create flow 1

Go to the right side and find dashboard. Install the dashboard if it isn’t already installed.

ESP-IDF MQTT BME280 with Node-Red Create flow 2

Click ‘Dashboard’ to open its tab. Now we create the dashboard tab node. To create a new tab, go to the Dashboard tab and click +tab. Give a name according to your preference and an icon available from the list of links given below:

After that, click Update to save the changes.

We has created one tab named Home and using icons from Angular Material Icons.

ESP-IDF MQTT BME280 with Node-Red Edit Dashboard Tab Node

Next we will add the widgets. We will add one group to the tab for the sensor. To create a group, click +group in the tab.

We have named the group as ‘BME280 Readings.’

ESP-IDF MQTT BME280 with Node-Red Edit Dashboard Group Node

Now let us add the widgets to our group. To display the temperature and humidity readings we will choose gauges and to display the pressure readings we will choose a line chart. Go to the Nodes section found at the far left and scroll down to find the required nodes under Dashboard.

Drag and drop two gauges, one chart and three mqtt in nodes to the flow as shown below:

ESP-IDF MQTT BME280 with Node-Red Add Widgets

Let us edit the properties of the nodes now. Click the first mqtt in node. This opens its properties. For the server (MQTT Broker), which is Mosquitto Broker on Raspberry Pi, set it to localhost:1883. Add the name of the topic to be subscribed, specify the qos and then click the Done button.

This particular node is subscribed to the topic ‘esp32/bme280/temperature.’

ESP-IDF MQTT BME280 with Node-Red Edit MQTT in Node 1

Likewise, edit the properties of the second mqtt in node. This particular node is subscribed to the topic ‘esp32/bme280/humidity.’

ESP-IDF MQTT BME280 with Node-Red Edit MQTT in Node 2

Likewise, edit the properties of the third mqtt in node as well. This particular node is subscribed to the topic ‘esp32/bme280/pressure.’

ESP-IDF MQTT BME280 with Node-Red Edit MQTT in Node 3

Edit the properties of the first and second gauge as shown below:

For the pressure readings, we will plot them on the line chart. Hence, edit the properties of the line chart accordingly.

ESP-IDF MQTT BME280 with Node-Red Edit Chart Node

Join the nodes as shown below. Then, click the Deploy button at the top.

ESP-IDF MQTT BME280 with Node-Red Join Nodes and Deploy

The status of the MQTT in nodes changes to ‘connected.’

ESP-IDF MQTT BME280 with Node-Red Nodes Connected

To open the dashboard, type: http://Your_RPI_IP_address:1880/ui in a new web browser and press enter. The user interface will open showing the two gauges and a line chart with readings from the BME280 sensor.

ESP-IDF MQTT BME280 with Node-Red Dashboard Laptop View

This is how the Node-Red Dashboard looks like when viewed from a smart phone.

ESP-IDF MQTT BME280 with Node-Red Dashboard Mobile View

You may also like to read:

6 thoughts on “ESP32 MQTT Publish BME280 Sensor Readings to Node-Red using ESP-IDF”

  1. Hi!
    Thanks for the tutorial! In the build process I get the following error: “bme280.h: No such file or directory”
    But I inserted the header file as described and I also specified the path in c_cpp_properties.json. I also believe that the file is found, because when I rename it, I still get an error that the file does not exist. Something seems to be wrong with “bme280.h”. When I open the file I get the error that #include is not found. Do you know these errors or do you have a tip as to what it could be? Thanks in advance!

    Reply

Leave a Comment