Hello, I’ve been trying to manipulate the code from the web server ebook 3_4_charts from file. I want to compare temperature and humidity as separate series but on the same graph. I’ve tried looking at the high charts examples but they’re too simple for me to manipulate with the code in the book. Wondering what a good fix would be.
Thanks!
Hi Dylan.
This tutorial shows how to plot multiple series: https://randomnerdtutorials.com/esp32-plot-readings-charts-multiple/
You just need to modify to use temperature and humidity.
Let me know if you need further help.
Regards,
Sara
Thanks Sara,
I managed to get both values on the same graph, but now the old values don’t show up when I reload the page. I’m not sure if the error is in my main code or in the javascript code.
// Get current sensor readings when the page loads
window.addEventListener(‘load’, getReadings);
Highcharts.setOptions({
time: {
timezoneOffset: 420 //Add your time zone offset here in minutes
}
});
// Create Temperature Chart
var chartT = new Highcharts.Chart({
chart:{
renderTo:’chart-temperature’
},
series: [
{
name: ‘Temperature’,
type:’line’,
},
{
name:’humidity’,
type:’line’,
}
],
title: {
text: undefined
},
plotOptions: {
line: {
animation: false,
dataLabels: {
enabled: true
}
}
},
xAxis: {
type: ‘datetime’,
dateTimeLabelFormats: { second: ‘%H:%M:%S’ }
},
yAxis: {
title: {
text: ‘Temperature Celsius Degrees’
}
},
credits: {
enabled: false
}
});
// Create Humidity Chart
var chartH = new Highcharts.Chart({
chart:{
renderTo:’chart-humidity’
},
series: [{
name: ‘Humidity’
}],
title: {
text: undefined
},
plotOptions: {
line: {
animation: false,
dataLabels: {
enabled: true
}
},
series: {
color: ‘#50b8b4’
}
},
xAxis: {
type: ‘datetime’,
dateTimeLabelFormats: { second: ‘%H:%M:%S’ }
},
yAxis: {
title: {
text: ‘Humidity (%)’
}
},
credits: {
enabled: false
}
});
/* Plot temperature in the temperature chart
function plotTemperature(timeValue, value){
console.log(timeValue);
var x = new Date(timeValue*1000).getTime();
console.log(x);
var y = Number(value);
if(chartT.series[0].data.length > 40) {
chartT.series[0].addPoint([x, y], true, true, true);
} else {
chartT.series[0].addPoint([x, y], true, false, true);
}
}
*/
function plotTemperature(jsonValue) {
var keys = Object.keys(jsonValue);
console.log(keys);
console.log(keys.length);
for (var i = 0; i < keys.length; i++){
var x = (new Date()).getTime();
console.log(x);
const key = keys[i];
var y = Number(jsonValue[key]);
console.log(y);
if(chartT.series[i].data.length > 40) {
chartT.series[i].addPoint([x, y], true, true, true);
} else {
chartT.series[i].addPoint([x, y], true, false, true);
}
}
}
// Plot humidity in the humidity chart
function plotHumidity(timeValue, value){
console.log(timeValue);
var x = new Date(timeValue*1000).getTime();
console.log(x);
var y = Number(value);
if(chartH.series[0].data.length > 40) {
chartH.series[0].addPoint([x, y], true, true, true);
} else {
chartH.series[0].addPoint([x, y], true, false, true);
}
}
// Function to get current readings on the webpage when it loads for the first time
function getReadings(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
//console.log(“[“+this.responseText.slice(0, -1)+”]”);
// var myObj = JSON.parse(“[“+this.responseText.slice(0, -1)+”]”);
var myObj = JSON.parse(this.responseText);
//console.log(myObj);
plotTemperature(myObj);
}
};
/*if(len > 40) {
for(var i = len-40; i<len; i++){
var len = myObj.length;
plotHumidity(myObj[i].time, myObj[i].humidity);
}
}
else {
for(var i = 0; i<len; i++){
plotTemperature(myObj);
plotHumidity(myObj[i].time, myObj[i].humidity);
}
}
}
};
*/
xhr.open(“GET”, “/readings”, true);
xhr.send();
}
if (!!window.EventSource) {
var source = new EventSource(‘/events’);
source.addEventListener(‘open’, function(e) {
console.log(“Events Connected”);
}, false);
source.addEventListener(‘error’, function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log(“Events Disconnected”);
}
}, false);
source.addEventListener(‘message’, function(e) {
console.log(“message”, e.data);
}, false);
source.addEventListener(‘new_readings’, function(e) {
console.log(“new_readings”, e.data);
var myObj = JSON.parse(e.data);
console.log(myObj);
plotTemperature(myObj);
plotHumidity(myObj.time, myObj.humidity);
}, false);
}
MAIN CODE
/ Replace with your network credentials
const char* ssid = “Mighty Fraser Wifi”;
const char* password = “mightyfraser”;
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// Create an Event Source on /events
AsyncEventSource events(“/events”);
// NTP server to request epoch time
const char* ntpServer = “pool.ntp.org”;
// Json Variable to Hold Sensor Readings
JSONVar readings;
// File name where readings will be saved
const char* dataPath = “/data.txt”;
// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 10000;
//
//1800000
// Function that gets current epoch time
unsigned long getTime() {
time_t now;
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
//Serial.println(“Failed to obtain time”);
return(0);
}
time(&now);
return now;
}
// Get Sensor Readings and return JSON object
String getSensorReadings(){
readings[“temperature”] = String(dht.readTemperature());
readings[“humidity”] = String(dht.readHumidity());
String jsonString = JSON.stringify(readings);
return jsonString;
}
// Initialize SPIFFS
void initSPIFFS() {
if (!SPIFFS.begin()) {
Serial.println(“An error has occurred while mounting SPIFFS”);
}
Serial.println(“SPIFFS mounted successfully”);
}
// Read file from SPIFFS
String readFile(fs::FS &fs, const char * path){
Serial.printf(“Reading file: %s\r\n”, path);
File file = fs.open(path);
if(!file || file.isDirectory()){
Serial.println(“- failed to open file for reading”);
return String();
}
String fileContent;
while(file.available()){
fileContent += file.readStringUntil(‘\n’);
break;
}
file.close();
return fileContent;
}
// Append data to file in SPIFFS
void appendFile(fs::FS &fs, const char * path, const char * message){
Serial.printf(“Appending to file: %s\r\n”, path);
File file = fs.open(path, FILE_APPEND);
if(!file){
Serial.println(“- failed to open file for appending”);
return;
}
if(file.print(message)){
Serial.println(“- message appended”);
} else {
Serial.println(“- append failed”);
}
file.close();
}
// Delete File
void deleteFile(fs::FS &fs, const char * path){
Serial.printf(“Deleting file: %s\r\n”, path);
if(fs.remove(path)){
Serial.println(“- file deleted”);
} else {
Serial.println(“- delete failed”);
}
}
// Get file size
int getFileSize(fs::FS &fs, const char * path){
File file = fs.open(path);
if(!file){
Serial.println(“Failed to open file for checking size”);
return 0;
}
Serial.print(“File size: “);
Serial.println(file.size());
return file.size();
}
// Initialize WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print(“Connecting to WiFi ..”);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(‘.’);
delay(1000);
}
Serial.println(WiFi.localIP());
}
void setup() {
// Serial port for debugging purposes
Serial.begin(115200);
Serial.print(“ok”);
u8x8.begin();
dht.begin();
initWiFi();
initSPIFFS();
// Create a data.txt file
bool fileexists = SPIFFS.exists(dataPath);
Serial.print(fileexists);
if(!fileexists) {
Serial.println(“File doesn’t exist”);
Serial.println(“Creating file…”);
// Prepare readings to add to the file
String message = getSensorReadings() + “,”;
// Apend data to file to create it
appendFile(SPIFFS, dataPath, message.c_str());
}
else {
Serial.println(“File already exists”);
}
// Web Server Root URL
server.on(“/”, HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, “/index.html”, “text/html”);
});
server.serveStatic(“/”, SPIFFS, “/”);
// Request for the latest sensor readings
server.on(“/readings”, HTTP_GET, [](AsyncWebServerRequest *request){
String json = getSensorReadings();
request->send(200, “application/json”, json);
json = String();
});
// Request for the latest sensor readings
server.on(“/view-data”, HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, “/data.txt”, “text/txt”);
});
// Request for the latest sensor readings
server.on(“/delete-data”, HTTP_GET, [](AsyncWebServerRequest *request){
deleteFile(SPIFFS, dataPath);
request->send(200, “text/plain”, “data.txt has been deleted.”);
});
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf(“Client reconnected! Last message ID that it got is: %u\n”, client->lastId());
}
// send event with message “hello!”, id current millis
// and set reconnect delay to 1 second
client->send(“hello!”, NULL, millis(), 10000);
});
server.addHandler(&events);
configTime(0, 0, ntpServer);
// Start server
server.begin();
events.send(getSensorReadings().c_str(),”new_readings” ,millis());
}
void loop() {
float h = dht.readHumidity();
// Read temperature as Celsius (the default)
float t = dht.readTemperature();
u8x8.clearDisplay();
u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
u8x8.setCursor(0, 0);
u8x8.print(“Temperature”);
u8x8.setCursor(0, 2);
u8x8.print(t);
u8x8.print(“*C”);
u8x8.setCursor(0, 4);
u8x8.print(“Humidity”);
u8x8.setCursor(0, 6);
u8x8.print(h);
u8x8.print(“%”);
delay(2000);
if ((millis() – lastTime) > timerDelay) {
// Send Events to the client with the Sensor Readings
events.send(“ping”,NULL,millis());
events.send(getSensorReadings().c_str(),”new_readings” ,millis());
String message = getSensorReadings() + “,”;
if ((getFileSize(SPIFFS, dataPath))>= 3400){
Serial.print(“Too many data points, deleting file…”);
// Comment the next two lines if you don’t want to delete the data file automatically.
// It won’t log more data into the file
deleteFile(SPIFFS, dataPath);
appendFile(SPIFFS, “/data.txt”, message.c_str());
}
else{
// Append new readings to the file
appendFile(SPIFFS, “/data.txt”, message.c_str());
}
lastTime = millis();
Serial.print(readFile(SPIFFS, dataPath));
}
}
Hi.
That’s how the project works.
It only collects data as long as your web browser is opened.
If you close it, and open it again it will not remember the previous values.
To remember old values you need to save your values locally on your ESP32 filesystem, or in a file on a microSD card, or database.
Regards,
Sara