2021年12月3日 星期五

ESP32 Web Server with BME680

 ESP32 Web Server with BME680

源自於https://randomnerdtutorials.com/esp32-bme680-web-server-arduino/













/*********

  Rui Santos

  Complete project details at https://RandomNerdTutorials.com/esp32-bme680-sensor-arduino/

  

  Permission is hereby granted, free of charge, to any person obtaining a copy

  of this software and associated documentation files.

  

  The above copyright notice and this permission notice shall be included in all

  copies or substantial portions of the Software.

*********/


#include <Wire.h>

#include <SPI.h>

#include <Adafruit_Sensor.h>

#include "Adafruit_BME680.h"

#include <WiFi.h>

#include "ESPAsyncWebServer.h"


// Replace with your network credentials

//const char* ssid = "REPLACE_WITH_YOUR_SSID";

//const char* password = "REPLACE_WITH_YOUR_PASSWORD";

const char* ssid     = "PTS-2F";

const char* password = "PTS6662594";


//Uncomment if using SPI

/*#define BME_SCK 18

#define BME_MISO 19

#define BME_MOSI 23

#define BME_CS 5*/


Adafruit_BME680 bme; // I2C

//Adafruit_BME680 bme(BME_CS); // hardware SPI

//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);


float temperature;

float humidity;

float pressure;

float gasResistance;


AsyncWebServer server(80);

AsyncEventSource events("/events");


unsigned long lastTime = 0;  

unsigned long timerDelay = 30000;  // send readings timer


void getBME680Readings(){

  // Tell BME680 to begin measurement.

  unsigned long endTime = bme.beginReading();

  if (endTime == 0) {

    Serial.println(F("Failed to begin reading :("));

    return;

  }

  if (!bme.endReading()) {

    Serial.println(F("Failed to complete reading :("));

    return;

  }

  temperature = bme.temperature;

  pressure = bme.pressure / 100.0;

  humidity = bme.humidity;

  gasResistance = bme.gas_resistance / 1000.0;

}


String processor(const String& var){

  getBME680Readings();

  //Serial.println(var);

  if(var == "TEMPERATURE"){

    return String(temperature);

  }

  else if(var == "HUMIDITY"){

    return String(humidity);

  }

  else if(var == "PRESSURE"){

    return String(pressure);

  }

 else if(var == "GAS"){

    return String(gasResistance);

  }

}


const char index_html[] PROGMEM = R"rawliteral(

<!DOCTYPE HTML><html>

