diff --git a/data/index.html b/data/index.html index 5ece2ec..25bcffa 100644 --- a/data/index.html +++ b/data/index.html @@ -13,11 +13,7 @@
current data update interval (ms):
set data update interval (ms): - + 10 20 50 @@ -32,11 +28,7 @@ current chart update interval (ms): set chart update interval (ms): - + 200 500 1000 @@ -44,22 +36,38 @@ - Start recording - Stop recording + + Start recording + Stop recording + + + Sorry, your browser does not support inline SVG. + + Recording CSV data to + + + Timed recording + + + Stop recording after (min): + - Channel A: + Channel A label: - - Channel B: + Channel B label: - diff --git a/data/script.js b/data/script.js index ab0c64b..0733aa3 100644 --- a/data/script.js +++ b/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 = "" + data["tempA"] + ""; } 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 = "" + data["tempB"] + ""; } 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"; + } +}; diff --git a/data/style.css b/data/style.css index 997c146..8584884 100644 --- a/data/style.css +++ b/data/style.css @@ -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; +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 056225e..3bbe5d7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/src/main.cpp b/src/main.cpp index e66b210..b322e19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,18 @@ #include #include #include -#include +#ifdef ESP32 + #include +#else + #include +#endif #include #include #include #include #include -#include - +// 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();
current chart update interval (ms):
set chart update interval (ms): - + 200 500 1000 @@ -44,22 +36,38 @@
Recording CSV data to