01/2018
The ESP8266 WiFi barometer with
BMP280 sensor and Arduino software
New look
- Wi-Fi Protected Setup / PBC
- mDNS responder
- HTTP BASIC authentication
- simply OTA from browser with password
- for any classic ESP8266-12
- displays minimum and maximum pressure
- it also shows the pressure trend over the last three hours
- settings for indoor and outdoor use.
// Arduino WiFi Barometer as HTTP server - V5.5 / 2018-03-18
// For ESP8266 and BMP280 sensor
// Copyright 2018 Pavel Janko, www.fancon.cz
// MIT license, http://opensource.org/licenses/MIT
// Tested with ARDUINO IDE 1.8.5, Arduino-ESP8266 2.4.1
//
// Properties:
// - WiFi Protected Setup with PBC ready
// - mDNS responder for connect by name
// - HTTP BASIC authentication
// - WiFi auto reconnect with restart
// - Supports simply OTA from browser with password
// (with at least 1MB of memory for OTA)
// - Improved barometric formula
// - Minimal and maximal pressure
// - Dynamic trend of pressure in interval three hours
//
// Added libraries:
// Adafruit Unified Sensor by Adafruit 1.0.2
// Adafruit BMP280 Library by Adafruit 1.0.2
// After flash from serial restart the device !!!
//
// Connection :
// ESP8266-12 ------------------------------------------
// Vcc -> Power 3.3V
// GND -> Power GND
// CH_PD -> Vcc
// RESET -> resistor 10Kohms to Vcc and switch to GND
// GPIO0 -> resistor 10Kohms to Vcc and switch with
// serial resistor around 330ohms to GND
// GPIO2 -> resistor 10Kohms to Vcc
// GPIO13 -> LED with serial resistor around 330ohms to GND
// GPIO15 -> resistor 10Kohms to GND
// Vcc -> Vcc BMP280
// GND -> GND BMP280
// GPIO4 -> SDA BMP280
// GPIO5 -> SCL BMP280
// --------------------------------------------------------
//
// How to:
// For local altitude correction, set LOCALALTUD in meters
//
// To connect to an access point, hold down the button (switch on GPIO0) for about
// three seconds (LED on GPIO13 is lit permanently) and activate WPS
// (Wi-Fi Protected Setup) with PBC (Push Button Configuration) on your Access point.
//
// For mDNS install Bonjour to your Windows - https://support.apple.com/bonjour
// or Avahi for your Linux - https://www.avahi.org/
// mDNS name is defined as "barometer" below here. Just enter http://barometer.local
// into the browser. But in my case, mDNS is sometimes
// slower than a direct IP address.
//
// For web authentication set #define WWWAUTHENTI true and
// your WWWUSERNAME and WWWPASSWORD below only.
//
// For OTA activation set OTAUSER, OTAPASSWORD and then in operation
// write to browser address row http://barometer.local/firmware and upload your *.bin
// file.
// Default users and passwords are all "admin"
//
// |--------------------------------------------------------------------|
// | Display | Value |
// |--------------------------------------------------------------------|
// | red marks | Maximum and minimum measured pressure |
// | yellow mark | The difference between the yellow mark |
// | | on the scale and pointer show trend of |
// | | pressure in interval three hours. |
// | olive green dot | Standard atmospheric pressure 1013.25 hPa |
// |--------------------------------------------------------------------|
//
// |--------------------------------------------------------------------|
// | Button - GPIO0 | LED - GPIO13| Action |
// |--------------------------------------------------------------------|
// | short press | ON or OFF | --- |
// | long press > 3sec. | ON | wait for WPS from Access point |
// | --- | flashes 4Hz | connection to WiFi |
// | before power on | --- | flash mode |
// |--------------------------------------------------------------------|
//
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
#include <Wire.h>
#include <Ticker.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
#define LOCALALTUD 208.0 // [m] Set your local altitude in meters
#define INSIDE true // true = sensor inside, false = sensor outdoor
#define STDTEMP 15.0 // [Celsius] Standard temperature
#define STDPRESSURE 1013.25 // [hPa] Standard atmosferic pressure
#define LENGTH 18 // length of string with old pressure data
#define WWWPAGEREFR 30 // [s] Web page refresh time [s]
#define WWWAUTHENTI false // Set true for web authentication
#define WWWUSERNAME "admin" // Set www user name
#define WWWPASSWORD "admin" // Set www user password
#define OTAUSER "admin" // Set OTA user
#define OTAPASSWORD "admin" // Set OTA password
#define OTAPATH "/firmware"// Set path for update
#define MDNSNAME "barometer"// Set local mDNS name
#define SERVERPORT 80 // Server port
#define ROUNDING 1
#define BUTTONPIN 0 // GPIO0 button pin
#define LEDPIN 13 // GPIO13 LED
#define SDAI2CPIN 4 // GPIO4 I2C SDA bus
#define SCLI2CPIN 5 // GPIO5 I2C SCL bus
#define BMP280ADDR 0x76 // Set BMP280 I2C address
#define BUTTONTIME 0.25 // [s] Periodic time for button read
#define SENSORTIME 10.0 // [s] Periodic time for sensor read
#define WIFITIME 10.0 // [s] Periodic time for WiFi test
#define TRENDTIME 600.0 // [s] Periodic time for calculation of trend
bool ButtonFlag = false;
bool SensorFlag = false;
bool WifiFlag = false;
bool TrendFlag = false;
bool LedState = false; // LED off
char ButtonCount = 0;
float MaxPress = 95700.0;
float MinPress = 106300.0;
float SeaPressure;
float Temperature;
float Trend;
float PointerAngle;
float TrendAngle;
float StdPssAngle;
float MaxAngle;
float MinAngle;
float OldPressure[LENGTH];
ESP8266WebServer HttpServer(SERVERPORT);
ESP8266HTTPUpdateServer httpUpdater;
Ticker ButtonTick; // Preparing for periodic button reading
Ticker SensorTick; // preparing for periodic sensor reading
Ticker WifiTick; // preparing for periodic test WiFi connection
Ticker TrendTick; // preparing for periodic trend calculation
Adafruit_BMP280 bmp280;
//-----------------------------------------------------------------
void setup(void) {
WiFi.begin(); // Disable this line only if you can not use WPS
//WiFi.persistent(false);// Enable this three lines only if you can not use WPS
//WiFi.mode(WIFI_STA); // -"-
//WiFi.begin(YourSSID,YourPassword); // -"-
Wire.begin(SDAI2CPIN, SCLI2CPIN);
bmp280.begin(BMP280ADDR);
pinMode(BUTTONPIN, INPUT);
pinMode(LEDPIN, OUTPUT);
/* wait for WiFi connect */
while (WiFi.status() != WL_CONNECTED) {
GetButton();
delay(250);
LedSet(!LedState);
}
LedSet(false); //LED OFF
/* prepare first data for trend calculation */
for (int i = 0; i < LENGTH; i++)TrendCalc();
/* position of the standard pressure indicator */
StdPssAngle = (30.0 + (STDPRESSURE - 960.0) * 3.0);
/* set interupt timer */
ButtonTick.attach(BUTTONTIME, ButtonFlagSet);
SensorTick.attach(SENSORTIME, SensorFlagSet);
WifiTick.attach(WIFITIME, WifiFlagSet);
TrendTick.attach(TRENDTIME, TrendFlagSet);
httpUpdater.setup(&HttpServer, OTAPATH, OTAUSER, OTAPASSWORD);
MDNS.begin(MDNSNAME);
HttpServer.on("/", HTTP_GET, ModifySendPage);
HttpServer.onNotFound(handleNotFound);
HttpServer.begin();
}
//-----------------------------------------------------------------
void loop(void) {
HttpServer.handleClient(); // Listen for HTTP requests from clients
if (ButtonFlag) GetButton(); // Periodic serve button
if (SensorFlag) SensorRead(); // Periodic sensor reading
if (WifiFlag) WifiTest(); // Periodic test WiFi connection
if (TrendFlag) TrendCalc(); // Periodic save trend data
}//-----------------------------------------------------------------
void ModifySendPage(void) {
if (WWWAUTHENTI == true) {
/* request for www user/password from client */
if (!HttpServer.authenticate(WWWUSERNAME, WWWPASSWORD))
return HttpServer.requestAuthentication();
}
SendPage();
}
void SendPage(void) {
String buff = "<!DOCTYPE HTML><html><head><meta charset=\"UTF-8\"><meta http-equiv=\"refresh\" content=\"" + String(WWWPAGEREFR) + "\">\n";
buff += "<title>Barometer</title></head><body style=\"background:#dcdcdc\">\n";
buff += "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"1000\" height=\"1000\">\n";
buff += "<defs><linearGradient id=\"g1\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n";
buff += "<stop offset=\"0%\" style=\"stop-color:#888;stop-opacity:1\"/>\n";
buff += "<stop offset=\"50%\" style=\"stop-color:#ccc;stop-opacity:1\"/>\n";
buff += "<stop offset=\"100%\" style=\"stop-color:#888;stop-opacity:1\"/></linearGradient>\n";
buff += "<linearGradient id=\"g2\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n";
buff += "<stop offset=\"0%\" style=\"stop-color:#b0a080;stop-opacity:1\"/>\n";
buff += "<stop offset=\"50%\" style=\"stop-color:#f0e0c0;stop-opacity:1\"/>\n";
buff += "<stop offset=\"100%\" style=\"stop-color:#b0a080;stop-opacity:1\"/></linearGradient>\n";
buff += "<g id=\"PO\"><path d=\"M495 440A12 12 0 1 1 505 440L505 440 503 710 500 720 497 710z\" fill=\"black\"/>\n";
buff += "<circle cx=\"500\" cy=\"500\" r=\"10\" stroke=\"black\" stroke-width=\"3\" fill=\"#CD7F32\"/>\n";
buff += "<circle cx=\"500\" cy=\"500\" r=\"5\" fill=\"#B5A642\"/>\n";
buff += "<line stroke-width=\"2\" id=\"S\" x1=\"500\" y1=\"725\" x2=\"500\" y2=\"750\"/>\n";
buff += "<line stroke-width=\"2\" id=\"M\" x1=\"500\" y1=\"720\" x2=\"500\" y2=\"750\"/>\n";
buff += "<line stroke-width=\"2\" id=\"L\" x1=\"500\" y1=\"705\" x2=\"500\" y2=\"750\"/></g>\n";
buff += "<g id=\"T\"><use xlink:href=\"#L\" transform=\"rotate(30,500,500)\"/>\n";
buff += "<g id=\"D\"><use xlink:href=\"#S\" transform=\"rotate(33,500,500)\"/>\n";
buff += "<use xlink:href=\"#S\" transform=\"rotate(36,500,500)\"/>\n";
buff += "<use xlink:href=\"#S\" transform=\"rotate(39,500,500)\"/></g>\n";
buff += "<use xlink:href=\"#S\" transform=\"rotate(42,500,500)\"/>\n";
buff += "<use xlink:href=\"#M\" transform=\"rotate(45,500,500)\"/>\n";
buff += "<use xlink:href=\"#S\" transform=\"rotate(48,500,500)\"/>\n";
buff += "<use xlink:href=\"#D\" transform=\"rotate(18,500,500)\"/></g>\n";
buff += "<circle id=\"SP\" cx=\"500\" cy=\"756\" r=\"5\" fill=\"#808000\"/>\n";
buff += "<path id=\"TR\" d=\"M500 750 L495 760 L505 760 Z\"/></defs>\n";
buff += "<circle cx=\"500\" cy=\"500\" r=\"294\" fill=\"url(#g1)\" transform=\"rotate(-45,500,500)\"/>\n";
buff += "<circle cx=\"500\" cy=\"500\" r=\"282\" fill=\"url(#g1)\" transform=\"rotate(45,500,500)\"/>\n";
buff += "<circle cx=\"500\" cy=\"500\" r=\"267\" fill=\"url(#g2)\" transform=\"rotate(45,500,500)\"/>\n";
buff += "<g stroke=\"black\" stroke-width=\"2\" fill=\"none\">\n";
buff += "<rect x=\"450\" y=\"590\" rx=\"5\" ry=\"5\" width=\"100\" height=\"40\"/>\n";
buff += "<rect x=\"450\" y=\"635\" rx=\"5\" ry=\"5\" width=\"100\" height=\"40\"/></g>\n";
buff += "<g stroke=\"black\"><use xlink:href=\"#D\" transform=\"rotate(-12,500,500)\"/>\n";
buff += "<g id=\"TT\"><use xlink:href=\"#T\"/>\n";
buff += "<use xlink:href=\"#T\" transform=\"rotate(30,500,500)\"/>\n";
buff += "<use xlink:href=\"#T\" transform=\"rotate(60,500,500)\"/>\n";
buff += "<use xlink:href=\"#T\" transform=\"rotate(90,500,500)\"/>\n";
buff += "<use xlink:href=\"#T\" transform=\"rotate(120,500,500)\"/></g>\n";
buff += "<use xlink:href=\"#TT\" transform=\"rotate(150,500,500)\"/>\n";
buff += "<use xlink:href=\"#L\" transform=\"rotate(330,500,500)\"/>\n";
buff += "<use xlink:href=\"#D\" transform=\"rotate(300,500,500)\"/></g>\n";
buff += "<g font-family=\"arial\" font-size=\"28\" font-weight=\"normal\" font-style=\"italic\" fill=\"black\" >\n";
buff += "<text x=\"330\" y=\"600\">970</text>\n";
buff += "<text x=\"305\" y=\"500\">980</text>\n";
buff += "<text x=\"330\" y=\"400\">990</text>\n";
buff += "<text x=\"390\" y=\"350\">1000</text>\n";
buff += "<text x=\"465\" y=\"320\">1010</text>\n";
buff += "<text x=\"540\" y=\"350\">1020</text>\n";
buff += "<text x=\"605\" y=\"400\">1030</text>\n";
buff += "<text x=\"635\" y=\"500\">1040</text>\n";
buff += "<text x=\"610\" y=\"600\">1050</text>\n";
buff += "<text x=\"470\" y=\"740\">hPa</text>\n";
buff += "<text x=\"455\" y=\"620\" fill=\"#800000\">" + String(SeaPressure / 100, ROUNDING) + "</text>\n";
buff += "<text x=\"555\" y=\"620\" font-size=\"24\" >hPa</text>\n";
buff += "<text x=\"470\" y=\"665\" fill=\"#800000\">" + String(Temperature, ROUNDING) + "</text>\n";
buff += "<text x=\"560\" y=\"665\" font-size=\"24\">C</text>\n";
/* grade marker */
buff += "<circle cx=\"560\" cy=\"645\" r=\"3\" fill=\"transparent\" stroke=\"black\" stroke-width=\"2\"/></g>\n";
/* mark of standard pressure */
buff += "<use xlink:href=\"#SP\" fill=\"#8b0000\" transform=\"rotate(" + String(StdPssAngle) + ",500,500)\"/>\n";
/* minimum mark */
buff += "<use xlink:href=\"#TR\" fill=\"#8b0000\" transform=\"rotate(" + String(MinAngle) + ",500,500)\"/>\n";
/* maximum mark */
buff += "<use xlink:href=\"#TR\" fill=\"#8b0000\" transform=\"rotate(" + String(MaxAngle) + ",500,500)\"/>\n";
/* trend mark */
buff += "<use xlink:href=\"#TR\" fill=\"#daa520\" transform=\"rotate(" + String(TrendAngle) + ",500,500)\"/>\n";
buff += "<use xlink:href=\"#PO\" transform=\"rotate(" + String(PointerAngle) + ",500,500)\"/></svg></body></html>\n";
HttpServer.send(200, "text/html", buff);
}
/* Send HTTP status 404 Not Found */
void handleNotFound(void) {
HttpServer.send(404, "text/plain", "404: Not found");
}
/* Read button on GPIO0*/
void GetButton(void) {
/* short press butoon to change state of LED */
if (digitalRead(BUTTONPIN) == false ) ++ButtonCount;
if (digitalRead(BUTTONPIN) == true && ButtonCount > 1 && ButtonCount < 12 ) {
LedSet(!LedState); // change LED
ButtonCount = 0;
}
/* long press button - WPS mode */
if (ButtonCount > 12) {
LedSet(!LedState); // LED change
ButtonTick.detach(); // Stop Tickers
SensorTick.detach();
WifiTick.detach();
TrendTick.detach();
/* Wait for release button */
while (!digitalRead(BUTTONPIN))yield();
delay(100);
StartWpsWaiting();
}
if (digitalRead(BUTTONPIN) == true ) ButtonCount = 0;
ButtonFlag = false;
}
/* Service Wi-Fi Protected Setup with Push Button Configuration */
void StartWpsWaiting(void) {
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
bool RafHo = false;
String HowDelka = "";
/* Wait for access point WPS */
do {
RafHo = WiFi.beginWPSConfig();
HowDelka = WiFi.SSID();
}
while (RafHo == false || HowDelka.length() == 0);
delay(500);
ESP.restart();
}
/* If disconnected WiFi then restart */
void WifiTest(void) {
if (WiFi.status() != WL_CONNECTED)ESP.restart();
WifiFlag = false;
}
void SensorRead(void) {
Temperature = bmp280.readTemperature();
float Pressure = bmp280.readPressure();
if (INSIDE) {
SeaPressure = Pressure / pow(1.0 - 0.0065 * LOCALALTUD / (STDTEMP + 273.15), 5.255); // as BOSCH formula
}
else {
SeaPressure = Pressure / pow(1.0 - 0.0065 * LOCALALTUD / (Temperature + 273.15), 5.255); // ICAO formula
}
if (SeaPressure > MaxPress)MaxPress = SeaPressure;
if (SeaPressure < MinPress)MinPress = SeaPressure;
PointerAngle = (30.0 + (SeaPressure / 100.0 - 960.0) * 3.0);
MaxAngle = (30.0 + (MaxPress / 100.0 - 960.0) * 3.0);
MinAngle = (30.0 + (MinPress / 100.0 - 960.0) * 3.0);
TrendAngle = (30.0 + (Trend / 100.0 - 960.0) * 3.0);
SensorFlag = false;
}
void TrendCalc(void) {
delay(10);
SensorRead();
OldPressure[LENGTH - 1] = SeaPressure;
for (int i = 0; i < (LENGTH - 1); i++) {
OldPressure[i] = OldPressure[i + 1];
}
Trend = OldPressure[0];
TrendFlag = false;
}
/* set LED */
void LedSet(bool SetLedState) {
LedState = SetLedState;
digitalWrite(LEDPIN, LedState);
}
void ButtonFlagSet(void) {
ButtonFlag = true;
}
void SensorFlagSet(void) {
SensorFlag = true;
}
void WifiFlagSet(void) {
WifiFlag = true;
}
void TrendFlagSet(void) {
TrendFlag = true;
}