<head>

  <title>BME680 Web Server</title>

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

  <link rel="icon" href="data:,">

  <style>

    html {font-family: Arial; display: inline-block; text-align: center;}

    p {  font-size: 1.2rem;}

    body {  margin: 0;}

    .topnav { overflow: hidden; background-color: #4B1D3F; color: white; font-size: 1.7rem; }

    .content { padding: 20px; }

    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }

    .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

    .reading { font-size: 2.8rem; }

    .card.temperature { color: #0e7c7b; }

    .card.humidity { color: #17bebb; }

    .card.pressure { color: #3fca6b; }

    .card.gas { color: #d62246; }

  </style>

</head>

<body>

  <div class="topnav">

    <h3>BME680 WEB SERVER</h3>

  </div>

  <div class="content">

    <div class="cards">

      <div class="card temperature">

        <h4><i class="fas fa-thermometer-half"></i> TEMPERATURE</h4><p><span class="reading"><span id="temp">%TEMPERATURE%</span> &deg;C</span></p>

      </div>

      <div class="card humidity">

        <h4><i class="fas fa-tint"></i> HUMIDITY</h4><p><span class="reading"><span id="hum">%HUMIDITY%</span> &percnt;</span></p>

      </div>

      <div class="card pressure">

        <h4><i class="fas fa-angle-double-down"></i> PRESSURE</h4><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>

      </div>

      <div class="card gas">

        <h4><i class="fas fa-wind"></i> GAS</h4><p><span class="reading"><span id="gas">%GAS%</span> K&ohm;</span></p>

      </div>

    </div>

  </div>

<script>

if (!!window.EventSource) {

 var source = new EventSource('/events');

 

 source.addEventListener('open', function(e) {

  console.log("Events Connected");

 }, false);

 source.addEventListener('error', function(e) {

  if (e.target.readyState != EventSource.OPEN) {

    console.log("Events Disconnected");

  }

 }, false);

 

 source.addEventListener('message', function(e) {

  console.log("message", e.data);

 }, false);

 

 source.addEventListener('temperature', function(e) {

  console.log("temperature", e.data);

  document.getElementById("temp").innerHTML = e.data;

 }, false);

 

 source.addEventListener('humidity', function(e) {

  console.log("humidity", e.data);

  document.getElementById("hum").innerHTML = e.data;

 }, false);

 

 source.addEventListener('pressure', function(e) {

  console.log("pressure", e.data);

  document.getElementById("pres").innerHTML = e.data;

 }, false);

 

 source.addEventListener('gas', function(e) {

  console.log("gas", e.data);

  document.getElementById("gas").innerHTML = e.data;

 }, false);

}

</script>

</body>

</html>)rawliteral";


void setup() {

  Serial.begin(115200);


  // Set the device as a Station and Soft Access Point simultaneously

  WiFi.mode(WIFI_AP_STA);

  

  // Set device as a Wi-Fi Station

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

    delay(1000);

    Serial.println("Setting as a Wi-Fi Station..");

  }

  Serial.print("Station IP Address: ");

  Serial.println(WiFi.localIP());

  Serial.println();


  // Init BME680 sensor

  if (!bme.begin(0x76)) {

    Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));

    while (1);

  }

  Serial.println(F("BME680 sensor is working!"));

  // Set up oversampling and filter initialization

  bme.setTemperatureOversampling(BME680_OS_8X);

  bme.setHumidityOversampling(BME680_OS_2X);

  bme.setPressureOversampling(BME680_OS_4X);

  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);

  bme.setGasHeater(320, 150); // 320*C for 150 ms


  // Handle Web Server

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/html", index_html, processor);

  });


  // Handle Web Server Events

  events.onConnect([](AsyncEventSourceClient *client){

    if(client->lastId()){

      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());

    }

    // send event with message "hello!", id current millis

    // and set reconnect delay to 1 second

    client->send("hello!", NULL, millis(), 10000);

  });

  server.addHandler(&events);

  server.begin();

}


void loop() {

  if ((millis() - lastTime) > timerDelay) {

    getBME680Readings();

    Serial.printf("Temperature = %.2f ºC \n", temperature);

    Serial.printf("Humidity = %.2f % \n", humidity);

    Serial.printf("Pressure = %.2f hPa \n", pressure);

    Serial.printf("Gas Resistance = %.2f KOhm \n", gasResistance);

    Serial.println();


    // Send Events to the Web Server with the Sensor Readings

    events.send("ping",NULL,millis());

    events.send(String(temperature).c_str(),"temperature",millis());

    events.send(String(humidity).c_str(),"humidity",millis());

    events.send(String(pressure).c_str(),"pressure",millis());

    events.send(String(gasResistance).c_str(),"gas",millis());

    

    lastTime = millis();

  }

}


