Add CSV recording capabilities

This commit is contained in:
huskee 2023-12-19 22:09:27 +02:00
parent 4ea73aa32c
commit 688e8f03bb
3 changed files with 128 additions and 101 deletions

View file

@ -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>

View file

@ -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;
} }

View file

@ -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();
} }