Add preliminary ESP32 support

This commit is contained in:
huskee 2023-12-23 19:49:32 +02:00
parent f90f45b401
commit 53abc53ade
5 changed files with 252 additions and 73 deletions

View file

@ -13,11 +13,7 @@
<p>current data update interval (ms): <span id="updInt"></span></p> <p>current data update interval (ms): <span id="updInt"></span></p>
<p> <p>
set data update interval (ms): set data update interval (ms):
<select <select id="upd" onchange="setUpdateInterval();">
id="upd"
onchange="setUpdateInterval();"
onfocus="this.selectedIndex = -1;"
>
<option>10</option> <option>10</option>
<option>20</option> <option>20</option>
<option>50</option> <option>50</option>
@ -32,11 +28,7 @@
<p>current chart update interval (ms): <span id="cupdInt"></span></p> <p>current chart update interval (ms): <span id="cupdInt"></span></p>
<p> <p>
set chart update interval (ms): set chart update interval (ms):
<select <select id="chartUpd" onchange="location.reload()">
id="chartUpd"
onchange="location.reload()"
onfocus="this.selectedIndex = -1;"
>
<option>200</option> <option>200</option>
<option selected>500</option> <option selected>500</option>
<option>1000</option> <option>1000</option>
@ -44,22 +36,38 @@
</p> </p>
</div> </div>
<br /> <br />
<button id="startRec" onclick="startRecording()">Start recording</button> <div id="record">
<button id="stopRec" onclick="stopRecording()">Stop recording</button> <button id="startRec" onclick="startRecording()">Start recording</button>
<button id="stopRec" onclick="stopRecording()">Stop recording</button>
<span id="recInfo"
><svg class="blinking" width="20px" height="20px">
<circle cx="10" cy="10" r="7" fill="red" />
Sorry, your browser does not support inline SVG.
</svg>
<p>Recording CSV data to <span id="filename"></span> </p
></span><span id="timeLeft"></span>
</div>
<div id="timedCheck">
<input type="checkbox" id="timedRec" /><label for="timedRec"
>Timed recording</label
>
</div>
<span id="timedSettings">
<label for="timer">Stop recording after (min): </label
><input type="text" id="timer" />
</span>
<table> <table>
<tr> <tr>
<td class="chart-container"> <td class="chart-container">
<h2>Channel A:</h2> <h2>Channel A <span id="error-tempA"></span></h2>
<label for="labelA">label: </label <label for="labelA">label: </label
><input type="text" value="tempA" id="labelA" /> ><input type="text" value="tempA" id="labelA" />
<div id="error-tempA"></div>
<canvas id="chartA"></canvas> <canvas id="chartA"></canvas>
</td> </td>
<td class="chart-container"> <td class="chart-container">
<h2>Channel B:</h2> <h2>Channel B <span id="error-tempB"></span></h2>
<label for="labelB">label: </label <label for="labelB">label: </label
><input type="text" value="tempB" id="labelB" /> ><input type="text" value="tempB" id="labelB" />
<div id="error-tempB"></div>
<canvas id="chartB"></canvas> <canvas id="chartB"></canvas>
</td> </td>
</tr> </tr>

View file

