Created
January 30, 2021 11:08
-
-
Save mohclips/3bac5de34f649dbac944fc23a7762342 to your computer and use it in GitHub Desktop.
A temperature, pressure, humidity sensor (BME280), with webserver, syslog and mqtt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Based in part on https://lastminuteengineers.com/bme280-esp8266-weather-station/ | |
* | |
* massively tweaked by me :) | |
*/ | |
/* | |
* BME280 Room Sensors | |
* | |
* 00F50507 - room1 | |
*/ | |
/* | |
* 21:57:06.588 -> I2C device found at address 0x76 ! BMP280 or BME280 or BME680 or MS5607,MS5611,MS5637 | |
* 21:57:06.588 -> Device ID=60 = BME280 | |
*/ | |
#include <ESP8266WebServer.h> | |
#include <Wire.h> | |
#include <Adafruit_Sensor.h> | |
#include <Adafruit_BME280.h> | |
#include <PubSubClient.h> | |
#include <NTPClient.h> | |
#include <WiFiUdp.h> | |
#include <Syslog.h> | |
// ================================================================================================ | |
#define MY_SERVER "172.19.5.6" | |
// ================================================================================================ | |
//https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer | |
WiFiClient espClient; | |
/*Put your SSID & Password*/ | |
const char* ssid = "House"; // Enter SSID here | |
const char* password = "Password1!"; // Enter Password here | |
// ================================================================================================ | |
// BME280 I2C address is 0x76(108) | |
#define Addr 0x76 | |
Adafruit_BME280 bme; | |
// Altitude does not work very well | |
//#define SEALEVELPRESSURE_HPA (1013.25) | |
// how long betweeen reads of the BME280 | |
#define READ_SENSOR_DELAY 1000*60 // millisecs, so 60secs | |
// globals for the sensor data at each read | |
float temperature, humidity, pressure, altitude; | |
// ================================================================================================ | |
// blinken lights | |
int LED = D7; | |
// ================================================================================================ | |
PubSubClient mqtt_client(espClient); | |
#define mqtt_server MY_SERVER // Change this | |
#define mqtt_port 1883 | |
#define mqtt_user "your_username" // If needed | |
#define mqtt_password "your_password" // if needed | |
// https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/ | |
// no leading slash | |
// sensor/chip_id/temperature | |
// sensor/chip_id/..... | |
// How big are our topics | |
// 6 + 1 + 8 + 1 + len(humidity/temperature/pressure/wifi_strength) + \0 | |
char humidity_topic[16+8+1+1]; | |
char temperature_topic[16+11+1+1]; | |
char pressure_topic[16+8+1+1]; | |
char wifi_topic[16+13+1+1]; | |
char mqtt_client_id[23]; // v3.1 standard | |
char chip_id[10]; // ESP8266 id | |
// ================================================================================================ | |
ESP8266WebServer http_server(80); | |
// ================================================================================================ | |
WiFiUDP ntpUDP; | |
// You can specify the time server pool and the offset (in seconds, can be | |
// changed later with setTimeOffset() ). Additionaly you can specify the | |
// update interval (in milliseconds, can be changed using setUpdateInterval() ). | |
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 0, 60000); | |
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv | |
#define TIME_ZONE_DEFINITION "GMT0BST,M3.5.0/1,M10.5.0" | |
static char currentDate[21] = {'\0'}; | |
char dateFormat[] = "%04d-%02d-%02d %02d:%02d:%02d"; // adjust the currentDate length if this is changed | |
// ================================================================================================ | |
// Syslog server connection info | |
#define SYSLOG_SERVER MY_SERVER | |
#define SYSLOG_PORT 514 | |
// A UDP instance to let us send and receive packets over UDP | |
WiFiUDP udpClient; | |
// Create a new empty syslog instance | |
Syslog syslog(udpClient, SYSLOG_PROTO_IETF); | |
// ###################################################################################### | |
// | |
// | |
void log_event(int severity, char *app, char *msg) { | |
char buff[32]; | |
Serial.print(app); | |
Serial.print(" "); | |
Serial.println(msg); | |
snprintf(buff,sizeof(buff),"%s[1]:",app); // make more syslog like application/process | |
/* | |
2020-07-10 21:33:28 00F50507 sensor[1]: Setup complete | |
2020-07-10 21:33:28 00F50507 mqtt[1]: Attempting MQTT connection ... | |
*/ | |
syslog.appName(buff).logf(severity, msg); | |
} | |
void log_error(char *app, char *msg) { | |
log_event(LOG_ERR, app, msg); | |
} | |
void log_info(char *app, char *msg) { | |
log_event(LOG_INFO, app, msg); | |
} | |
void setup() { | |
char buff[64]; | |
Serial.begin(115200); | |
delay(100); | |
Serial.println(); | |
Serial.println(); | |
pinMode(LED, OUTPUT); // Make LED pin D7 an output pin | |
bme.begin(Addr); | |
// chip-id - which becomes the client id on mqtt | |
snprintf(chip_id,sizeof(chip_id),"%08X",ESP.getChipId()); | |
Serial.print("*ESP8266 Chip id = "); | |
Serial.println(chip_id); | |
// wifi | |
do_wifi_setup(); | |
// prepare syslog configuration here (can be anywhere before first call of | |
// log/logf method) | |
syslog.server(SYSLOG_SERVER, SYSLOG_PORT); | |
syslog.deviceHostname(chip_id); | |
syslog.appName("default"); | |
syslog.defaultPriority(LOG_KERN); | |
log_info("setup","Running"); | |
// ntp | |
timeClient.begin(); | |
setenv("TZ", TIME_ZONE_DEFINITION, 1); | |
tzset(); | |
// http server | |
http_server.on("/", handle_OnConnect); | |
http_server.onNotFound(handle_NotFound); | |
http_server.begin(); | |
log_info("http","server started"); | |
// mqtt client id | |
snprintf(mqtt_client_id,sizeof(mqtt_client_id),"esp8266_sensor_%s",chip_id); | |
snprintf(buff,sizeof(buff),"client: %s",mqtt_client_id); | |
log_info("mqtt",buff); | |
// mqtt destination topics | |
Serial.println("mqtt topics"); | |
snprintf(humidity_topic, sizeof(humidity_topic),"sensor/%s/humidity",chip_id); | |
snprintf(temperature_topic,sizeof(temperature_topic),"sensor/%s/temperature",chip_id); | |
snprintf(pressure_topic,sizeof(pressure_topic),"sensor/%s/pressure",chip_id); | |
snprintf(wifi_topic,sizeof(wifi_topic),"sensor/%s/wifi_strength",chip_id); | |
log_info("mqtt",humidity_topic); | |
log_info("mqtt",temperature_topic); | |
log_info("mqtt",pressure_topic); | |
log_info("mqtt",wifi_topic); | |
// connect to MQTT | |
snprintf(buff,sizeof(buff),"Connecting to server: %s on port %d",mqtt_server, mqtt_port); | |
log_info("mqtt",buff); | |
mqtt_client.setServer(mqtt_server, mqtt_port); | |
log_info("setup","complete"); | |
Serial.println(); | |
} | |
// ###################################################################################### | |
// | |
// | |
void do_wifi_setup() { | |
delay(10); | |
Serial.println(); | |
Serial.print("WiFi MAC: "); | |
Serial.println(WiFi.macAddress()); | |
Serial.println("Connecting to "); | |
Serial.println(ssid); | |
//connect to your local wi-fi network | |
WiFi.begin(ssid, password); | |
//check wi-fi is connected to wi-fi network | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(1000); | |
Serial.print("."); | |
} | |
Serial.println(); | |
Serial.println("WiFi connected..!"); | |
Serial.print("Got IP: "); | |
Serial.println(WiFi.localIP()); | |
long rssi = WiFi.RSSI(); | |
Serial.print("RSSI: "); // print the received signal strength: | |
Serial.println(rssi); | |
} | |
// ###################################################################################### | |
// | |
// | |
void mqtt_reconnect() { | |
char buff[32]; | |
// Loop until we're reconnected | |
while (!mqtt_client.connected()) { | |
log_info("mqtt","Attempting connection..."); | |
// Attempt to connect | |
// If you want to use a username and password, change next line to | |
// if (mqtt_client.connect("ESP8266Client", mqtt_user, mqtt_password)) { | |
if (mqtt_client.connect(mqtt_client_id)) { | |
log_info("mqtt","Connected"); | |
do_read_sensor("timer"); // initial publish once we have a connection | |
} else { | |
snprintf(buff,sizeof(buff),"failed, rc=%d",mqtt_client.state()); | |
log_error("mqtt",buff); | |
delay(5000); | |
} | |
} | |
} | |
/*######################################################################### | |
# Loop | |
# | |
*/ | |
void loop() { | |
static unsigned long last_time = 0; | |
http_server.handleClient(); | |
if (!mqtt_client.connected()) { | |
mqtt_reconnect(); | |
} | |
mqtt_client.loop(); | |
if(millis() - last_time > READ_SENSOR_DELAY) { | |
do_read_sensor("timer"); | |
do_blink_led(); | |
last_time = millis(); | |
} | |
} | |
// ###################################################################################### | |
// | |
// | |
char * get_timestamp() { | |
unsigned long epochTime = timeClient.getEpochTime(); | |
struct tm *ptm = localtime ((time_t *)&epochTime); // localtime so we can use the TimeZone we set | |
sprintf(currentDate, dateFormat, | |
ptm->tm_year+1900, | |
ptm->tm_mon+1, | |
ptm->tm_mday, | |
ptm->tm_hour, | |
ptm->tm_min, | |
ptm->tm_sec); | |
return currentDate; | |
} | |
// ###################################################################################### | |
// | |
// | |
void do_blink_led() { | |
int blink_delay = 75; | |
digitalWrite(LED, HIGH); // LED on | |
delay(blink_delay); | |
digitalWrite(LED, LOW); // LED off | |
delay(blink_delay); | |
digitalWrite(LED, HIGH); // LED on | |
delay(blink_delay); | |
digitalWrite(LED, LOW); // LED off | |
} | |
// ###################################################################################### | |
// | |
// | |
void do_read_sensor(char *caller) { | |
boolean ok; | |
boolean failed; | |
char sensor_result[15]; // where to store the float to char conversion | |
char buff[55]; | |
timeClient.update(); | |
Serial.println(get_timestamp()); | |
temperature = bme.readTemperature(); | |
humidity = bme.readHumidity(); | |
pressure = bme.readPressure() / 100.0F; | |
// Altitude does not work very well. | |
//altitude = bme.readAltitude(SEALEVELPRESSURE_HPA); | |
Serial.print("Called by : "); | |
Serial.println(caller); | |
/* | |
Serial.print("Temperature in Celsius : "); | |
Serial.print(temperature); | |
Serial.println(" C"); | |
Serial.print("Pressure : "); | |
Serial.print(pressure); | |
Serial.println(" hPa"); | |
Serial.print("Relative Humidity : "); | |
Serial.print(humidity); | |
Serial.println(" RH"); | |
*/ | |
/* | |
Serial.print("Altitude : "); | |
Serial.print(altitude); | |
Serial.println(" m"); | |
*/ | |
if (caller == "timer") { | |
failed=false; | |
dtostrf(temperature, 5, 2, sensor_result); // convert float to char array - str len inc decimal point, nums after decimal place | |
ok = mqtt_client.publish(temperature_topic, sensor_result, true); | |
if (!ok) { log_error("mqtt","temperature publish failed!"); failed=true; }; | |
dtostrf(pressure, 7, 2, sensor_result); | |
ok = mqtt_client.publish(pressure_topic, sensor_result, true); | |
if (!ok) { log_error("mqtt","pressure publish failed!"); failed=true;}; | |
dtostrf(humidity, 7, 2, sensor_result); | |
ok = mqtt_client.publish(humidity_topic, sensor_result, true); | |
if (!ok) { log_error("mqtt","humidity publish failed!"); failed=true;}; | |
long rssi = WiFi.RSSI(); | |
dtostrf(rssi, 3, 0, sensor_result); | |
ok = mqtt_client.publish(wifi_topic, sensor_result, true); | |
if (!ok) { log_error("mqtt","wifi publish failed!"); failed=true;}; | |
if (!failed) { | |
snprintf(buff,sizeof(buff),"published %0.2f C, %0.2f hPa, %0.2f RH, %d dB",temperature,pressure,humidity,rssi); | |
log_info("mqtt",buff); | |
} | |
} | |
Serial.println(); | |
} | |
// ###################################################################################### | |
// | |
// | |
void handle_OnConnect() { | |
Serial.println("web request"); | |
do_read_sensor("web client"); | |
http_server.send(200, "text/html", SendHTML(temperature,humidity,pressure,altitude)); | |
} | |
// ###################################################################################### | |
// | |
// | |
void handle_NotFound(){ | |
char buff[128]; | |
//from https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino#L78 | |
String message = "File Not Found\n\n"; | |
message += "URI: "; | |
message += http_server.uri(); | |
message += "\nMethod: "; | |
message += (http_server.method() == HTTP_GET) ? "GET" : "POST"; | |
message += "\nArguments: "; | |
message += http_server.args(); | |
message += "\n"; | |
for (uint8_t i = 0; i < http_server.args(); i++) { | |
message += " " + http_server.argName(i) + ": " + http_server.arg(i) + "\n"; | |
} | |
http_server.send(404, "text/plain", message); | |
snprintf(buff,sizeof(buff),"404 %s",http_server.uri().c_str()); | |
log_error("http",buff); | |
} | |
// ###################################################################################### | |
// | |
// | |
String SendHTML(float temperature,float humidity,float pressure,float altitude){ | |
char *temp_date = get_timestamp(); // this fills the global 'currentDate' | |
// darn ESP8266WebServer only likes String() class | |
String ptr = "<!DOCTYPE html>"; | |
ptr +="<html>"; | |
ptr +="<head>"; | |
ptr +="<title>ESP8266 Weather Station ["; | |
ptr += chip_id; | |
ptr += "]</title>"; | |
ptr +="<meta name='viewport' content='width=device-width, initial-scale=1.0'>"; | |
ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>"; | |
ptr +="<style>"; | |
ptr +="html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #444444;}"; | |
ptr +="body{margin: 0px;} "; | |
ptr +="h1 {margin: 50px auto 30px;} "; | |
ptr +=".side-by-side{display: table-cell;vertical-align: middle;position: relative;}"; | |
ptr +=".text{font-weight: 600;font-size: 19px;width: 200px;}"; | |
ptr +=".reading{font-weight: 300;font-size: 50px;padding-right: 25px;}"; | |
ptr +=".temperature .reading{color: #F29C1F;}"; | |
ptr +=".humidity .reading{color: #3B97D3;}"; | |
ptr +=".pressure .reading{color: #26B99A;}"; | |
ptr +=".altitude .reading{color: #955BA5;}"; | |
ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;top: 10px;}"; | |
ptr +=".data{padding: 10px;}"; | |
ptr +=".container{display: table;margin: 0 auto;}"; | |
ptr +=".icon{width:65px}"; | |
ptr +="</style>"; | |
ptr +="<script>\n"; | |
ptr +="setInterval(loadDoc,60000);\n"; | |
ptr +="function loadDoc() {\n"; | |
ptr +="var xhttp = new XMLHttpRequest();\n"; | |
ptr +="xhttp.onreadystatechange = function() {\n"; | |
ptr +="if (this.readyState == 4 && this.status == 200) {\n"; | |
ptr +="document.body.innerHTML =this.responseText}\n"; | |
ptr +="};\n"; | |
ptr +="xhttp.open(\"GET\", \"/\", true);\n"; | |
ptr +="xhttp.send();\n"; | |
ptr +="}\n"; | |
ptr +="</script>\n"; | |
ptr +="</head>"; | |
ptr +="<body>"; | |
ptr +="<h1>ESP8266 Weather Station ["; | |
ptr += chip_id; | |
ptr += "]</h1>"; | |
ptr += "Client Time: "; | |
ptr += currentDate; | |
ptr += "<br/>"; | |
//TODO: add mqtt details here | |
ptr +="<div class='container'>"; | |
ptr +="<div class='data temperature'>"; | |
ptr +="<div class='side-by-side icon'>"; | |
ptr +="<svg enable-background='new 0 0 19.438 54.003'height=54.003px id=Layer_1 version=1.1 viewBox='0 0 19.438 54.003'width=19.438px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M11.976,8.82v-2h4.084V6.063C16.06,2.715,13.345,0,9.996,0H9.313C5.965,0,3.252,2.715,3.252,6.063v30.982"; | |
ptr +="C1.261,38.825,0,41.403,0,44.286c0,5.367,4.351,9.718,9.719,9.718c5.368,0,9.719-4.351,9.719-9.718"; | |
ptr +="c0-2.943-1.312-5.574-3.378-7.355V18.436h-3.914v-2h3.914v-2.808h-4.084v-2h4.084V8.82H11.976z M15.302,44.833"; | |
ptr +="c0,3.083-2.5,5.583-5.583,5.583s-5.583-2.5-5.583-5.583c0-2.279,1.368-4.236,3.326-5.104V24.257C7.462,23.01,8.472,22,9.719,22"; | |
ptr +="s2.257,1.01,2.257,2.257V39.73C13.934,40.597,15.302,42.554,15.302,44.833z'fill=#F29C21 /></g></svg>"; | |
ptr +="</div>"; | |
ptr +="<div class='side-by-side text'>Temperature</div>"; | |
ptr +="<div class='side-by-side reading'>"; | |
ptr +=(int)temperature; | |
ptr +="<span class='superscript'>°C</span></div>"; | |
ptr +="</div>"; | |
ptr +="<div class='data humidity'>"; | |
ptr +="<div class='side-by-side icon'>"; | |
ptr +="<svg enable-background='new 0 0 29.235 40.64'height=40.64px id=Layer_1 version=1.1 viewBox='0 0 29.235 40.64'width=29.235px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><path d='M14.618,0C14.618,0,0,17.95,0,26.022C0,34.096,6.544,40.64,14.618,40.64s14.617-6.544,14.617-14.617"; | |
ptr +="C29.235,17.95,14.618,0,14.618,0z M13.667,37.135c-5.604,0-10.162-4.56-10.162-10.162c0-0.787,0.638-1.426,1.426-1.426"; | |
ptr +="c0.787,0,1.425,0.639,1.425,1.426c0,4.031,3.28,7.312,7.311,7.312c0.787,0,1.425,0.638,1.425,1.425"; | |
ptr +="C15.093,36.497,14.455,37.135,13.667,37.135z'fill=#3C97D3 /></svg>"; | |
ptr +="</div>"; | |
ptr +="<div class='side-by-side text'>Humidity</div>"; | |
ptr +="<div class='side-by-side reading'>"; | |
ptr +=(int)humidity; | |
ptr +="<span class='superscript'>%</span></div>"; | |
ptr +="</div>"; | |
ptr +="<div class='data pressure'>"; | |
ptr +="<div class='side-by-side icon'>"; | |
ptr +="<svg enable-background='new 0 0 40.542 40.541'height=40.541px id=Layer_1 version=1.1 viewBox='0 0 40.542 40.541'width=40.542px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M34.313,20.271c0-0.552,0.447-1,1-1h5.178c-0.236-4.841-2.163-9.228-5.214-12.593l-3.425,3.424"; | |
ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l3.425-3.424"; | |
ptr +="c-3.375-3.059-7.776-4.987-12.634-5.215c0.015,0.067,0.041,0.13,0.041,0.202v4.687c0,0.552-0.447,1-1,1s-1-0.448-1-1V0.25"; | |
ptr +="c0-0.071,0.026-0.134,0.041-0.202C14.39,0.279,9.936,2.256,6.544,5.385l3.576,3.577c0.391,0.391,0.391,1.024,0,1.414"; | |
ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293L5.142,6.812c-2.98,3.348-4.858,7.682-5.092,12.459h4.804"; | |
ptr +="c0.552,0,1,0.448,1,1s-0.448,1-1,1H0.05c0.525,10.728,9.362,19.271,20.22,19.271c10.857,0,19.696-8.543,20.22-19.271h-5.178"; | |
ptr +="C34.76,21.271,34.313,20.823,34.313,20.271z M23.084,22.037c-0.559,1.561-2.274,2.372-3.833,1.814"; | |
ptr +="c-1.561-0.557-2.373-2.272-1.815-3.833c0.372-1.041,1.263-1.737,2.277-1.928L25.2,7.202L22.497,19.05"; | |
ptr +="C23.196,19.843,23.464,20.973,23.084,22.037z'fill=#26B999 /></g></svg>"; | |
ptr +="</div>"; | |
ptr +="<div class='side-by-side text'>Pressure</div>"; | |
ptr +="<div class='side-by-side reading'>"; | |
ptr +=(int)pressure; | |
ptr +="<span class='superscript'>hPa</span></div>"; | |
ptr +="</div>"; | |
/* | |
ptr +="<div class='data altitude'>"; | |
ptr +="<div class='side-by-side icon'>"; | |
ptr +="<svg enable-background='new 0 0 58.422 40.639'height=40.639px id=Layer_1 version=1.1 viewBox='0 0 58.422 40.639'width=58.422px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M58.203,37.754l0.007-0.004L42.09,9.935l-0.001,0.001c-0.356-0.543-0.969-0.902-1.667-0.902"; | |
ptr +="c-0.655,0-1.231,0.32-1.595,0.808l-0.011-0.007l-0.039,0.067c-0.021,0.03-0.035,0.063-0.054,0.094L22.78,37.692l0.008,0.004"; | |
ptr +="c-0.149,0.28-0.242,0.594-0.242,0.934c0,1.102,0.894,1.995,1.994,1.995v0.015h31.888c1.101,0,1.994-0.893,1.994-1.994"; | |
ptr +="C58.422,38.323,58.339,38.024,58.203,37.754z'fill=#955BA5 /><path d='M19.704,38.674l-0.013-0.004l13.544-23.522L25.13,1.156l-0.002,0.001C24.671,0.459,23.885,0,22.985,0"; | |
ptr +="c-0.84,0-1.582,0.41-2.051,1.038l-0.016-0.01L20.87,1.114c-0.025,0.039-0.046,0.082-0.068,0.124L0.299,36.851l0.013,0.004"; | |
ptr +="C0.117,37.215,0,37.62,0,38.059c0,1.412,1.147,2.565,2.565,2.565v0.015h16.989c-0.091-0.256-0.149-0.526-0.149-0.813"; | |
ptr +="C19.405,39.407,19.518,39.019,19.704,38.674z'fill=#955BA5 /></g></svg>"; | |
ptr +="</div>"; | |
ptr +="<div class='side-by-side text'>Altitude</div>"; | |
ptr +="<div class='side-by-side reading'>"; | |
ptr +=(int)altitude; | |
ptr +="<span class='superscript'>m</span></div>"; | |
ptr +="</div>"; | |
ptr +="</div>"; | |
*/ | |
ptr +="</body>"; | |
ptr +="</html>"; | |
return ptr; | |
} |
Author
mohclips
commented
Jan 30, 2021
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment