Add CSV recording capabilities
This commit is contained in:
parent
4ea73aa32c
commit
688e8f03bb
|
@ -9,39 +9,22 @@
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<h1>/* therminator */</h1>
|
<h1>/* therminator */</h1>
|
||||||
<p>last sensor update: <span id="time"></span></p>
|
<p>last sensor update: <span id="time"></span></p>
|
||||||
<div id="options">
|
<p>chart update interval (ms): <select id="chartUpd" onchange="location.reload()">
|
||||||
<div id="mv">
|
<option>200</option>
|
||||||
<p>
|
|
||||||
max data points:
|
|
||||||
<select id="maxVals" onchange="location.reload()">
|
|
||||||
<option>15</option>
|
|
||||||
<option>20</option>
|
|
||||||
<option>30</option>
|
|
||||||
<option>50</option>
|
|
||||||
<option>100</option>
|
|
||||||
</select>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div id="ui">
|
|
||||||
<p>
|
|
||||||
update interval (ms):
|
|
||||||
<select id="updateInterval" onchange="changeUpdateInterval()">
|
|
||||||
<option selected>500</option>
|
<option selected>500</option>
|
||||||
<option>1000</option>
|
<option>1000</option>
|
||||||
<option>3000</option>
|
</select></p>
|
||||||
</select>
|
<p>data update interval (ms): <select id="upd" onload="getUpdateInterval()" onchange="setUpdateInterval()">
|
||||||
</p>
|
<option>10</option>
|
||||||
</div>
|
<option>20</option>
|
||||||
</div>
|
<option>50</option>
|
||||||
<div id="chart-small">
|
<option>100</option>
|
||||||
<h2>Channel A:</h2>
|
<option>200</option>
|
||||||
<div id="error-tempA"></div>
|
<option selected>500</option>
|
||||||
<canvas id="chartA-mobile"></canvas>
|
<option>1000</option>
|
||||||
<h2>Channel B:</h2>
|
</select></p>
|
||||||
<div id="error-tempB"></div>
|
<button id="startRec" onclick="startRecording()">Start recording</button>
|
||||||
<canvas id="chartB-mobile"></canvas>
|
<button id="stopRec" onclick="stopRecording()">Stop recording</button>
|
||||||
</div>
|
|
||||||
<div id="chart-large">
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="chart-container">
|
<td class="chart-container">
|
||||||
|
@ -56,8 +39,6 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
164
data/script.js
164
data/script.js
|
@ -1,11 +1,11 @@
|
||||||
const canvasA = document.getElementById("chartA");
|
const canvasA = document.getElementById("chartA");
|
||||||
const canvasB = document.getElementById("chartB");
|
const canvasB = document.getElementById("chartB");
|
||||||
const mobCanvasA = document.getElementById("chartA-mobile");
|
const chartUpdateInt = document.getElementById("chartUpd").value;
|
||||||
const mobCanvasB = document.getElementById("chartB-mobile");
|
document.getElementById("stopRec").disabled = true;
|
||||||
|
let data;
|
||||||
const maxValsSelect = document.getElementById("maxVals");
|
let record = 0;
|
||||||
let maxVals = maxValsSelect.options[maxValsSelect.selectedIndex].text;
|
let filename;
|
||||||
|
const csvData = ["time, tempA, tempB"];
|
||||||
const chartAdata = {
|
const chartAdata = {
|
||||||
data: {
|
data: {
|
||||||
labels: [],
|
labels: [],
|
||||||
|
@ -35,6 +35,12 @@ const chartBdata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
animation: false,
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
pointStyle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
|
@ -107,38 +113,6 @@ const chartB = new Chart(canvasB, {
|
||||||
options: options,
|
options: options,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mobChartA = new Chart(mobCanvasA, {
|
|
||||||
type: "line",
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "Channel A temperature",
|
|
||||||
backgroundColor: "red",
|
|
||||||
borderColor: "red",
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: options,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mobChartB = new Chart(mobCanvasB, {
|
|
||||||
type: "line",
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "Channel B temperature",
|
|
||||||
backgroundColor: "blue",
|
|
||||||
borderColor: "blue",
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: options,
|
|
||||||
});
|
|
||||||
|
|
||||||
var gateway = `ws://${window.location.hostname}/ws`;
|
var gateway = `ws://${window.location.hostname}/ws`;
|
||||||
var websocket;
|
var websocket;
|
||||||
// Init web socket when the page loads
|
// Init web socket when the page loads
|
||||||
|
@ -152,6 +126,22 @@ function onload(event) {
|
||||||
function getReadings() {
|
function getReadings() {
|
||||||
websocket.send("getReadings");
|
websocket.send("getReadings");
|
||||||
}
|
}
|
||||||
|
function setUpdateInterval() {
|
||||||
|
let updateInt = document.getElementById("upd").value;
|
||||||
|
websocket.send("u" + updateInt);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUpdateInterval() {
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
document.getElementById("upd").value = this.responseText;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", "/int", true);
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
function initWebSocket() {
|
function initWebSocket() {
|
||||||
console.log("Trying to open a WebSocket connection...");
|
console.log("Trying to open a WebSocket connection...");
|
||||||
|
@ -187,30 +177,19 @@ function removeFirstData(chart) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeUpdateInterval() {
|
|
||||||
const updateSelect = document.getElementById("updateInterval");
|
|
||||||
let updateInt = updateSelect.options[updateSelect.selectedIndex].text;
|
|
||||||
websocket.send("u" + updateInt);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function that receives the message from the ESP32 with the readings
|
// Function that receives the message from the ESP32 with the readings
|
||||||
function onMessage(event) {
|
function onMessage(event) {
|
||||||
console.log(event.data);
|
//console.log(event.data);
|
||||||
var data = JSON.parse(event.data);
|
data = JSON.parse(event.data);
|
||||||
var today = new Date();
|
let timestamp = new Date();
|
||||||
var date =
|
document.getElementById("time").innerHTML = timestamp.getTime();
|
||||||
today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate();
|
data["time"] = timestamp.getTime();
|
||||||
var time =
|
if (record == 1) {
|
||||||
today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
|
csvData.push(createCSV(data));
|
||||||
var dateTime = date + " " + time;
|
|
||||||
document.getElementById("time").innerHTML = dateTime;
|
|
||||||
if (chartA.data.labels.length > maxVals) {
|
|
||||||
removeFirstData(chartA);
|
|
||||||
}
|
}
|
||||||
if (chartB.data.labels.length > maxVals) {
|
|
||||||
removeFirstData(chartB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
if (isNaN(data["tempA"])) {
|
if (isNaN(data["tempA"])) {
|
||||||
document.getElementById("error-tempA").innerHTML =
|
document.getElementById("error-tempA").innerHTML =
|
||||||
"<h3>" + data["tempA"] + "</h3>";
|
"<h3>" + data["tempA"] + "</h3>";
|
||||||
|
@ -223,8 +202,69 @@ function onMessage(event) {
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("error-tempB").innerText = "";
|
document.getElementById("error-tempB").innerText = "";
|
||||||
}
|
}
|
||||||
addData(chartA, data["time"] / 1000, data["tempA"]);
|
addData(chartA, data["time"], data["tempA"]);
|
||||||
addData(chartB, data["time"] / 1000, data["tempB"]);
|
addData(chartB, data["time"], data["tempB"]);
|
||||||
addData(mobChartA, data["time"] / 1000, data["tempA"]);
|
}, chartUpdateInt);
|
||||||
addData(mobChartB, data["time"] / 1000, data["tempB"]);
|
|
||||||
|
function escapeValue(value) {
|
||||||
|
var result = "";
|
||||||
|
var escape = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < value.length; ++i) {
|
||||||
|
var entry = value[i];
|
||||||
|
|
||||||
|
result += entry;
|
||||||
|
|
||||||
|
switch (entry) {
|
||||||
|
case '"':
|
||||||
|
result += '"';
|
||||||
|
case ",":
|
||||||
|
escape = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (escape) return '"' + result + '"';
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCSV(object) {
|
||||||
|
var body = "";
|
||||||
|
|
||||||
|
for (var column in object) {
|
||||||
|
if (object.hasOwnProperty(column)) {
|
||||||
|
if (body) body += ",";
|
||||||
|
|
||||||
|
var value = object[column];
|
||||||
|
|
||||||
|
if (value == null) continue;
|
||||||
|
|
||||||
|
body += escapeValue(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRecording() {
|
||||||
|
date = new Date();
|
||||||
|
let filedate = date.toISOString().slice(0, -5).replaceAll(":", "");
|
||||||
|
filename = "therminator-" + filedate + ".csv";
|
||||||
|
record = 1;
|
||||||
|
document.getElementById("startRec").disabled = true;
|
||||||
|
document.getElementById("stopRec").disabled = false;
|
||||||
|
}
|
||||||
|
function stopRecording() {
|
||||||
|
record = 0;
|
||||||
|
let csvFile = csvData.join("\n");
|
||||||
|
const blob = new Blob([csvFile], { type: "text/csv;charset=utf-8," });
|
||||||
|
const objUrl = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.setAttribute("href", objUrl);
|
||||||
|
link.setAttribute("download", filename);
|
||||||
|
document.querySelector("body").append(link);
|
||||||
|
link.click();
|
||||||
|
csvData.length = 1;
|
||||||
|
document.getElementById("startRec").disabled = false;
|
||||||
|
document.getElementById("stopRec").disabled = true;
|
||||||
}
|
}
|
||||||
|
|
12
src/main.cpp
12
src/main.cpp
|
@ -8,9 +8,12 @@
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <pinout.h>
|
#include <pinout.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#define CS_A D2
|
#define CS_A D2
|
||||||
#define CS_B D3
|
#define CS_B D3
|
||||||
|
|
||||||
|
|
||||||
Adafruit_MAX31855 thermocoupleA(CS_A);
|
Adafruit_MAX31855 thermocoupleA(CS_A);
|
||||||
Adafruit_MAX31855 thermocoupleB(CS_B);
|
Adafruit_MAX31855 thermocoupleB(CS_B);
|
||||||
|
|
||||||
|
@ -39,9 +42,9 @@ String checkSensor(Adafruit_MAX31855 &sensor) {
|
||||||
return String(sensor.readCelsius());
|
return String(sensor.readCelsius());
|
||||||
}
|
}
|
||||||
String getSensorReadings() {
|
String getSensorReadings() {
|
||||||
|
readings["time"] = String(lastTime);
|
||||||
readings["tempA"] = checkSensor(thermocoupleA);
|
readings["tempA"] = checkSensor(thermocoupleA);
|
||||||
readings["tempB"] = checkSensor(thermocoupleB);
|
readings["tempB"] = checkSensor(thermocoupleB);
|
||||||
readings["time"] = String(lastTime);
|
|
||||||
String jsonString = JSON.stringify(readings);
|
String jsonString = JSON.stringify(readings);
|
||||||
return jsonString;
|
return jsonString;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +62,7 @@ void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
|
||||||
if (strcmp((char *)data, "getReadings") == 0) {
|
if (strcmp((char *)data, "getReadings") == 0) {
|
||||||
// if it is, send current sensor readings
|
// if it is, send current sensor readings
|
||||||
String sensorReadings = getSensorReadings();
|
String sensorReadings = getSensorReadings();
|
||||||
Serial.print(sensorReadings);
|
Serial.printf("%s\n", sensorReadings.c_str());
|
||||||
notifyClients(sensorReadings);
|
notifyClients(sensorReadings);
|
||||||
}
|
}
|
||||||
if (message.startsWith("u")) {
|
if (message.startsWith("u")) {
|
||||||
|
@ -126,6 +129,10 @@ void setup() {
|
||||||
request->send(404, "text/plain", "404 Not Found");
|
request->send(404, "text/plain", "404 Not Found");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on("/int", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
|
request->send_P(200, "text/plain", String(timerDelay).c_str());
|
||||||
|
});
|
||||||
|
|
||||||
server.serveStatic("/", LittleFS, "/");
|
server.serveStatic("/", LittleFS, "/");
|
||||||
server.begin();
|
server.begin();
|
||||||
Serial.printf("webserver init\n");
|
Serial.printf("webserver init\n");
|
||||||
|
@ -136,7 +143,6 @@ void loop() {
|
||||||
String sensorReadings = getSensorReadings();
|
String sensorReadings = getSensorReadings();
|
||||||
Serial.println(sensorReadings);
|
Serial.println(sensorReadings);
|
||||||
notifyClients(sensorReadings);
|
notifyClients(sensorReadings);
|
||||||
|
|
||||||
lastTime = millis();
|
lastTime = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue