Skip to content
Merged
2 changes: 0 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"pioarduino.pioarduino-ide",
"platformio.platformio-ide"
Expand Down
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog
-
## [unpublished]
- Breaking Change : MQTT base topic is now eSpa/unique_id eg eSpa/844F6033E864
- Breaking Change : File system update is required
- Feature : Add web-based debug log viewer (`/debug`) with live WebSocket console and remote reboot/level controls
- Feature : Replace RemoteDebug with WebRemoteDebug to support web and serial debug output
- Feature : MQTT connection is now established independently of the spa serial link
- Feature : Re-enable Home Assistant auto-discovery for Date Time and Day of Week
- Fix : Conversion of 2 digit year
- Fix : Correct initial `mqttLastConnect` value so MQTT reconnect backoff works from boot
- Fix : Improve reliability of web-initiated reboot

## [2.0.1-beta]
- Feature: Make `PSAV_LVL` a read-write property and implement writer (`W63:<0-2>`) to set Off/Low/High.
- Feature: Add read-write properties `PSAV_BGN` and `PSAV_END` and implement writers (`W64:<value>`, `W65:<value>`) for power-save start/end times (encoded as h*256+m).
Expand Down
178 changes: 178 additions & 0 deletions data/www/debug.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<title>ESP32 Debug</title>

<style>
body {
margin: 0;
padding: 1rem;
background: #111;
color: #ddd;
font-family: monospace;
}

header {
position: sticky;
top: 0;
padding: 0.5rem 0;
background: #111;
}

button,
select,
input {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}

#status {
margin-left: 0.5rem;
}

#log {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
</style>
</head>

<body>
<header>
<button id="clear">Clear</button>

<select id="level">
<option value="verbose">Verbose</option>
<option value="debug" selected>Debug</option>
<option value="info">Info</option>
<option value="warning">Warning</option>
<option value="error">Error</option>
</select>

<button id="silence">Silence</button>
<button id="reboot">Reboot</button>

<input
id="command"
type="text"
placeholder="WebSocket command"
>

<button id="send">Send</button>

<span id="status">Connecting...</span>
</header>

<div id="log"></div>

<script>
const logElement = document.getElementById("log");
const statusElement = document.getElementById("status");
const commandElement = document.getElementById("command");

let socket;

const maxLogLines = 2000;
let logLines = [];

function appendLog(message) {
const incomingLines = message
.replace(/\r/g, "")
.split("\n")
.filter(line => line.length > 0);

logLines.push(...incomingLines);

if (logLines.length > maxLogLines) {
logLines = logLines.slice(-maxLogLines);
}

logElement.textContent = logLines.join("\n") + "\n";

window.scrollTo(0, document.body.scrollHeight);
}

function connect() {
const protocol =
window.location.protocol === "https:"
? "wss:"
: "ws:";

socket = new WebSocket(
protocol +
"//" +
window.location.host +
"/debug/ws"
);

socket.onopen = () => {
statusElement.textContent = "Connected";
socket.send("status");
};

socket.onmessage = event => {
appendLog(event.data);
};

socket.onclose = () => {
statusElement.textContent =
"Disconnected; reconnecting...";

setTimeout(connect, 2000);
};

socket.onerror = () => {
socket.close();
};
}

function sendCommand(command) {
if (
socket &&
socket.readyState === WebSocket.OPEN
) {
socket.send(command);
}
}

document.getElementById("clear").onclick = () => {
logLines = [];
logElement.textContent = "";
};

document.getElementById("level").onchange = event => {
sendCommand("level " + event.target.value);
};

document.getElementById("silence").onclick = () => {
sendCommand("silence");
};

document.getElementById("reboot").onclick = () => {
sendCommand("reboot");
};

document.getElementById("send").onclick = () => {
sendCommand(commandElement.value);
commandElement.value = "";
};

commandElement.addEventListener(
"keydown",
event => {
if (event.key === "Enter") {
sendCommand(commandElement.value);
commandElement.value = "";
}
}
);

connect();
</script>
</body>
</html>
7 changes: 7 additions & 0 deletions data/www/index.htm
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<div class="dropdown-menu">
<a class="dropdown-item" href="#" id="jsonLink">Show Spa JSON</a>
<a class="dropdown-item" href="#" id="statusLink">Show Spa Response</a>
<a class="dropdown-item" href="/debug" id="debugLink">Debug Logs</a>
</div>
</li>