/*

#include <Wire.h>

#include <SPI.h>

#include <Adafruit_Sensor.h>

#include "Adafruit_BME680.h"

 

 

#define SEALEVELPRESSURE_HPA (1013.25)

 

Adafruit_BME680 bme; // I2C

 

void setup() {

  Serial.begin(115200);

  while (!Serial);

  Serial.println(F("BME680 test"));

 

  if (!bme.begin(0x76)) 

  {

    Serial.println("Could not find a valid BME680 sensor, check wiring!");

    while (1);

  }

 

  // Set up oversampling and filter initialization

  bme.setTemperatureOversampling(BME680_OS_8X);

  bme.setHumidityOversampling(BME680_OS_2X);

  bme.setPressureOversampling(BME680_OS_4X);

  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);

  bme.setGasHeater(320, 150); // 320*C for 150 ms

}

 

void loop() 

{

  if (! bme.performReading()) 

  {

    Serial.println("Failed to perform reading :(");

    return;

  }

  Serial.print("Temperature = ");

  Serial.print(bme.temperature);

  Serial.println(" *C");

 

  Serial.print("Pressure = ");

  Serial.print(bme.pressure / 100.0);

  Serial.println(" hPa");

 

  Serial.print("Humidity = ");

  Serial.print(bme.humidity);

  Serial.println(" %");

 

  Serial.print("Gas = ");

  Serial.print(bme.gas_resistance / 1000.0);

  Serial.println(" KOhms");

 

  Serial.print("Approx. Altitude = ");

  Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));

  Serial.println(" m");

 

  Serial.println();

  delay(3000);

}

*/



You also need to install the following libraries.

Follow the next instructions to install them.

Installing the BME680 Library

To get readings from the BME680 sensor module we’ll use the Adafruit_BME680 library. Follow the next steps to install the library in your Arduino IDE:

Open your Arduino IDE and go to Sketch Include Library > Manage Libraries. The Library Manager should open.

Search for “adafruit bme680 ” on the Search box and install the library.

Install BM6280 Adafruit Library Arduino IDE Library Manager

Installing the Adafruit_Sensor Library

To use the BME680 library, you also need to install the Adafruit_Sensor library. Follow the next steps to install the library in your Arduino IDE:

Go to Sketch Include Library > Manage Libraries and type “Adafruit Unified Sensor” in the search box. Scroll all the way down to find the library and install it.

Installing Adafruit Unified Sensor Driver library

Installing the ESPAsyncWebServer library

The ESPAsyncWebServer library is not available to install in the Arduino IDE Library Manager. So, you need to install it manually.

Follow the next steps to install the ESPAsyncWebServer library:

  1. Click here to download the ESPAsyncWebServer library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should get ESPAsyncWebServer-master folder
  3. Rename your folder from ESPAsyncWebServer-master to ESPAsyncWebServer
  4. Move the ESPAsyncWebServer folder to your Arduino IDE installation libraries folder

Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the library you’ve just downloaded.

Installing the ESPAsync TCP Library

The ESPAsyncWebServer library requires the ESPAsyncTCP library to work. Follow the next steps to install that library:

  1. Click here to download the ESPAsyncTCP library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should get ESPAsyncTCP-master folder
  3. Rename your folder from ESPAsyncTCP-master to ESPAsyncTCP
  4. Move the ESPAsyncTCP folder to your Arduino IDE installation libraries folder
  5. Finally, re-open your Arduino IDE

Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the library you’ve just downloaded.




ESP32 Web Server with BME680 – Weather Station (Arduino IDE)

This tutorial shows how to build a web server weather station with the ESP32 to display sensor readings from the BME680 environmental sensor: gas (air quality), temperature, humidity and pressure. The readings are updated automatically on the web server using Server-Sent Events (SSE). The ESP32 will be programmed using Arduino IDE.

ESP32 BME680 Gas sensor humidity barometric pressure ambient temperature gas air quality Arduino IDE Web Server

To build the web server we’ll use the ESP Async Web Server library that provides an easy way to build an asynchronous web server.

BME680 Environmental Sensor

The BME680 is an environmental sensor that combines gas, temperature, humidity and pressure 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

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

For more information about the BME680, read our getting started guide: ESP32: BME680 Environmental Sensor using Arduino IDE (Gas, Pressure, Humidity, Temperature).

Parts Required

ESP32 Board BME680 Gas sensor circuit wiring diagram schematics

To complete this tutorial you need the following parts:

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

