Skip to content

Instantly share code, notes, and snippets.

@mohclips
Created January 30, 2021 11:08
Show Gist options
  • Save mohclips/3bac5de34f649dbac944fc23a7762342 to your computer and use it in GitHub Desktop.
Save mohclips/3bac5de34f649dbac944fc23a7762342 to your computer and use it in GitHub Desktop.
A temperature, pressure, humidity sensor (BME280), with webserver, syslog and mqtt
/*
* 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'>&deg;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;
}
@mohclips
Copy link
Author

Screenshot from 2021-01-30 11-18-36

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment