Display Temp Sensor data using JavaScript
Posted: Mon May 16, 2016 10:33 pm
Hello everyone! I have been working to get the temperature reading from the ADO GPIO on my NodeMCU to display in a web page using an SVG image. I finally have something that I feel is worth showing to others who might be new to development and the ESP8266. The code below is a hack of several different examples I found on the web. The counter.html and the script within it is something that I figured out on my own via trial and error (aka pulling my hair out! ). My original attempt was trying to work directly with an SVG image file but it was too advanced for me. So I found justgage.com and used javascript from that site to create the image. It was helpful as I learned more about what it takes to work directly with SVG files using the DOM object. I will add another post if I get my next version working.
The Arduino code also has SPIFFS functions included so that the .js can be downloaded by the browser allowing the app to work on any device. SPIFFS is a powerful feature on the ESP and I highly recommend learning how it works to extend your apps. The .js are compressed into .gz files to speed up the loading in the browser.....the browser decompresses the files in memory (at least I think thats how it works)
In order for my code to work you will need to upload the following files using the ESP8266 Sketch Data Upload feature in Arduino:
counter.html (Located below the Arduino code)
Set your Wifi password in the Arduino code, compile and upload the code below to your ESP....then connect to ESPAP and enter 192.168.4.1 in your browser. The code below has simulation that will need to removed once you have a sensor connected to your AD0 pin.
/* Create a WiFi access point and provide a web server on it so show temperature. */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <WebSocketsServer.h>
/* Set these to your desired credentials. */
const char WiFiAPPSK[] = "put your password here before compiling";
//Digital PINS are for Thingboard not NodeMCU...AD0 is the same for both boards
const int LED_PIN = 2; // Thing's onboard, green LED
const int ANALOG_PIN = A0; // The only analog pin on the Thing
const int DIGITAL_PIN = 12; // Digital pin to be read..was 12
//used to convert temperatureF to an integer
int temp_int;
uint8_t remote_ip;
uint8_t socketNumber;
String x = "";
#define USE_SERIAL Serial
#define DBG_OUTPUT_PORT Serial
ESP8266WebServer server(80);
// Create a Websocket server
WebSocketsServer webSocket(81);
// state machine states
unsigned int state;
#define SEQUENCE_IDLE 0x00
#define GET_SAMPLE 0x10
#define GET_SAMPLE__WAITING 0x12
void analogSample(void)
{
if (state == SEQUENCE_IDLE)
{
return;
}
else if (state == GET_SAMPLE)
{
state = GET_SAMPLE__WAITING;
return;
}
else if (state == GET_SAMPLE__WAITING)
{
int ADCreading = analogRead(ANALOG_PIN);
byte ledStatus = LOW;
//Scale to voltage
float voltage = ADCreading * 3.2;
//Steinhart–Hart voltage to temp conversion
float Temp = log(((10240000/ADCreading) - 10000));
Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
float temperatureC = Temp - 273.15; // Convert Kelvin to Celsius
float temperatureF = (temperatureC * 9.0)/ 5.0 + 32.0; // Celsius to Fahrenheit - comment out this line if you need Celsius
float temperature = round(temperatureF*10)/10;
//Remove the comment below to enable reading from the AD0 on your board
//temp_int = (int) temperature;
//SIMULATION
//Comment the line below to disable the simulation
temp_int = temp_int + 1;
String temp_str = String(temp_int);
webSocket.sendTXT(socketNumber, "wpMeter,Arduino," + temp_str + ",1");
//Serial.println("Temp sent!! ");
//Delay sending next sample so that the web server can respond
delay(2000);
//Remove the comment below to switch the off the continous sampling
//state = SEQUENCE_IDLE;
return;
//}
}
}
/* Just a little test message. Go to http://192.168.4.1 in a web browser
* connected to this access point to see it.
*/
void handleRoot() {
server.send(200, "text/html", "<h1>You are connected</h1>");
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
switch(type) {
case WStype_DISCONNECTED:
//Reset the control for sending samples of ADC to idle to allow for web server to respond.
USE_SERIAL.printf("[%u] Disconnected!\n", num);
state = SEQUENCE_IDLE;
break;
case WStype_CONNECTED:
{
//Display client IP info that is connected in Serial monitor and set control to enable samples to be sent every two seconds (see analogsample() function)
IPAddress ip = webSocket.remoteIP(num);
USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
socketNumber = num;
state = GET_SAMPLE;
}
break;
case WStype_TEXT:
if (payload[0] == '#')
{
Serial.printf("[%u] get Text: %s\n", num, payload);
}
break;
case WStype_ERROR:
USE_SERIAL.printf("Error [%u] , %s\n",num, payload);
}
}
String getContentType(String filename){
if(server.hasArg("download")) return "application/octet-stream";
else if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
else if(filename.endsWith(".css")) return "text/css";
else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".png")) return "image/png";
else if(filename.endsWith(".gif")) return "image/gif";
else if(filename.endsWith(".jpg")) return "image/jpeg";
else if(filename.endsWith(".ico")) return "image/x-icon";
else if(filename.endsWith(".xml")) return "text/xml";
else if(filename.endsWith(".pdf")) return "application/x-pdf";
else if(filename.endsWith(".zip")) return "application/x-zip";
else if(filename.endsWith(".gz")) return "application/x-gzip";
else if(filename.endsWith(".svg")) return "image/svg+xml";
return "text/plain";
}
bool handleFileRead(String path){
DBG_OUTPUT_PORT.println("handleFileRead: " + path);
if(path.endsWith("/"))
{
path += "counter.html";
state = SEQUENCE_IDLE;
}
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
DBG_OUTPUT_PORT.println("PathFile: " + pathWithGz);
if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){
if(SPIFFS.exists(pathWithGz))
path += ".gz";
File file = SPIFFS.open(path, "r");
size_t sent = server.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
void setupWiFi()
{
WiFi.mode(WIFI_AP);
String AP_NameString = "ESPAP";
char AP_NameChar[AP_NameString.length() + 1];
memset(AP_NameChar, 0, AP_NameString.length() + 1);
for (int i=0; i<AP_NameString.length(); i++)
AP_NameChar[i] = AP_NameString.charAt(i);
WiFi.softAP(AP_NameChar, WiFiAPPSK);
}
void setup() {
delay(1000);
Serial.begin(115200);
SPIFFS.begin();
Serial.println();
Serial.print("Configuring access point...");
/* You can remove the password parameter if you want the AP to be open. */
setupWiFi();
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
server.on("/", HTTP_GET, [](){
handleFileRead("/");
});
//Handle when user requests a file that does not exist
server.onNotFound([](){
if(!handleFileRead(server.uri()))
server.send(404, "text/plain", "FileNotFound");
});
// start webSocket server
webSocket.begin();
webSocket.onEvent(webSocketEvent);
server.begin();
Serial.println("HTTP server started");
//+++++++ MDNS will not work when WiFi is in AP mode but I am leave this code in place incase this changes++++++
//if (!MDNS.begin("esp8266")) {
// Serial.println("Error setting up MDNS responder!");
// while(1) {
// delay(1000);
// }
// }
// Serial.println("mDNS responder started");
// Add service to MDNS
// MDNS.addService("http", "tcp", 80);
// MDNS.addService("ws", "tcp", 81);
}
void loop() {
webSocket.loop();
analogSample();
server.handleClient();
}
*************************** COUNTER.HTML ****************************
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Counter</title>
<meta name="viewport" content="width=device-width">
<style>
.container {
width: 450px;
margin: 0 auto;
text-align: center;
}
.gauge {
width: 450px;
height: 450px;
}
a:link.button,
a:active.button,
a:visited.button,
a:hover.button {
margin: 30px 5px 0 2px;
padding: 7px 13px;
}
</style>
</head>
<body>
<div class="container">
<div id="g1" class="gauge"></div>
<a href="#" id="g1_refresh">Random Refresh</a>
</div>
<script src="raphael-2.1.4.min.js"></script>
<script src="justgage.js"></script>
<script type="text/javascript">
var g1;
var Analog0 = new Array(); //create the arrays for the analog readings here.
document.addEventListener("DOMContentLoaded", function(event) {
console.log("DOM fully loaded and parsed");
g1 = new JustGage({
id: "g1",
value: 0,
min: 0,
max: 100,
donut: true,
pointer: true,
gaugeWidthScale: 0.6,
counter: true,
hideInnerShadow: true,
title: "Temperature",
titlePosition: "below"
});
});
var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']);
connection.onopen = function () { connection.send('GET_TEMP'); };
console.log("connection opened");
connection.onerror = function (error) { console.log('WebSocket Error ', error); };
connection.onmessage = function(evt)
{
// handle websocket message. update attributes or values of elements that match the name on incoming message
console.log("msg rec", evt.data);
var msgArray = evt.data.split(","); //split message by delimiter into a string array
console.log("msgArray", msgArray[0]);
console.log("msgArray", msgArray[1]);
console.log("msgArray", msgArray[2]);
console.log("msgArray", msgArray[3]);
var indicator = msgArray[1]; //the first element in the message array is the ID of the object to update
console.log("indiactor", indicator);
if (indicator) //if an object by the name of the message exists, update its value or its attributes
{
switch(msgArray[1])
{
case "Arduino":
console.log("Arduino ran");
var A0 = (msgArray[2]);
g1.refresh(A0, null);
var x = Analog0.length;
if (x < 101)
{
Analog0[x] = A0;
}
else
{
Analog0.shift();
x = Analog0.length;
Analog0[x] = A0;
}
break;
default:
//unrecognized message type. do nothing
break;
}
}
};
</script>
</body>
</html>
The Arduino code also has SPIFFS functions included so that the .js can be downloaded by the browser allowing the app to work on any device. SPIFFS is a powerful feature on the ESP and I highly recommend learning how it works to extend your apps. The .js are compressed into .gz files to speed up the loading in the browser.....the browser decompresses the files in memory (at least I think thats how it works)
In order for my code to work you will need to upload the following files using the ESP8266 Sketch Data Upload feature in Arduino:
counter.html (Located below the Arduino code)
Set your Wifi password in the Arduino code, compile and upload the code below to your ESP....then connect to ESPAP and enter 192.168.4.1 in your browser. The code below has simulation that will need to removed once you have a sensor connected to your AD0 pin.
/* Create a WiFi access point and provide a web server on it so show temperature. */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <WebSocketsServer.h>
/* Set these to your desired credentials. */
const char WiFiAPPSK[] = "put your password here before compiling";
//Digital PINS are for Thingboard not NodeMCU...AD0 is the same for both boards
const int LED_PIN = 2; // Thing's onboard, green LED
const int ANALOG_PIN = A0; // The only analog pin on the Thing
const int DIGITAL_PIN = 12; // Digital pin to be read..was 12
//used to convert temperatureF to an integer
int temp_int;
uint8_t remote_ip;
uint8_t socketNumber;
String x = "";
#define USE_SERIAL Serial
#define DBG_OUTPUT_PORT Serial
ESP8266WebServer server(80);
// Create a Websocket server
WebSocketsServer webSocket(81);
// state machine states
unsigned int state;
#define SEQUENCE_IDLE 0x00
#define GET_SAMPLE 0x10
#define GET_SAMPLE__WAITING 0x12
void analogSample(void)
{
if (state == SEQUENCE_IDLE)
{
return;
}
else if (state == GET_SAMPLE)
{
state = GET_SAMPLE__WAITING;
return;
}
else if (state == GET_SAMPLE__WAITING)
{
int ADCreading = analogRead(ANALOG_PIN);
byte ledStatus = LOW;
//Scale to voltage
float voltage = ADCreading * 3.2;
//Steinhart–Hart voltage to temp conversion
float Temp = log(((10240000/ADCreading) - 10000));
Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
float temperatureC = Temp - 273.15; // Convert Kelvin to Celsius
float temperatureF = (temperatureC * 9.0)/ 5.0 + 32.0; // Celsius to Fahrenheit - comment out this line if you need Celsius
float temperature = round(temperatureF*10)/10;
//Remove the comment below to enable reading from the AD0 on your board
//temp_int = (int) temperature;
//SIMULATION
//Comment the line below to disable the simulation
temp_int = temp_int + 1;
String temp_str = String(temp_int);
webSocket.sendTXT(socketNumber, "wpMeter,Arduino," + temp_str + ",1");
//Serial.println("Temp sent!! ");
//Delay sending next sample so that the web server can respond
delay(2000);
//Remove the comment below to switch the off the continous sampling
//state = SEQUENCE_IDLE;
return;
//}
}
}
/* Just a little test message. Go to http://192.168.4.1 in a web browser
* connected to this access point to see it.
*/
void handleRoot() {
server.send(200, "text/html", "<h1>You are connected</h1>");
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
switch(type) {
case WStype_DISCONNECTED:
//Reset the control for sending samples of ADC to idle to allow for web server to respond.
USE_SERIAL.printf("[%u] Disconnected!\n", num);
state = SEQUENCE_IDLE;
break;
case WStype_CONNECTED:
{
//Display client IP info that is connected in Serial monitor and set control to enable samples to be sent every two seconds (see analogsample() function)
IPAddress ip = webSocket.remoteIP(num);
USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
socketNumber = num;
state = GET_SAMPLE;
}
break;
case WStype_TEXT:
if (payload[0] == '#')
{
Serial.printf("[%u] get Text: %s\n", num, payload);
}
break;
case WStype_ERROR:
USE_SERIAL.printf("Error [%u] , %s\n",num, payload);
}
}
String getContentType(String filename){
if(server.hasArg("download")) return "application/octet-stream";
else if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
else if(filename.endsWith(".css")) return "text/css";
else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".png")) return "image/png";
else if(filename.endsWith(".gif")) return "image/gif";
else if(filename.endsWith(".jpg")) return "image/jpeg";
else if(filename.endsWith(".ico")) return "image/x-icon";
else if(filename.endsWith(".xml")) return "text/xml";
else if(filename.endsWith(".pdf")) return "application/x-pdf";
else if(filename.endsWith(".zip")) return "application/x-zip";
else if(filename.endsWith(".gz")) return "application/x-gzip";
else if(filename.endsWith(".svg")) return "image/svg+xml";
return "text/plain";
}
bool handleFileRead(String path){
DBG_OUTPUT_PORT.println("handleFileRead: " + path);
if(path.endsWith("/"))
{
path += "counter.html";
state = SEQUENCE_IDLE;
}
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
DBG_OUTPUT_PORT.println("PathFile: " + pathWithGz);
if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){
if(SPIFFS.exists(pathWithGz))
path += ".gz";
File file = SPIFFS.open(path, "r");
size_t sent = server.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
void setupWiFi()
{
WiFi.mode(WIFI_AP);
String AP_NameString = "ESPAP";
char AP_NameChar[AP_NameString.length() + 1];
memset(AP_NameChar, 0, AP_NameString.length() + 1);
for (int i=0; i<AP_NameString.length(); i++)
AP_NameChar[i] = AP_NameString.charAt(i);
WiFi.softAP(AP_NameChar, WiFiAPPSK);
}
void setup() {
delay(1000);
Serial.begin(115200);
SPIFFS.begin();
Serial.println();
Serial.print("Configuring access point...");
/* You can remove the password parameter if you want the AP to be open. */
setupWiFi();
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
server.on("/", HTTP_GET, [](){
handleFileRead("/");
});
//Handle when user requests a file that does not exist
server.onNotFound([](){
if(!handleFileRead(server.uri()))
server.send(404, "text/plain", "FileNotFound");
});
// start webSocket server
webSocket.begin();
webSocket.onEvent(webSocketEvent);
server.begin();
Serial.println("HTTP server started");
//+++++++ MDNS will not work when WiFi is in AP mode but I am leave this code in place incase this changes++++++
//if (!MDNS.begin("esp8266")) {
// Serial.println("Error setting up MDNS responder!");
// while(1) {
// delay(1000);
// }
// }
// Serial.println("mDNS responder started");
// Add service to MDNS
// MDNS.addService("http", "tcp", 80);
// MDNS.addService("ws", "tcp", 81);
}
void loop() {
webSocket.loop();
analogSample();
server.handleClient();
}
*************************** COUNTER.HTML ****************************
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Counter</title>
<meta name="viewport" content="width=device-width">
<style>
.container {
width: 450px;
margin: 0 auto;
text-align: center;
}
.gauge {
width: 450px;
height: 450px;
}
a:link.button,
a:active.button,
a:visited.button,
a:hover.button {
margin: 30px 5px 0 2px;
padding: 7px 13px;
}
</style>
</head>
<body>
<div class="container">
<div id="g1" class="gauge"></div>
<a href="#" id="g1_refresh">Random Refresh</a>
</div>
<script src="raphael-2.1.4.min.js"></script>
<script src="justgage.js"></script>
<script type="text/javascript">
var g1;
var Analog0 = new Array(); //create the arrays for the analog readings here.
document.addEventListener("DOMContentLoaded", function(event) {
console.log("DOM fully loaded and parsed");
g1 = new JustGage({
id: "g1",
value: 0,
min: 0,
max: 100,
donut: true,
pointer: true,
gaugeWidthScale: 0.6,
counter: true,
hideInnerShadow: true,
title: "Temperature",
titlePosition: "below"
});
});
var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']);
connection.onopen = function () { connection.send('GET_TEMP'); };
console.log("connection opened");
connection.onerror = function (error) { console.log('WebSocket Error ', error); };
connection.onmessage = function(evt)
{
// handle websocket message. update attributes or values of elements that match the name on incoming message
console.log("msg rec", evt.data);
var msgArray = evt.data.split(","); //split message by delimiter into a string array
console.log("msgArray", msgArray[0]);
console.log("msgArray", msgArray[1]);
console.log("msgArray", msgArray[2]);
console.log("msgArray", msgArray[3]);
var indicator = msgArray[1]; //the first element in the message array is the ID of the object to update
console.log("indiactor", indicator);
if (indicator) //if an object by the name of the message exists, update its value or its attributes
{
switch(msgArray[1])
{
case "Arduino":
console.log("Arduino ran");
var A0 = (msgArray[2]);
g1.refresh(A0, null);
var x = Analog0.length;
if (x < 101)
{
Analog0[x] = A0;
}
else
{
Analog0.shift();
x = Analog0.length;
Analog0[x] = A0;
}
break;
default:
//unrecognized message type. do nothing
break;
}
}
};
</script>
</body>
</html>