Schematic – ESP32 with BME680

The BME680 can communicate using I2C or SPI communication protocols. In this tutorial, we’ll use I2C communication protocol.

Follow the next schematic diagram to wire the BME680 to the ESP32 using the default I2C pins.

ESP32 BME680 Wiring Diagram I2C

Recommended reading: ESP32 Pinout Reference – Which GPIO pins should you use?

Preparing Arduino IDE

We’ll program the ESP32 board using Arduino IDE. So, make sure you have the ESP32 add-on installed. Follow the next tutorial:

You also need to install the following libraries.

Follow the next instructions to install them.

Installing the BME680 Library

To get readings from the BME680 sensor module we’ll use the Adafruit_BME680 library. Follow the next steps to install the library in your Arduino IDE:

Open your Arduino IDE and go to Sketch Include Library > Manage Libraries. The Library Manager should open.

Search for “adafruit bme680 ” on the Search box and install the library.

Install BM6280 Adafruit Library Arduino IDE Library Manager

Installing the Adafruit_Sensor Library

To use the BME680 library, you also need to install the Adafruit_Sensor library. Follow the next steps to install the library in your Arduino IDE:

Go to Sketch Include Library > Manage Libraries and type “Adafruit Unified Sensor” in the search box. Scroll all the way down to find the library and install it.

Installing Adafruit Unified Sensor Driver library

Installing the ESPAsyncWebServer library

The ESPAsyncWebServer library is not available to install in the Arduino IDE Library Manager. So, you need to install it manually.

Follow the next steps to install the ESPAsyncWebServer library:

  1. Click here to download the ESPAsyncWebServer library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should get ESPAsyncWebServer-master folder
  3. Rename your folder from ESPAsyncWebServer-master to ESPAsyncWebServer
  4. Move the ESPAsyncWebServer folder to your Arduino IDE installation libraries folder

Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the library you’ve just downloaded.

Installing the ESPAsync TCP Library

The ESPAsyncWebServer library requires the ESPAsyncTCP library to work. Follow the next steps to install that library:

  1. Click here to download the ESPAsyncTCP library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should get ESPAsyncTCP-master folder
  3. Rename your folder from ESPAsyncTCP-master to ESPAsyncTCP
  4. Move the ESPAsyncTCP folder to your Arduino IDE installation libraries folder
  5. Finally, re-open your Arduino IDE

Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the library you’ve just downloaded.

ESP32 BME680 Web Server Code

Open your Arduino IDE and copy the following code. To make it work, you need to insert your network credentials: SSID and password.

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-bme680-sensor-arduino/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include <WiFi.h>
#include "ESPAsyncWebServer.h"

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

