Add preliminary ESP32 support
This commit is contained in:
parent
f90f45b401
commit
53abc53ade
|
@ -13,11 +13,7 @@
|
|||
<p>current data update interval (ms): <span id="updInt"></span></p>
|
||||
<p>
|
||||
set data update interval (ms):
|
||||
<select
|
||||
id="upd"
|
||||
onchange="setUpdateInterval();"
|
||||
onfocus="this.selectedIndex = -1;"
|
||||
>
|
||||
<select id="upd" onchange="setUpdateInterval();">
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>50</option>
|
||||
|
@ -32,11 +28,7 @@
|
|||
<p>current chart update interval (ms): <span id="cupdInt"></span></p>
|
||||
<p>
|
||||
set chart update interval (ms):
|
||||
<select
|
||||
id="chartUpd"
|
||||
onchange="location.reload()"
|
||||
onfocus="this.selectedIndex = -1;"
|
||||
>
|
||||
<select id="chartUpd" onchange="location.reload()">
|
||||
<option>200</option>
|
||||
<option selected>500</option>
|
||||
<option>1000</option>
|
||||
|
@ -44,22 +36,38 @@
|
|||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<div id="record">
|
||||
<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>
|
||||
<tr>
|
||||
<td class="chart-container">
|
||||
<h2>Channel A:</h2>
|
||||
<h2>Channel A <span id="error-tempA"></span></h2>
|
||||
<label for="labelA">label: </label
|
||||
><input type="text" value="tempA" id="labelA" />
|
||||
<div id="error-tempA"></div>
|
||||
<canvas id="chartA"></canvas>
|
||||
</td>
|
||||
<td class="chart-container">
|
||||
<h2>Channel B:</h2>
|
||||
<h2>Channel B <span id="error-tempB"></span></h2>
|
||||
<label for="labelB">label: </label
|
||||
><input type="text" value="tempB" id="labelB" />
|
||||
<div id="error-tempB"></div>
|
||||
<canvas id="chartB"></canvas>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
116
data/script.js
116
data/script.js
|
@ -1,8 +1,7 @@
|
|||
|
||||
const canvasA = document.getElementById("chartA");
|
||||
const canvasB = document.getElementById("chartB");
|
||||
const chartUpdateInt = document.getElementById("chartUpd").value;
|
||||
document.getElementById("stopRec").disabled = true;
|
||||
const canvasA = document.querySelector("#chartA");
|
||||
const canvasB = document.querySelector("#chartB");
|
||||
const chartUpdateInt = document.querySelector("#chartUpd").value;
|
||||
document.querySelector("#stopRec").disabled = true;
|
||||
let data;
|
||||
let record = 0;
|
||||
let filename;
|
||||
|
@ -86,11 +85,11 @@ const options = {
|
|||
fetch("/int")
|
||||
.then((res) => res.text())
|
||||
.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, {
|
||||
type: "line",
|
||||
data: {
|
||||
|
@ -137,7 +136,7 @@ function getReadings() {
|
|||
websocket.send("getReadings");
|
||||
}
|
||||
function setUpdateInterval() {
|
||||
let updateInt = document.getElementById("upd").value;
|
||||
let updateInt = document.querySelector("#upd").value;
|
||||
websocket.send("u" + updateInt);
|
||||
location.reload();
|
||||
}
|
||||
|
@ -146,7 +145,7 @@ function getUpdateInterval() {
|
|||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
document.getElementById("upd").value = this.responseText;
|
||||
document.querySelector("#upd").value = this.responseText;
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/int", true);
|
||||
|
@ -170,6 +169,7 @@ function onOpen(event) {
|
|||
function onClose(event) {
|
||||
console.log("Connection closed");
|
||||
setTimeout(initWebSocket, 2000);
|
||||
csvData.length = 0;
|
||||
}
|
||||
|
||||
function addData(chart, label, data) {
|
||||
|
@ -192,7 +192,7 @@ function onMessage(event) {
|
|||
//console.log(event.data);
|
||||
data = JSON.parse(event.data);
|
||||
let timestamp = new Date();
|
||||
document.getElementById("time").innerHTML = timestamp.getTime();
|
||||
document.querySelector("#time").innerHTML = timestamp.getTime();
|
||||
data["time"] = timestamp.getTime();
|
||||
if (record == 1) {
|
||||
csvData.push(createCSV(data));
|
||||
|
@ -201,16 +201,16 @@ function onMessage(event) {
|
|||
|
||||
setInterval(function () {
|
||||
if (isNaN(data["tempA"])) {
|
||||
document.getElementById("error-tempA").innerHTML =
|
||||
document.querySelector("#error-tempA").innerHTML =
|
||||
"<h3>" + data["tempA"] + "</h3>";
|
||||
} else {
|
||||
document.getElementById("error-tempA").innerText = "";
|
||||
document.querySelector("#error-tempA").innerText = "";
|
||||
}
|
||||
if (isNaN(data["tempB"])) {
|
||||
document.getElementById("error-tempB").innerHTML =
|
||||
document.querySelector("#error-tempB").innerHTML =
|
||||
"<h3>" + data["tempB"] + "</h3>";
|
||||
} else {
|
||||
document.getElementById("error-tempB").innerText = "";
|
||||
document.querySelector("#error-tempB").innerText = "";
|
||||
}
|
||||
addData(chartA, data["time"], data["tempA"]);
|
||||
addData(chartB, data["time"], data["tempB"]);
|
||||
|
@ -257,15 +257,61 @@ function createCSV(object) {
|
|||
}
|
||||
|
||||
function startRecording() {
|
||||
let timedRec = document.querySelector("#timedRec").checked;
|
||||
date = new Date();
|
||||
let filedate = date.toISOString().slice(0, -5).replaceAll(":", "");
|
||||
filename = "therminator-" + filedate + ".csv";
|
||||
labelA = document.getElementById("labelA").value;
|
||||
labelB = document.getElementById("labelB").value;
|
||||
document.querySelector("#filename").innerText = filename;
|
||||
labelA = document.querySelector("#labelA").value;
|
||||
labelB = document.querySelector("#labelB").value;
|
||||
csvData = ['time,"' + labelA + '","' + labelB + '"'];
|
||||
record = 1;
|
||||
document.getElementById("startRec").disabled = true;
|
||||
document.getElementById("stopRec").disabled = false;
|
||||
document.querySelector("#startRec").disabled = true;
|
||||
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() {
|
||||
record = 0;
|
||||
|
@ -277,7 +323,33 @@ function stopRecording() {
|
|||
link.setAttribute("download", filename);
|
||||
document.querySelector("body").append(link);
|
||||
link.click();
|
||||
csvData.length = 1;
|
||||
document.getElementById("startRec").disabled = false;
|
||||
document.getElementById("stopRec").disabled = true;
|
||||
csvData.length = 0;
|
||||
document.querySelector("#startRec").disabled = false;
|
||||
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";
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
* {
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
}
|
||||
body {
|
||||
background-color: rgb(50, 50, 50);
|
||||
color: white;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
#du,
|
||||
|
@ -10,7 +12,55 @@ body {
|
|||
display: inline-block;
|
||||
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-tempB {
|
||||
color: red !important;
|
||||
|
@ -22,3 +72,11 @@ body {
|
|||
width: 40vw;
|
||||
padding: 1em;
|
||||
}
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0.0;
|
||||
}
|
||||
}
|
||||
.blinking {
|
||||
animation: blink 1s step-start 0s infinite;
|
||||
}
|
|
@ -21,3 +21,18 @@ lib_deps =
|
|||
Arduino_JSON
|
||||
ESPAsyncWiFiManager
|
||||
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
|
||||
|
|
94
src/main.cpp
94
src/main.cpp
|
@ -1,15 +1,18 @@
|
|||
#include <Adafruit_MAX31855.h>
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_JSON.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#else
|
||||
#include <ESP8266WiFi.h>
|
||||
#endif
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPAsyncWiFiManager.h>
|
||||
#include <LittleFS.h>
|
||||
#include <SPI.h>
|
||||
#include <pinout.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
// sensor chip select pin definitions
|
||||
#define CS_A D2
|
||||
#define CS_B D3
|
||||
|
||||
|
@ -24,9 +27,14 @@ AsyncWiFiManager wm(&server, &dns);
|
|||
|
||||
JSONVar readings;
|
||||
|
||||
// data acquisition functions
|
||||
unsigned long int lastTime = 0;
|
||||
unsigned long int timerDelay = 500;
|
||||
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())) {
|
||||
uint8_t err = sensor.readError();
|
||||
if (err & MAX31855_FAULT_OPEN) {
|
||||
|
@ -42,13 +50,16 @@ String checkSensor(Adafruit_MAX31855 &sensor) {
|
|||
return String(sensor.readCelsius());
|
||||
}
|
||||
String getSensorReadings() {
|
||||
/*
|
||||
return sensor readings along with timestamp in JSON format
|
||||
*/
|
||||
readings["time"] = String(lastTime);
|
||||
readings["tempA"] = checkSensor(thermocoupleA);
|
||||
readings["tempB"] = checkSensor(thermocoupleB);
|
||||
String jsonString = JSON.stringify(readings);
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
// websocket functions
|
||||
void notifyClients(String 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) {
|
||||
data[len] = 0;
|
||||
String message = (char *)data;
|
||||
// Check if the message is "getReadings"
|
||||
// check if message is "getReadings"
|
||||
if (strcmp((char *)data, "getReadings") == 0) {
|
||||
// if it is, send current sensor readings
|
||||
// if it is, send current readings to server
|
||||
String sensorReadings = getSensorReadings();
|
||||
Serial.printf("%s\n", sensorReadings.c_str());
|
||||
notifyClients(sensorReadings);
|
||||
}
|
||||
// check if message is an update interval change
|
||||
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()));
|
||||
int uInt = uVal.toInt();
|
||||
timerDelay = uInt;
|
||||
|
@ -91,53 +104,66 @@ void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType
|
|||
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() {
|
||||
ws.onEvent(onEvent);
|
||||
server.addHandler(&ws);
|
||||
Serial.printf("websocket init\n");
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
void initWebServer() {
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
request->send(LittleFS, "/index.html", "text/html");
|
||||
});
|
||||
|
||||
server.onNotFound([](AsyncWebServerRequest *request) {
|
||||
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.begin();
|
||||
Serial.printf("webserver init\n");
|
||||
}
|
||||
|
||||
void setup() {
|
||||
initSerial();
|
||||
initSPI();
|
||||
initLittleFS();
|
||||
initSensor();
|
||||
initWiFi();
|
||||
initWebSocket();
|
||||
initWebServer();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if ((millis() - lastTime) > timerDelay) {
|
||||
String sensorReadings = getSensorReadings();
|
||||
|
|
Loading…
Reference in a new issue