@ -1,8 +1,7 @@
const canvasA = document.querySelector("#chartA");
const canvasA = document.getElementById("chartA"); const canvasB = document.querySelector("#chartB");
const canvasB = document.getElementById("chartB"); const chartUpdateInt = document.querySelector("#chartUpd").value;
const chartUpdateInt = document.getElementById("chartUpd").value; document.querySelector("#stopRec").disabled = true;
document.getElementById("stopRec").disabled = true;
let data; let data;
let record = 0; let record = 0;
let filename; let filename;
@ -86,11 +85,11 @@ const options = {
fetch("/int") fetch("/int")
.then((res) => res.text()) .then((res) => res.text())
.then((textResponse) => { .then((textResponse) => {
document.getElementById("updInt").innerText = textResponse; document.querySelector("#updInt").innerText = textResponse;
}); });
document.getElementById("cupdInt").innerText = chartUpdateInt; document.querySelector("#cupdInt").innerText = chartUpdateInt;
document.querySelector("#recInfo").style.display = "none";
const chartA = new Chart(canvasA, { const chartA = new Chart(canvasA, {
type: "line", type: "line",
data: { data: {
@ -137,7 +136,7 @@ function getReadings() {
websocket.send("getReadings"); websocket.send("getReadings");
} }
function setUpdateInterval() { function setUpdateInterval() {
let updateInt = document.getElementById("upd").value; let updateInt = document.querySelector("#upd").value;
websocket.send("u" + updateInt); websocket.send("u" + updateInt);
location.reload(); location.reload();
} }
@ -146,7 +145,7 @@ function getUpdateInterval() {
var xhttp = new XMLHttpRequest(); var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () { xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) { if (this.readyState == 4 && this.status == 200) {
document.getElementById("upd").value = this.responseText; document.querySelector("#upd").value = this.responseText;
} }
}; };
xhttp.open("GET", "/int", true); xhttp.open("GET", "/int", true);
@ -170,6 +169,7 @@ function onOpen(event) {
function onClose(event) { function onClose(event) {
console.log("Connection closed"); console.log("Connection closed");
setTimeout(initWebSocket, 2000); setTimeout(initWebSocket, 2000);
csvData.length = 0;
} }
function addData(chart, label, data) { function addData(chart, label, data) {
@ -192,7 +192,7 @@ function onMessage(event) {
//console.log(event.data); //console.log(event.data);
data = JSON.parse(event.data); data = JSON.parse(event.data);
let timestamp = new Date(); let timestamp = new Date();
document.getElementById("time").innerHTML = timestamp.getTime(); document.querySelector("#time").innerHTML = timestamp.getTime();
data["time"] = timestamp.getTime(); data["time"] = timestamp.getTime();
if (record == 1) { if (record == 1) {
csvData.push(createCSV(data)); csvData.push(createCSV(data));
@ -201,16 +201,16 @@ function onMessage(event) {
setInterval(function () { setInterval(function () {
if (isNaN(data["tempA"])) { if (isNaN(data["tempA"])) {
document.getElementById("error-tempA").innerHTML = document.querySelector("#error-tempA").innerHTML =
"<h3>" + data["tempA"] + "</h3>"; "<h3>" + data["tempA"] + "</h3>";
} else { } else {
document.getElementById("error-tempA").innerText = ""; document.querySelector("#error-tempA").innerText = "";
} }
if (isNaN(data["tempB"])) { if (isNaN(data["tempB"])) {
document.getElementById("error-tempB").innerHTML = document.querySelector("#error-tempB").innerHTML =
"<h3>" + data["tempB"] + "</h3>"; "<h3>" + data["tempB"] + "</h3>";
} else { } else {
document.getElementById("error-tempB").innerText = ""; document.querySelector("#error-tempB").innerText = "";
} }
addData(chartA, data["time"], data["tempA"]); addData(chartA, data["time"], data["tempA"]);
addData(chartB, data["time"], data["tempB"]); addData(chartB, data["time"], data["tempB"]);
@ -257,15 +257,61 @@ function createCSV(object) {
} }
function startRecording() { function startRecording() {
let timedRec = document.querySelector("#timedRec").checked;
date = new Date(); date = new Date();
let filedate = date.toISOString().slice(0, -5).replaceAll(":", ""); let filedate = date.toISOString().slice(0, -5).replaceAll(":", "");
filename = "therminator-" + filedate + ".csv"; filename = "therminator-" + filedate + ".csv";
labelA = document.getElementById("labelA").value; document.querySelector("#filename").innerText = filename;
labelB = document.getElementById("labelB").value; labelA = document.querySelector("#labelA").value;
labelB = document.querySelector("#labelB").value;
csvData = ['time,"' + labelA + '","' + labelB + '"']; csvData = ['time,"' + labelA + '","' + labelB + '"'];
record = 1; record = 1;
document.getElementById("startRec").disabled = true; document.querySelector("#startRec").disabled = true;
document.getElementById("stopRec").disabled = false; document.querySelector("#stopRec").disabled = false;
document.querySelector("#recInfo").style.display = "inline-grid";
document.querySelector("#timedSettings").style.display = "none";
document.querySelector("#timedCheck").style.display = "none";
if (timedRec) {
let timer = document.querySelector("#timer").value;
if (timer == 0) {
alert("Recording time must be non-zero");
return;
}
timer = timer * 60000;
let timeleft = timer / 1000;
let disp = document.querySelector("#timeLeft");
startTimer(timeleft, disp);
setTimeout(stopRecording, timer);
}
}
function startTimer(duration, display) {
var timer = duration, minutes, seconds;
setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.textContent = minutes + ":" + seconds;
if (--timer < 0) {
timer = duration;
}
}, 1000);
}
function fancyTimeFormat(e) {
const h = Math.floor(e / 3600)
.toString()
.padStart(2, "0"),
m = Math.floor((e % 3600) / 60)
.toString()
.padStart(2, "0"),
s = Math.floor(e % 60)
.toString()
.padStart(2, "0");
return h + ":" + m + ":" + s;
} }
function stopRecording() { function stopRecording() {
record = 0; record = 0;
@ -277,7 +323,33 @@ function stopRecording() {
link.setAttribute("download", filename); link.setAttribute("download", filename);
document.querySelector("body").append(link); document.querySelector("body").append(link);
link.click(); link.click();
csvData.length = 1; csvData.length = 0;
document.getElementById("startRec").disabled = false; document.querySelector("#startRec").disabled = false;
document.getElementById("stopRec").disabled = true; document.querySelector("#stopRec").disabled = true;
document.querySelector("#recInfo").style.display = "none";
document.querySelector("#timedSettings").style.display = "none";
document.querySelector("#timedCheck").style.display = "inline-block";
document.querySelector("#timedRec").checked = false;
document.querySelector("#timeLeft").style.display = "none";
} }
const beforeUnloadHandler = (event) => {
// Recommended
event.preventDefault();
// Included for legacy support, e.g. Chrome/Edge < 119
event.returnValue = true;
};
onbeforeunload = function () {
if (record) {
return "You are still recording data! Are you sure you want to leave?";
}
};
document.querySelector("#timedRec").onchange = function () {
if (this.checked) {
document.querySelector("#timedSettings").style.display = "inherit";
} else {
document.querySelector("#timedSettings").style.display = "none";
}
};

View file

@ -1,8 +1,10 @@
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"); @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap");
* {
font-family: "IBM Plex Mono", monospace;
}
body { body {
background-color: rgb(50, 50, 50); background-color: rgb(50, 50, 50);
color: white; color: white;
font-family: "IBM Plex Mono", monospace;
padding: 1rem 2rem; padding: 1rem 2rem;
} }
#du, #du,
@ -10,7 +12,55 @@ body {
display: inline-block; display: inline-block;
padding-right: 3rem; padding-right: 3rem;
} }
button {
background-color: white;
border: 1px rgb(0, 0, 0);
color: rgb(0, 0, 0);
text-align: center;
text-decoration: none;
display: inline-block;
}
input {
background-color: rgb(70, 70, 70);
border: none;
color: white;
padding: 5px 0;
padding-left: 5px;
font-size: 15px;
}
select {
border: 1px black;
background-color: white;
color: black;
padding: 5px;
}
button:disabled,
button[disabled] {
display: none;
}
#record, #recInfo {
display: inline-grid;
grid-template-columns: auto 1fr;
grid-gap: 10px;
align-items: center;
}
#timedCheck {
display: inline-block;
}
#timedCheck label {
padding-left: 8px;
}
#timedSettings {
display: none;
padding: 0 9.5rem;
}
#error-tempA, #error-tempA,
#error-tempB { #error-tempB {
color: red !important; color: red !important;
@ -22,3 +72,11 @@ body {
width: 40vw; width: 40vw;
padding: 1em; padding: 1em;
} }
@keyframes blink {
50% {
opacity: 0.0;
}
}
.blinking {
animation: blink 1s step-start 0s infinite;
}

View file

@ -21,3 +21,18 @@ lib_deps =
Arduino_JSON Arduino_JSON
ESPAsyncWiFiManager ESPAsyncWiFiManager
monitor_filters = esp8266_exception_decoder, colorize monitor_filters = esp8266_exception_decoder, colorize
[env:lolin32]
platform = espressif32
board = lolin32
board_build.filesystem = littlefs
framework = arduino
lib_deps =
adafruit/Adafruit MAX31855 library
Wire
SPI
ESPAsyncTCP
zeed/ESP Async WebServer
Arduino_JSON
ESPAsyncWiFiManager
monitor_filters = esp32_exception_decoder, colorize

View file

@ -1,15 +1,18 @@
#include <Adafruit_MAX31855.h> #include <Adafruit_MAX31855.h>
#include <Arduino.h> #include <Arduino.h>
#include <Arduino_JSON.h> #include <Arduino_JSON.h>
#include <ESP8266WiFi.h> #ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h> #include <ESPAsyncWiFiManager.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <SPI.h> #include <SPI.h>
#include <pinout.h> #include <pinout.h>
#include <chrono> // sensor chip select pin definitions
#define CS_A D2 #define CS_A D2
#define CS_B D3 #define CS_B D3
@ -24,9 +27,14 @@ AsyncWiFiManager wm(&server, &dns);
JSONVar readings; JSONVar readings;
// data acquisition functions
unsigned long int lastTime = 0; unsigned long int lastTime = 0;
unsigned long int timerDelay = 500; unsigned long int timerDelay = 500;
String checkSensor(Adafruit_MAX31855 &sensor) { String checkSensor(Adafruit_MAX31855 &sensor) {
/*
return sensor value if it's numerical. if it isn't,
check for errors and return current error.
*/
if (isnan(sensor.readCelsius())) { if (isnan(sensor.readCelsius())) {
uint8_t err = sensor.readError(); uint8_t err = sensor.readError();
if (err & MAX31855_FAULT_OPEN) { if (err & MAX31855_FAULT_OPEN) {
@ -42,13 +50,16 @@ String checkSensor(Adafruit_MAX31855 &sensor) {
return String(sensor.readCelsius()); return String(sensor.readCelsius());
} }
String getSensorReadings() { String getSensorReadings() {
/*
return sensor readings along with timestamp in JSON format
*/
readings["time"] = String(lastTime); readings["time"] = String(lastTime);
readings["tempA"] = checkSensor(thermocoupleA); readings["tempA"] = checkSensor(thermocoupleA);
readings["tempB"] = checkSensor(thermocoupleB); readings["tempB"] = checkSensor(thermocoupleB);
String jsonString = JSON.stringify(readings); String jsonString = JSON.stringify(readings);
return jsonString; return jsonString;
} }
// websocket functions
void notifyClients(String text) { void notifyClients(String text) {
ws.textAll(text); ws.textAll(text);
} }
@ -58,14 +69,16 @@ void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0; data[len] = 0;
String message = (char *)data; String message = (char *)data;
// Check if the message is "getReadings" // check if message is "getReadings"
if (strcmp((char *)data, "getReadings") == 0) { if (strcmp((char *)data, "getReadings") == 0) {
// if it is, send current sensor readings // if it is, send current readings to server
String sensorReadings = getSensorReadings(); String sensorReadings = getSensorReadings();
Serial.printf("%s\n", sensorReadings.c_str()); Serial.printf("%s\n", sensorReadings.c_str());
notifyClients(sensorReadings); notifyClients(sensorReadings);
} }
// check if message is an update interval change
if (message.startsWith("u")) { if (message.startsWith("u")) {
// if it is, strip the first character, convert to int and update timerDelay variable
String uVal = (message.substring(message.indexOf("u") + 1, message.length())); String uVal = (message.substring(message.indexOf("u") + 1, message.length()));
int uInt = uVal.toInt(); int uInt = uVal.toInt();
timerDelay = uInt; timerDelay = uInt;
@ -91,53 +104,66 @@ void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType
break; break;
} }
} }
// initialization functions
void initSerial() {
Serial.begin(115200);
while (!Serial) delay(1);
delay(5000); // prevent garbage on serial output
Serial.print("serial init\n");
}
void initSPI() {
SPI.begin();
Serial.printf("SPI init\n");
}
void initLittleFS() {
LittleFS.begin();
Serial.printf("LittleFS init\n");
}
void initSensor() {
if (!thermocoupleA.begin() | !thermocoupleB.begin()) {
Serial.printf("sensor init error\n");
while (true) delay(10);
}
Serial.printf("sensor init\n");
}
void initWiFi() {
WiFi.mode(WIFI_AP_STA);
WiFi.setHostname("therminator");
Serial.printf("wifi init\n");
wm.autoConnect("therminator");
Serial.printf("hostname: %s\n", String(WiFi.getHostname()).c_str());
}
void initWebSocket() { void initWebSocket() {
ws.onEvent(onEvent); ws.onEvent(onEvent);
server.addHandler(&ws); server.addHandler(&ws);
Serial.printf("websocket init\n");
} }
void initWebServer() {
void setup() {
Serial.begin(9600);
while (!Serial) delay(1);
delay(5000); // prevent garbage on serial output
Serial.println("serial init");
LittleFS.begin();
Serial.printf("LittleFS init\n");
SPI.begin();
Serial.println("SPI init");
Serial.println("MAX31855 test");
delay(500);
Serial.println("sensor init");
if (!thermocoupleA.begin() | !thermocoupleB.begin()) {
Serial.println("sensor init error");
while (true) delay(10);
}
WiFi.mode(WIFI_AP_STA);
Serial.printf("wifi init\n");
wm.autoConnect("therminator");
Serial.printf("connected! ip addr: ");
Serial.println(WiFi.localIP());
initWebSocket();
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(LittleFS, "/index.html", "text/html"); request->send(LittleFS, "/index.html", "text/html");
}); });
server.onNotFound([](AsyncWebServerRequest *request) { server.onNotFound([](AsyncWebServerRequest *request) {
request->send(404, "text/plain", "404 Not Found"); request->send(404, "text/plain", "404 Not Found");
}); });
server.on("/int", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/int", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, "text/plain", String(timerDelay).c_str()); 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");
} }
void setup() {
initSerial();
initSPI();
initLittleFS();
initSensor();
initWiFi();
initWebSocket();
initWebServer();
}
void loop() { void loop() {
if ((millis() - lastTime) > timerDelay) { if ((millis() - lastTime) > timerDelay) {
String sensorReadings = getSensorReadings(); String sensorReadings = getSensorReadings();