//Uncomment if using SPI
/*#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);

float temperature;
float humidity;
float pressure;
float gasResistance;

AsyncWebServer server(80);
AsyncEventSource events("/events");

unsigned long lastTime = 0;  
unsigned long timerDelay = 30000;  // send readings timer

void getBME680Readings(){
  // Tell BME680 to begin measurement.
  unsigned long endTime = bme.beginReading();
  if (endTime == 0) {
    Serial.println(F("Failed to begin reading :("));
    return;
  }
  if (!bme.endReading()) {
    Serial.println(F("Failed to complete reading :("));
    return;
  }
  temperature = bme.temperature;
  pressure = bme.pressure / 100.0;
  humidity = bme.humidity;
  gasResistance = bme.gas_resistance / 1000.0;
}

String processor(const String& var){
  getBME680Readings();
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return String(temperature);
  }
  else if(var == "HUMIDITY"){
    return String(humidity);
  }
  else if(var == "PRESSURE"){
    return String(pressure);
  }
 else if(var == "GAS"){
    return String(gasResistance);
  }
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>BME680 Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p {  font-size: 1.2rem;}
    body {  margin: 0;}
    .topnav { overflow: hidden; background-color: #4B1D3F; color: white; font-size: 1.7rem; }
    .content { padding: 20px; }
    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
    .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
    .reading { font-size: 2.8rem; }
    .card.temperature { color: #0e7c7b; }
    .card.humidity { color: #17bebb; }
    .card.pressure { color: #3fca6b; }
    .card.gas { color: #d62246; }
  </style>
</head>
<body>
  <div class="topnav">
    <h3>BME680 WEB SERVER</h3>
  </div>
  <div class="content">
    <div class="cards">
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> TEMPERATURE</h4><p><span class="reading"><span id="temp">%TEMPERATURE%</span> &deg;C</span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> HUMIDITY</h4><p><span class="reading"><span id="hum">%HUMIDITY%</span> &percnt;</span></p>
      </div>
      <div class="card pressure">
        <h4><i class="fas fa-angle-double-down"></i> PRESSURE</h4><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
      </div>
      <div class="card gas">
        <h4><i class="fas fa-wind"></i> GAS</h4><p><span class="reading"><span id="gas">%GAS%</span> K&ohm;</span></p>
      </div>
    </div>
  </div>
<script>
if (!!window.EventSource) {
 var source = new EventSource('/events');
 
 source.addEventListener('open', function(e) {
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);
 
 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);
 
 source.addEventListener('temperature', function(e) {
  console.log("temperature", e.data);
  document.getElementById("temp").innerHTML = e.data;
 }, false);
 
 source.addEventListener('humidity', function(e) {
  console.log("humidity", e.data);
  document.getElementById("hum").innerHTML = e.data;
 }, false);
 
 source.addEventListener('pressure', function(e) {
  console.log("pressure", e.data);
  document.getElementById("pres").innerHTML = e.data;
 }, false);
 
 source.addEventListener('gas', function(e) {
  console.log("gas", e.data);
  document.getElementById("gas").innerHTML = e.data;
 }, false);
}
</script>
</body>
</html>)rawliteral";

void setup() {
  Serial.begin(115200);

  // Set the device as a Station and Soft Access Point simultaneously
  WiFi.mode(WIFI_AP_STA);
  
  // Set device as a Wi-Fi Station
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Setting as a Wi-Fi Station..");
  }
  Serial.print("Station IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  // Init BME680 sensor
  if (!bme.begin()) {
    Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
    while (1);
  }
  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms

  // Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Handle Web Server Events
  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // send event with message "hello!", id current millis
    // and set reconnect delay to 1 second
    client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();
}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    getBME680Readings();
    Serial.printf("Temperature = %.2f ºC \n", temperature);
    Serial.printf("Humidity = %.2f % \n", humidity);
    Serial.printf("Pressure = %.2f hPa \n", pressure);
    Serial.printf("Gas Resistance = %.2f KOhm \n", gasResistance);
    Serial.println();

    // Send Events to the Web Server with the Sensor Readings
    events.send("ping",NULL,millis());
    events.send(String(temperature).c_str(),"temperature",millis());
    events.send(String(humidity).c_str(),"humidity",millis());
    events.send(String(pressure).c_str(),"pressure",millis());
    events.send(String(gasResistance).c_str(),"gas",millis());
    
    lastTime = millis();
  }
}

View raw code

Insert your network credentials in the following variables and the code will work straight away.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

How the Code Works

Read this section to learn how the code works, or skip to the next section.

Including Libraries

Start by including the necessary libraries. The Wire library is needed for I2C communication protocol. We also include the SPI library if you want to use SPI communication instead.

#include <Wire.h>
#include <SPI.h>

The Adafruit_Sensor and Adafruit_BME680 libraries are needed to interface with the BME680 sensor.

#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"

The WiFi and ESPAsyncWebServer libraries are used to create the web server.

#include <WiFi.h>
#include "ESPAsyncWebServer.h"

Network Credentials

Insert your network credentials in the following variables, so that the ESP32 can connect to your local network using Wi-Fi.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

I2C Communication

Create an Adafruit_BME680 object called bme on the default ESP32 I2C pins.

Adafruit_BME680 bme; // I2C

If you want to use SPI communication instead, you need to define the ESP32 SPI pins on the following lines (to uncomment remove the /* and */):