Expand Down Expand Up @@ -257,6 +258,12 @@ <h5 class="modal-title" id="fotaModalTitle">Firmware Update</h5>
<span id="lastestRelease" class="form-control-plaintext">Loading...</span>
</div>
</div>
<div class="mb-0 row">
<label for="downloadFirmware" class="col-sm-4 col-form-label">Download firmware:</label>
<div class="col-sm-8">
<span id="downloadFirmware" class="form-control-plaintext"><a href="https://github.com/wayne-love/espyspa/releases" id="firmwareLink" target="_blank">eSpa Releases</a></span>
</div>
</div>
<div class="mb-0 row" style="display: none;">
<label for="updateMethod" class="col-sm-4 col-form-label"><strong>Select Update Method:</strong></label>
<div class="col-sm-8">
Expand Down
4 changes: 2 additions & 2 deletions lib/Config/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
#include <Preferences.h>
#include <Arduino.h>
#include <ArduinoJson.h>
#include <RemoteDebug.h>
#include "WebRemoteDebug.h"

extern RemoteDebug Debug;
extern WebRemoteDebug Debug;

template <typename T>
class Setting {
Expand Down
4 changes: 2 additions & 2 deletions lib/MultiBlinker/MultiBlinker.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
*/

#include <Arduino.h>
#include <RemoteDebug.h>
#include "WebRemoteDebug.h"

#if defined(ESPA_V2) && defined(RGB_LED_PIN)
#include <Adafruit_NeoPixel.h>
#define USE_RGB_LED
#endif

extern RemoteDebug Debug;
extern WebRemoteDebug Debug;

// These are the four LEDs on the PCB (for ESPA_V1 and legacy boards)
#if defined(ESPA_V1)
Expand Down
18 changes: 13 additions & 5 deletions lib/SpaInterface/SpaInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ SpaInterface* SpaInterface::_instance = nullptr;
* framework is fully initialized. Serial initialization in constructors causes
* crashes. The begin() method must be called from setup() instead.
*/
SpaInterface::SpaInterface() : port(SPA_SERIAL) {
}

SpaInterface::SpaInterface() : port(SPA_SERIAL) {}
/**
* @brief Initialize serial communication with the spa controller.
*
Expand Down Expand Up @@ -983,7 +981,7 @@ void SpaInterface::loop(){
}

if ( _lastWaitMessage + 1000 < millis()) {
debugV("Waiting...");
debugV("Waiting... %u ms", millis());
_lastWaitMessage = millis();
}

Expand Down Expand Up @@ -1017,13 +1015,23 @@ void SpaInterface::updateMeasures() {
SpaDayOfWeek.update(statusResponseRaw[R2+5].toInt());
{
tmElements_t tm;
tm.Year = CalendarYrToTm(statusResponseRaw[R2+11].toInt());
int rawYear = statusResponseRaw[R2+11].toInt();
if (rawYear >= 100) {
tm.Year = CalendarYrToTm(rawYear); // full year, e.g. 2024
} else {
tm.Year = y2kYearToTm(rawYear); // 2-digit year, e.g. 26 -> 2026
}
tm.Month = statusResponseRaw[R2+10].toInt();
tm.Day = statusResponseRaw[R2+9].toInt();
tm.Hour = statusResponseRaw[R2+6].toInt();
tm.Minute = statusResponseRaw[R2+7].toInt();
tm.Second = statusResponseRaw[R2+8].toInt();
SpaTime.update(makeTime(tm));
debugV("Updated SpaTime to %04d-%02d-%02d %02d:%02d:%02d", tm.Year + 1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
{
time_t spaTime = SpaTime.get();
debugV("Updated SpaTime to %s", ctime(&spaTime));
}
}
HeaterTemperature.update(statusResponseRaw[R2+12].toInt());
PoolTemperature.update(statusResponseRaw[R2+13].toInt());
Expand Down
7 changes: 5 additions & 2 deletions lib/SpaInterface/SpaInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
#include <functional>
#include <stdexcept>
#include <vector>
#include <RemoteDebug.h>
#include "WebRemoteDebug.h"
#include <time.h>
#include <TimeLib.h>


extern RemoteDebug Debug;
extern WebRemoteDebug Debug;
#define FAILEDREADFREQUENCY 1000 //(ms) Frequency to retry on a failed read of the status registers.
#define V2FIRMWARE_STRING "SW V2" // String to identify V2 firmware
template <typename T, size_t N>
Expand Down Expand Up @@ -284,6 +284,9 @@ class SpaInterface {

~SpaInterface();

String UID;


// Read-only value holder synced from the spa; external code can only read.
template <typename T>
class ROProperty {
Expand Down
4 changes: 2 additions & 2 deletions lib/SpaUtils/SpaUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include <Arduino.h>
#include <ArduinoJson.h>
#include <RemoteDebug.h>
#include "WebRemoteDebug.h"
#include <time.h>
#include <TimeLib.h>
#include "SpaInterface.h"
Expand All @@ -15,7 +15,7 @@
#define xstr(a) str(a)
#define str(a) #a

extern RemoteDebug Debug;
extern WebRemoteDebug Debug;

String convertToTime(int data);
int convertToInteger(String &timeStr);
Expand Down
Loading
Loading