Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
A NodeMCU v1.0 and a BME280 send sensor values every second to a very fast webpage opened on a device via WebSocket. The page is a simple layout that can be changed fairly easy to suit your needs. I display the page 24/7 on an old tablet sitting on my home entertainment system beside my TV, where it is easy to read from any seat. Showing uptime of the NodeMCU is a plus so you can quickly see how long it's been running. You can click on the forecast widget to get more info if you want to. If you click any of the sensor reading words, it will load a history page showing Thingspeak readings.
InstallationConnection of the BME280 to the NodeMCU is as follows:
Fill in your WiFi and Thingspeak information and upload the sketch. Watch your serial readout for the IP address the device gets and load that in your browser making sure to add the port at the end (if you want, you can change the port to whatever you want). You can also go to ip:8890/update to load a httpupdater
page where you can upload a compiled bin to the device. You can also upload a sketch using Arduino OTA. You choose.
I also monitor the readings using a Javascript page found here for pulling and monitoring Thingspeak data in a much better graph. I created a simple app using the free MIT App Inventor 2 online at http://ai2.appinventor.mit.edu. I created an Android app with a simple webview component pointing to my ip:port and forwarded the port to my NodeMCU IP in my router so I can see my weather from anywhere with the touch of my phone.
/* Home Weather Station
Connect to a WiFi network and provide a web server on it so show Temperature, Humidity, Dew Point, Pressure, and Uptime as well as
a forecast widget from weatherforyou.com.
Refactored and added to for 4 readings by Scott Wilson 03-25-2017
Arduino 1.6.12 on Windows 10 64-bit compiled: 03-25-2017 by Scott Wilson
Sketch is running on a NodeMCU ESP-12E reading a BME280 sensor.
Connect to http://ip:8890/update for webpage updater.
Can be flashed OTA or http updater.
Send all sensor readings to Thingspeak.com once every minute.
History.html is hosted on another server.
Webpage auto refreshes every 6 hours to update the forecast widget. (websocket or ajax would be nice here as well)
I am using this to display the page on a tablet 24/7.
BME280 connection: VIN to 3v, GRD to grd, SCL to D1, SDA to D2. (not Adafruit BME280)
Forward ports 8888 - 8890 to this ip in your router to access remotely.
Can change look of webpage by modifying under HTTP START and WEBPAGE HTTP ROOT
Don't forget to input your thingspeak channel number in the code under WEBPAGE HTTP HISTORY
*/
#include <ESP8266HTTPUpdateServer.h>
#include <ArduinoOTA.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <BME280I2C.h>
#include <FS.h>
#include <WebSocketsServer.h> // Version vom 20.05.2015 https://github.com/Links2004/arduinoWebSockets
// --- FUNCTION DECLARATION
char *getipstr(void);
void setupAP(void);
// --- CONSTANTS
const char* host = "esp8266-weather";
const char* update_path = "/update";
const char* update_username = "admin";
const char* update_password = "password";
const char *ssid = "ssid";
const char *password = "password";
// Thingspeak stuff
char thingSpeakAddress[] = "api.thingspeak.com";
String APIKey = "YOUR-API-KEY"; //enter your channel's Write API Key
const int updateThingSpeakInterval = 60 * 1000; // 1 minute interval at which to update ThingSpeak
// Variable Setup
long lastConnectionTime = 0;
boolean lastConnected = false;
WiFiClient client;
// End Thingspeak stuff
// BME280 stuff
BME280I2C bme;
bool metric = false;
String temp_str;
String humid_str;
String pres_str;
String dew_str;
// End BME280 stuff
ESP8266WebServer server (8888);
// HTTP Updater page
ESP8266WebServer httpServer(8890);
ESP8266HTTPUpdateServer httpUpdater;
WebSocketsServer webSocket = WebSocketsServer(8889);
uint32_t time_poll = 0;
uint32_t bme_poll = 0;
//-------------------------------------------------HTTP START---------------------------------------------------------
const char HEAD_BEGIN[] PROGMEM = "\
<!DOCTYPE html>\r\n\
<html lang=\"en\">\r\n\
<head>\r\n\
<meta charset=\"utf-8\"/>\r\n\
<meta http-equiv=\"refresh\" content=\"21600\">\r\n\
";
const char WEBSOCKET_SCRIPT[] PROGMEM = "\
<script language=\"javascript\" type=\"text/javascript\">\r\n\
var connection = new WebSocket('ws://'+location.hostname+':8889/', ['arduino']);\r\n\\
connection.onmessage = function (e) \{\r\n\\
console.log('Server: ', e.data);\r\n\\
if(e.data.slice(0,2)==\"1:\") document.getElementById('ActUptime').value = e.data.slice(2);\r\n\\
if(e.data.slice(0,2)==\"2:\") document.getElementById('ActTemp').value = e.data.slice(2);\r\n\\
if(e.data.slice(0,2)==\"3:\") document.getElementById('ActHum').value = e.data.slice(2);\r\n\\
if(e.data.slice(0,2)==\"4:\") document.getElementById('ActDew').value = e.data.slice(2);\r\n\\
if(e.data.slice(0,2)==\"5:\") document.getElementById('ActPres').value = e.data.slice(2);\r\n\\
\};\r\n\\
connection.onclose = function(){\r\n\\
console.log('closed!');\r\n\\
//reconnect now\r\n\\
check();\r\n\\
}\r\n\\
function check(){\r\n\\
if(!connection || connection.readyState == 3){;\r\n\\
setInterval(check, 5000);\r\n\\
}\r\n\\
}\r\n\\
</script>\r\n\\
";
const char STYLE[] PROGMEM = "\
<style>\r\n\\
body { background-color: #000000; font-family: Arial, Helvetica, Sans-Serif; Color: #ffffff; }\r\n\\
a { text-decoration: none; color: #d3d3d3; }\r\n\\
</style>\r\n\\
";
const char TITLE[] PROGMEM = "<title>[TITLE]</title>\r\n";
const char HEAD_END[] PROGMEM = "</head>\r\n";
//-----------------------------------------------------WEBPAGE HTTP ROOT---------------------------------------------------
void http_root() {
String sResponse;
sResponse = FPSTR(HEAD_BEGIN);
sResponse += FPSTR(WEBSOCKET_SCRIPT);
sResponse.replace("[IP]", getipstr());
sResponse += FPSTR(TITLE);
sResponse.replace("[TITLE]", "Home Weather Station");
sResponse += FPSTR(STYLE);
sResponse += FPSTR(HEAD_END);
sResponse += "\
<body>\r\n\\
<center>\r\n\\
<table>\r\n\\
<tbody>\r\n\\
<tr>\r\n\\
<td colspan=\"3\" align=\"center\"><a href=\"history.html\" target=\"_new\">Temperature<br></a><font size=\"+5\"><output name=\"ActTemp\" id=\"ActTemp\"></output> °F</font><p></td>\r\n\\
</tr>\r\n\\
<tr>\r\n\\
<td align=\"left\"><a href=\"history.html\" target=\"_new\">Humidity<br></a><font size=\"+2\"><output name=\"ActHum\" id=\"ActHum\"></output> %</font><p></td>\r\n\\
<td align=\"center\"><a href=\"history.html\" target=\"_new\">Dew Point<br></a><font size=\"+2\"><output name=\"ActDew\" id=\"ActDew\"></output> °F</font><p></td>\r\n\\
<td align=\"right\"><a href=\"history.html\" target=\"_new\">Pressure<br></a><font size=\"+2\"><output name=\"ActPres\" id=\"ActPres\"></output> in</font><p></td>\r\n\\
</tr>\r\n\\
<tr>\r\n\\
<td colspan=\"3\" align=\"center\">Uptime: <output name=\"ActUptime\" id=\"ActUptime\"></output><p></td>\r\n\\
</tr>\r\n\\
<tr width=\"250\"><td colspan=\"3\"><div style='width: 300px; overflow: auto;'>\r\n\\
<a href=\"https://www.weatherforyou.com/weather/state/city.html\" target=\"_new\">\r\n\\
<img src=\"https://www.weatherforyou.net/fcgi-bin/hw3/hw3.cgi?config=png&forecast=zone&alt=hwizone7day5&place=city&state=state&country=us&hwvbg=black&hwvtc=white&hwvdisplay=&daysonly=2&maxdays=7\" width=\"500\" height=\"200\" border=\"0\" alt=\"City, State, weather forecast\"></a>\r\n\\
</div></td>\r\n\\
</tr>\r\n\\
</tbody>\r\n\\
</table>\r\n\\
</body>\r\n\\
</html>\r\n";
server.send ( 200, "text/html", sResponse );
Serial.println("Client disonnected");
}
//------------------------------------------------------WEBPAGE HTTP HISTORY-------------------------------------------------
void http_history() {
String sResponse;
sResponse = FPSTR(HEAD_BEGIN);
sResponse += FPSTR(HEAD_END);
sResponse += "\
<body>\r\n\\
<center>\r\n\\
<button type=\"button\" onclick=\"javascript:window.close()\"> Close Window </button><br>\r\n\\
<iframe width=\"450\" height=\"260\" style=\"border: 1px solid #cccccc;\" src=\"https://thingspeak.com/channels/YOUR_CHANNEL/charts/1?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&title=Temperature&type=line\"></iframe><br>\r\n\\
<iframe width=\"450\" height=\"260\" style=\"border: 1px solid #cccccc;\" src=\"https://thingspeak.com/channels/YOUR_CHANNEL/charts/2?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&title=Humidity&type=line\"></iframe><br>\r\n\\
<iframe width=\"450\" height=\"260\" style=\"border: 1px solid #cccccc;\" src=\"https://thingspeak.com/channels/YOUR_CHANNEL/charts/3?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&title=Dew+Point&type=line\"></iframe><br>\r\n\\
<iframe width=\"450\" height=\"260\" style=\"border: 1px solid #cccccc;\" src=\"https://thingspeak.com/channels/YOUR_CHANNEL/charts/4?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&title=Pressure&type=line\"></iframe>\r\n\\
</body>\r\n\\
</html>\r\n";
server.send ( 200, "text/html", sResponse );
Serial.println("Client disonnected");
}
//--------------------------------------------------------Websocket Event----------------------------------------------------
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
switch (type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
webSocket.disconnect(num);
break;
case WStype_CONNECTED:
{
IPAddress ip = webSocket.remoteIP(num);
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
//ForceSendValues = 1;
webSocket.sendTXT(num, "Connected");
}
break;
case WStype_TEXT:
Serial.printf("[%u] got Text: %s\n", num, payload);
break;
case WStype_BIN:
Serial.printf("[%u] got binary length: %u\n", num, lenght);
hexdump(payload, lenght);
break;
default:
Serial.println("webSocketEvent else");
}
}
//#######################################################
//#######################################################
//#######################################################
//#######################################################
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for ( uint8_t i = 0; i < server.args(); i++ ) {
message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
}
server.send( 404, "text/plain", message );
}
//--------------------------------------------------------BME280-------------------------------------------------------
void bmeSample() {
if (bme_poll <= millis()) {
float temp(NAN), hum(NAN), pres(NAN);
float dewPoint = bme.dew(metric);
uint8_t pressureUnit(2); // unit: B000 = Pa, B001 = hPa, B010 = Hg, B011 = atm, B100 = bar, B101 = torr, B110 = N/m^2, B111 = psi
bme.read(pres, temp, hum, metric, pressureUnit); // Parameters: (float& pressure, float& temp, float& humidity, bool celsius = false, uint8_t pressureUnit = 0x0)
/* Alternatives to ReadData():
float temp(bool celsius = false);
float pres(uint8_t unit = 0);
float hum();
Keep in mind the temperature is used for humidity and
pressure calculations. So it is more effcient to read
temperature, humidity and pressure all together.
*/
temp_str = (temp);
humid_str = (hum);
pres_str = (pres);
dew_str = (dewPoint);
Serial.print("Temperature"); Serial.println(": " + temp_str + "F");
Serial.print("Humidity"); Serial.println(": " + humid_str + "%");
Serial.print("Dew Point"); Serial.println(": " + dew_str + "F");
Serial.print("Pressure"); Serial.println(": " + pres_str + "in");
bme_poll = millis() + 1000;
}
}
//-------------------------------------------------------Thingspeak----------------------------------------------
void updateThingSpeak(String tsData) {
if (client.connect(thingSpeakAddress, 80)) {
client.print("POST /update HTTP/1.1\n");
client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");
client.print("X-THINGSPEAKAPIKEY: " + APIKey + "\n");
client.print("Content-Type: application/x-www-form-urlencoded\n");
client.print("Content-Length: ");
client.print(tsData.length());
client.print("\n\n");
client.print(tsData);
lastConnectionTime = millis();
if (client.connected()) {
Serial.println("Connecting to ThingSpeak...");
Serial.println();
}
}
}
void thingSpeak() {
if (client.available()) {
char c = client.read();
Serial.print(c);
}
// Disconnect from ThingSpeak
if (!client.connected() && lastConnected) {
Serial.println("...disconnected");
Serial.println();
client.stop();
}
// Update ThingSpeak
if (!client.connected() && (millis() - lastConnectionTime > updateThingSpeakInterval)) {
updateThingSpeak("field1=" + temp_str + "&field2=" + humid_str + "&field3=" + dew_str + "&field4=" + pres_str);
}
lastConnected = client.connected();
}
//------------------------------------------------------- SETUP -------------------------------------------------------
void setup ( void ) {
Serial.begin(115200);
Serial.println("Trying wifi config");
WiFi.begin(ssid, password);
uint8_t i=0;
while ( WiFi.status() != WL_CONNECTED && i<10) {
delay (500);
Serial.print (".");
i++;
}
Serial.println( "" );
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Own MAC: ");
Serial.println(WiFi.macAddress());
//START WEBSERVER UPGRADE
MDNS.begin(host);
httpUpdater.setup(&httpServer, update_path, update_username, update_password);
httpServer.begin();
MDNS.addService("http", "tcp", 8890);
//Print the IP Address
Serial.print("Use this URL to connect: ");
Serial.print("http://");
Serial.print(WiFi.localIP());
Serial.print(":8890");
Serial.println("/update");
// ------------------------------------------------------Arduino OTA------------------------------------------------------
// Port defaults to 8266
// ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");
// No authentication by default
// ArduinoOTA.setPassword((const char *)"123");
ArduinoOTA.onStart([]() {
Serial.println("Start");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
SPIFFS.begin();
// SPIFFS.format(); // only uncomment if you want to format SPIFFS. After flashing run once and then comment back.
//-------------------------------------------- SERVER HANDLES -------------------------------------------------------
server.on("/", http_root);
server.on("/history.html", http_history);
server.onNotFound ( handleNotFound );
server.begin();
Serial.println("HTTP server started");
webSocket.begin();
webSocket.onEvent(webSocketEvent);
Serial.println("Socket server started");
Serial.println(F("BME280 test"));
if (!bme.begin()) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
delay(1000);
}
}
//------------------------------------------------------- LOOP -------------------------------------------------------
void loop ( void ) {
server.handleClient();
ArduinoOTA.handle();
httpServer.handleClient();
webSocket.loop();
thingSpeak();
bmeSample();
if (time_poll <= millis()) {
int sec = millis() / 1000;
int min = sec / 60;
int hr = min / 60;
int day = hr / 24;
char Uptime[15];
sprintf(Uptime, "1:%02dd:%02dh:%02dm:%02ds", day, hr % 24, min % 60, sec % 60);
webSocket.broadcastTXT(Uptime);
webSocket.broadcastTXT("2\:" + temp_str);
webSocket.broadcastTXT("3\:" + humid_str);
webSocket.broadcastTXT("4\:" + dew_str);
webSocket.broadcastTXT("5\:" + pres_str);
time_poll = millis() + 1000;
}
}
char *getipstr(void){
static char cOut[20];
IPAddress ip = WiFi.localIP(); // the IP address of your esp
sprintf(cOut, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); //also pseudocode.
return cOut;
}
Comments