/*#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 15*/

And then, create an Adafruit_BME680 object using those pins (to uncomment remove the //).

//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);

Declaring Variables

The temperaturehumiditypressure and gasResistance float variables will be used to hold BME680 sensor readings.

float temperature;
float humidity;
float pressure;
float gasResistance;

The lastTime and the timerDelay variables will be used to update sensor readings every X number of seconds. As an example, we’ll get new sensor readings every 30 seconds (30000 milliseconds). You can change that delay time in the timerDelay variable.

unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

Create an Async Web Server on port 80.

AsyncWebServer server(80);

Create Event Source

To automatically display the information on the web server when a new reading arrives, we’ll use Server-Sent Events (SSE).

The following line creates a new event source on /events.

AsyncEventSource events("/events");

Server-Sent Events allow a web page (client) to get updates from a server. We’ll use this to automatically display new readings on the web server page when new BME680 readings are available.

Important: Server-sent events are not supported on Internet Explorer.

Get BME680 Readings

The getBME680Reading() function gets gas, temperature, humidity and pressure readings from the BME680 sensor and saves them on the gasResistancetemperaturehumidity and pressure variables.

void getBME680Readings(){
  // Tell BME680 to begin measurement.
  unsigned long endTime = bme.beginReading();
  if (endTime == 0) {
    Serial.println(F("Failed to begin reading :("));
    return;
  }
  if (!bme.endReading()) {
    Serial.println(F("Failed to complete reading :("));
    return;
  }
  temperature = bme.temperature;
  pressure = bme.pressure / 100.0;
  humidity = bme.humidity;
  gasResistance = bme.gas_resistance / 1000.0;
}

Processor

The processor() function replaces any placeholders on the HTML text used to build the web page with the current sensor readings.

String processor(const String& var){
  getBME680Readings();
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return String(temperature);
  }
  else if(var == "HUMIDITY"){
    return String(humidity);
  }
  else if(var == "PRESSURE"){
    return String(pressure);
  }
 else if(var == "GAS"){
    return String(gasResistance);
  }
}

This allows us to display the current sensor readings on the web page when you access it for the first time. Otherwise, you would see a blank space until new readings were available (which can take some time depending on the delay time you’ve defined on the code).

Building the Web Page

The index_html variable contains all the HTML, CSS and JavaScript to build the web page. We won’t go into detail on how the HTML and CSS works. We’ll just take a look at how to handle the events sent by the server.

Let’s take a quick look at the line that displays the temperature:

<h4><i class="fas fa-thermometer-half"></i> TEMPERATURE</h4><p><span class="reading"><span id="temp">%TEMPERATURE%</span> &deg;C</span></p>

You can see that the %TEMPERATURE% placeholder is surrounded by <span id=”temp”></span> tags. The HTML id attribute is used to specify a unique id for an HTML element.

It is used to point to a specific style or it can be used by JavaScript to access and manipulate the element with that specific id. That’s what we’re going to do.

For instance, when the web server receives a new event with the latest temperature reading, we’ll update the HTML element with the id “temp” with the new reading.

A similar process is done to update the other readings.

Handle Events

Create a new EventSource object and specify the URL of the page sending the updates. In our case, it’s /events.

