Saturday, September 2, 2017

Exposing Data in a Web Page

In a previous post, BME-280 data was exposed as JSON, and that data was made available using a simple API. Once the web server was created and API URL paths were specified, exposing sensor data as JSON became just a problem in string manipulation - we just constructed a string that contained the data, and that string was in valid JSON format.

This same trick can be applied to exposing data in an HTML page - we'll construct a string that includes the sensor data, and that string will be a valid HTML5 document. We return that string with mime type text/html, and it will be rendered in a browser!

Why stop with HTML? We can also use string manipulation to include CSS and JavaScript!

Our goal is to have the ESP8266 serve a page that:

  • Presents temperature, humidity, and barometric pressure from the BME-280 sensor
  • Includes CSS that makes the page somewhat attractive on both notebook computers and iPhones
  • Has button that when clicked will call a JavaScript function that refreshes the page

We could add this to the program in that previous post, but to keep things simple, this functionality will be put in a separate program.

Here's the program:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
 * BME280-WebServer-HTML
 * 
 * By: Mike Klepper
 * Date: 2 September 2017
 * 
 * This program exposes BME-280 data in a web page.
 * 
 * See patriot-geek.blogspot.com
 * for explanation.
 */

#include "ESP8266WiFi.h"
#include "WiFiClient.h"
#include "ESP8266WebServer.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"

const char* SSID = "**********";
const char* PASSWORD = "**********";

const int DELAY = 3000;
const int STARTUP_DELAY = 500;

const char* MESSAGE_404 = "404 Not Found";
const char* MESSAGE_503 = "503 Service Unavailable";

boolean sensorAvailable;

ESP8266WebServer server(80);
Adafruit_BME280 bme;

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

  // Start the BME280 sensor
  if(!bme.begin())
  {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    sensorAvailable = false;
  }
  else
  {
    sensorAvailable = true;
    delay(STARTUP_DELAY);
  }

  WiFi.begin(SSID, PASSWORD);

  // Wait for the connection
  while(WiFi.status() != WL_CONNECTED) 
  {
    delay(STARTUP_DELAY);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(SSID);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  server.on("/", returnHtml);
  server.on("", returnHtml);
  
  server.onNotFound(return404Error);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void)
{
  server.handleClient();
  yield();
}

void return404Error()
{
  server.send(404, "text/plain", MESSAGE_404);
}

void return503Error()
{
  server.send(500, "text/plain", MESSAGE_503);
}

void returnHtml()
{
  if(sensorAvailable)
  {
    // Get values
    float tempC = bme.readTemperature();
    float humidity = bme.readHumidity();
    float pressurePascals = bme.readPressure();
  
    // Convert to British units
    float tempF = 9.0/5.0 * tempC + 32.0;
    float pressureInchesOfMercury = 0.000295299830714 * pressurePascals;
  
    // Build HTML response
    String responseHtml = "";
    responseHtml += "<!DOCTYPE html>";
    responseHtml += "<html>";
    responseHtml += "    <head>";
    responseHtml += "        <meta charset=\"UTF-8\">";
    responseHtml += "        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
    responseHtml += "        <title>BME-280 Sensor Data</title>";
    responseHtml += "        <style>";
    responseHtml += "            body {font-family: sans-serif}";
    responseHtml += "            h1 {font-size: 1.0cm}";
    responseHtml += "            p {font-size: 0.50cm}";
    responseHtml += "            button {font-size: 1.0cm}";
    responseHtml += "        </style>";
    responseHtml += "        <script type=\"text/javascript\">";
    responseHtml += "        function refreshPage()";
    responseHtml += "        {";
    responseHtml += "            location.reload(true);";
    responseHtml += "        }";
    responseHtml += "        </script>";
    responseHtml += "    </head>";
    responseHtml += "    <body>";
    responseHtml += "        <h1>BME-280 Sensor Data</h1>";
    responseHtml += "        <p>Temperature: " + String(tempF) + "&deg; F</p>";
    responseHtml += "        <p>Humidity: " + String(humidity) + "%</p>";
    responseHtml += "        <p>Pressure: " + String(pressureInchesOfMercury) + " inHg</p>";
    responseHtml += "        <button type=\"button\" onclick=\"refreshPage()\">Refresh</button>";
    responseHtml += "    </body>";
    responseHtml += "</html>";
  
    // Return HTML with correct MIME type
    server.send(200, "text/html; charset=utf-8", responseHtml);
  }
  else
  {
    return503Error();
  }
}

Once the program is running, the IP address that the network assigns to the ESP8266 is displayed in the serial monitor. For example, my IP address is (currently) 192.168.1.8. Open a browser and go to http://192.168.1.8, and the following will be displayed:

On the iPhone, the result is:

Click or tap the "Refresh" button and the page does indeed refresh.

This program functions very similar to the one in the previous post, so I won't do a code walk-through.

Most any web developer will complain about the way I mixed HTML, CSS, and JavaScript in one file. But that is the attitude of one who has the luxury of great bandwidth and fast servers, and we are not in that position.

No comments:

Post a Comment