if (!!window.EventSource) {
  var source = new EventSource('/events');

Once you’ve instantiated an event source, you can start listening for messages from the server with addEventListener().

These are the default event listeners, as shown here in the AsyncWebServer documentation.

source.addEventListener('open', function(e) {
  console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
 if (e.target.readyState != EventSource.OPEN) {
   console.log("Events Disconnected");
 }
}, false);

source.addEventListener('message', function(e) {
 console.log("message", e.data);
}, false);

Then, add the event listener for “temperature”.

source.addEventListener('temperature', function(e) {

When a new temperature reading is available, the ESP32 sends an event (“temperature”) to the client. The following lines handle what happens when the browser receives that event.

console.log("temperature", e.data);
document.getElementById("temp").innerHTML = e.data;

Basically, print the new readings on the browser console, and put the received data into the element with the corresponding id (“temp“) on the web page.

A similar processor is done for humidity, pressure and gas resistance.

source.addEventListener('humidity', function(e) {
  console.log("humidity", e.data);
  document.getElementById("hum").innerHTML = e.data;
}, false);
 
source.addEventListener('pressure', function(e) {
  console.log("pressure", e.data);
  document.getElementById("pres").innerHTML = e.data;
}, false);
 
source.addEventListener('gas', function(e) {
  console.log("gas", e.data);
  document.getElementById("gas").innerHTML = e.data;
}, false);

setup()

In the setup(), initialize the Serial Monitor.

Serial.begin(115200);

Connect the ESP32 to your local network and print the ESP32 IP address.

// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.println();

Initialize the BME680 sensor.

// Init BME680 sensor
if (!bme.begin()) {
  Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
  while (1);
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms

Handle Requests

When you access the ESP32 IP address on the root / URL, send the text that is stored on the index_html variable to build the web page and pass the processor as argument, so that all placeholders are replaced with the latest sensor readings.

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});

Server Event Source

Set up the event source on the server.

// Handle Web Server Events
events.onConnect([](AsyncEventSourceClient *client){
  if(client->lastId()){
    Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
  }
  // send event with message "hello!", id current millis
  // and set reconnect delay to 1 second
  client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);

Finally, start the server.

server.begin();

loop()

In the loop(), get new sensor readings:

getBME680Readings();

Print the new readings in the Serial Monitor.

Serial.printf("Temperature = %.2f ºC \n", temperature);
Serial.printf("Humidity = %.2f % \n", humidity);
Serial.printf("Pressure = %.2f hPa \n", pressure);
Serial.printf("Gas Resistance = %.2f KOhm \n", gasResistance);
Serial.println();

Finally, send events to the browser with the newest sensor readings to update the web page.

// Send Events to the Web Server with the Sensor Readings
events.send("ping",NULL,millis());
events.send(String(temperature).c_str(),"temperature",millis());
events.send(String(humidity).c_str(),"humidity",millis());
events.send(String(pressure).c_str(),"pressure",millis());
events.send(String(gasResistance).c_str(),"gas",millis());

The following diagram summarizes how Server-Sent Events work to update the web page.

Update BME680 Web Server Readings ESP32 with Arduino IDE using Server Sent Events

Uploading the Code

Now, upload the code to your ESP32. Make sure you have the right board and COM port selected.

After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 on-board RST/EN button. The ESP32 IP address should be printed in the serial monitor.

ESP32 IP Address Serial Monitor Arduino IDE

Demonstration

Open a browser in your local network and type the ESP32 IP address. You should get access to the ESP32 web server with the latest BME680 readings.

ESP32 BME680 Gas sensor Web Server Demonstration

The readings are updated automatically using Server-Sent Events.

ESP32 or ESP8266 NodeMCU Board Web Server Demonstration with BME680 Gas sensor

Wrapping Up

In this tutorial you’ve learned how to build an asynchronous web server weather station with the ESP32 to display BME680 sensor readings – gas (air quality), temperature, humidity and pressure – and how to update the readings automatically on the web page using Server-Sent Events.

沒有留言:

張貼留言

Messaging API作為替代方案

  LINE超好用功能要沒了!LINE Notify明年3月底終止服務,有什麼替代方案? LINE Notify將於2025年3月31日結束服務,官方建議改用Messaging API作為替代方案。 //CHANNEL_ACCESS_TOKEN = 'Messaging ...