Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build_firmware.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
python-version: "3.13"
- name: Install PlatformIO
run: pip install platformio
- name: Update PlatformIO
Expand Down
33 changes: 33 additions & 0 deletions data/www/espa.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ function fetchStatus() {
updateStatusElement('status_serial', value_json.status.serial);
updateStatusElement('status_siInitialised', value_json.status.siInitialised);
updateStatusElement('status_mqtt', value_json.status.mqtt);
updateEspaControlStatus(value_json.status.espaControl);
updateStatusElement('espa_model', value_json.eSpa.model);
updateStatusElement('espa_build', value_json.eSpa.update.installed_version);
})
Expand All @@ -111,6 +112,7 @@ function fetchStatus() {
handleStatusError('status_serial');
handleStatusError('status_siInitialised');
handleStatusError('status_mqtt');
handleStatusError('status_espaControl');
handleStatusError('espa_model');
handleStatusError('espa_build');
});
Expand All @@ -126,6 +128,37 @@ function updateStatusElement(elementId, value) {
}
}

function updateEspaControlStatus(status) {
const element = document.getElementById('status_espaControl');
if (!element) return;

element.classList.remove('text-bg-success', 'text-bg-danger', 'text-bg-warning', 'text-bg-secondary');

switch(status) {
case 'connected':
element.classList.add('text-bg-success');
element.textContent = 'Connected';
break;
case 'disconnected':
element.classList.add('text-bg-warning');
element.textContent = 'Disconnected';
break;
case 'not_paired':
element.classList.add('text-bg-secondary');
element.textContent = 'Not Paired';
break;
case 'disabled':
element.classList.add('text-bg-secondary');
element.textContent = 'Disabled';
break;
case 'unavailable':
default:
element.classList.add('text-bg-secondary');
element.textContent = 'Unavailable';
break;
}
}

function handleStatusError(elementId) {
const element = document.getElementById(elementId);
element.classList.remove('text-bg-warning');
Expand Down
5 changes: 5 additions & 0 deletions data/www/index.htm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<a class="dropdown-item" href="#" onclick="confirmAction('/wifi-manager'); return false;">Wi-Fi Manager</a>
<a class="dropdown-item" href="#" id="fotaLink">Firmware Updater</a>
<a class="dropdown-item" href="#" onclick="sendCurrentTime();">Send Current Time to Spa</a>
<a class="dropdown-item" href="/pairing.htm">Connect to eSpa Control</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" onclick="confirmFunction(reboot('Reboot initiated.')); return false;">Reboot eSpa</a>
</div>
Expand Down Expand Up @@ -125,6 +126,10 @@ <h3>Status</h3>
<td>MQTT status:</td>
<td><span id="status_mqtt" class="badge text-bg-warning">Loading...</span></td>
</tr>
<tr>
<td>eSpa Control connection:</td>
<td><span id="status_espaControl" class="badge text-bg-warning">Loading...</span></td>
</tr>
<tr>
<td>eSpa Model:</td>
<td><span id="espa_model" class="badge text-bg-warning">Loading...</span></td>
Expand Down
397 changes: 397 additions & 0 deletions data/www/pairing.htm

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions data/www/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,8 @@ table {
} */

h1, h2, h3 {
text-align: left;
font-weight: 300;
padding-top: 20px;
display: flex;
align-items: center;
}

.footer {
Expand Down
167 changes: 167 additions & 0 deletions dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#!/bin/bash
# Quick helper script for common development tasks

set -e

DEVICE_IP="${ESPA_IP:-10.0.0.198}"

# Function to find the ESP32 serial port
find_esp_port() {
# Look for usbmodem (most common for ESP32-S3)
local port=$(ls /dev/cu.usbmodem* 2>/dev/null | head -1)
if [ -z "$port" ]; then
# Fall back to any USB serial device
port=$(ls /dev/cu.usbserial* 2>/dev/null | head -1)
fi
echo "$port"
}

# Function to clean serial port
clean_serial() {
# Kill any process using USB serial ports
lsof -t /dev/cu.usb* 2>/dev/null | xargs kill -9 2>/dev/null || true
sleep 0.5
}

# Function to ensure clean build (fixes LDF mode issues)
ensure_clean_build() {
# Check if we need a clean build (first time or after errors)
if [ ! -f ".pio/.dev-build-ok" ]; then
echo "First dev build or previous errors - doing complete clean..."
rm -rf .pio
mkdir -p .pio
touch .pio/.dev-build-ok
fi
}

case "$1" in
build)
echo "Building dev environment..."
ensure_clean_build
pio run -e espa-v1
;;

clean)
echo "Cleaning build completely..."
rm -rf .pio
echo "Clean complete. Next build will reinstall all dependencies."
;;

upload)
echo "Building and uploading firmware..."
ensure_clean_build
clean_serial
PORT=$(find_esp_port)
if [ -z "$PORT" ]; then
echo "ERROR: No ESP32 device found. Please connect your device."
exit 1
fi
echo "Using port: $PORT"
pio run -e espa-v1 --target upload --upload-port "$PORT"
;;

monitor)
echo "Opening serial monitor..."
PORT=$(find_esp_port)
if [ -z "$PORT" ]; then
echo "ERROR: No ESP32 device found. Please connect your device."
exit 1
fi
echo "Using port: $PORT"
pio device monitor --port "$PORT"
;;

uploadfs)
echo "Building and uploading filesystem..."
ensure_clean_build
clean_serial
PORT=$(find_esp_port)
if [ -z "$PORT" ]; then
echo "ERROR: No ESP32 device found. Please connect your device."
exit 1
fi
echo "Using port: $PORT"
pio run -e espa-v1 --target uploadfs --upload-port "$PORT"
;;

full)
echo "Build, upload firmware+filesystem, and monitor..."
ensure_clean_build
clean_serial
PORT=$(find_esp_port)
if [ -z "$PORT" ]; then
echo "ERROR: No ESP32 device found. Please connect your device."
exit 1
fi
echo "Using port: $PORT"
echo ""
echo "Step 1: Uploading firmware..."
pio run -e espa-v1 --target upload --upload-port "$PORT"
echo ""
echo "Step 2: Uploading filesystem..."
pio run -e espa-v1 --target uploadfs --upload-port "$PORT"
echo ""
echo "Upload complete! Starting serial monitor (Press Ctrl+C to exit)..."
echo ""
sleep 1
pio device monitor --port "$PORT"
;;

test)
echo "Testing script functionality..."
echo ""
echo "✓ Script is executable"
PORT=$(find_esp_port)
if [ -z "$PORT" ]; then
echo "✗ No ESP32 device found"
exit 1
else
echo "✓ ESP32 found at: $PORT"
fi
if [ -f ".pio/.dev-build-ok" ]; then
echo "✓ Build marker exists (incremental builds enabled)"
else
echo "⚠ No build marker (next build will be clean)"
fi
echo ""
echo "Script is ready to use!"
;;

unpair)
echo "Unpairing device at $DEVICE_IP..."
curl -X POST "http://$DEVICE_IP/api/espa-control/unpair"
echo ""
;;

info)
echo "Device info at $DEVICE_IP:"
echo ""
echo "Device ID:"
curl -s "http://$DEVICE_IP/api/espa-control/device-id" | jq
echo ""
echo "Config:"
curl -s "http://$DEVICE_IP/api/espa-control/config" | jq
;;

*)
echo "eSpa Development Helper"
echo ""
echo "Usage: $0 {command}"
echo ""
echo "Commands:"
echo " build - Build dev environment"
echo " clean - Clean build cache (fixes WiFi.h errors)"
echo " upload - Build and upload firmware"
echo " uploadfs - Build and upload filesystem"
echo " monitor - Open serial monitor (Ctrl+C to exit)"
echo " full - Build, upload firmware+filesystem, and monitor"
echo " test - Test script functionality"
echo " unpair - Clear pairing token"
echo " info - Show device info (ID, config)"
echo ""
echo "Environment:"
echo " ESPA_IP - Device IP (default: espa.local)"
echo " Example: ESPA_IP=192.168.1.50 $0 info"
exit 1
;;
esac
2 changes: 2 additions & 0 deletions lib/Config/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ bool Config::readConfig() {
SpaPollFrequency.setValue(preferences.getInt("spaPollFreq", 60));
SoftAPAlwaysOn.setValue(preferences.getBool("SoftAPAlwaysOn", true));
SoftAPPassword.setValue(preferences.getString("SoftAPPassword", "eSPA-Password"));
EspaToken.setValue(preferences.getString("EspaToken", ""));

preferences.end();
return true;
Expand All @@ -46,6 +47,7 @@ void Config::writeConfig() {
preferences.putInt("spaPollFreq", SpaPollFrequency.getValue());
preferences.putBool("SoftAPAlwaysOn", SoftAPAlwaysOn.getValue());
preferences.putString("SoftAPPassword", SoftAPPassword.getValue());
preferences.putString("EspaToken", EspaToken.getValue());
preferences.end();
} else {
debugE("Failed to open Preferences for writing");
Expand Down
1 change: 1 addition & 0 deletions lib/Config/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class ControllerConfig {
Setting<int> SpaPollFrequency = Setting<int>("SpaPollFrequency", 60, 10, 300);
Setting<bool> SoftAPAlwaysOn = Setting<bool>("SoftAPAlwaysOn", true);
Setting<String> SoftAPPassword = Setting<String>("SoftAPPassword", "eSPA-Password");
Setting<String> EspaToken = Setting<String>("EspaToken", "");
};

class Config : public ControllerConfig {
Expand Down
2 changes: 1 addition & 1 deletion lib/MultiBlinker/MultiBlinker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void MultiBlinker::start() {
return;
}
running = true;
xTaskCreate(runTask, "MultiBlinkerTask", 2048, this, 1, &taskHandle);
xTaskCreate(runTask, "MultiBlinkerTask", 4096, this, 1, &taskHandle);
}

void MultiBlinker::stop() {
Expand Down
2 changes: 1 addition & 1 deletion lib/MultiBlinker/MultiBlinker.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern RemoteDebug Debug;
const int PCB_LED1 = 14;
const int PCB_LED2 = 41;
const int PCB_LED3 = 42;
const int PCB_LED4 = 43;
const int PCB_LED4 = -1; // GPIO 43 conflicts with USB on ESP32-S3, disabled
#elif defined(ESPA_V2)
const int PCB_LED1 = 18;
const int PCB_LED2 = 21;
Expand Down
25 changes: 24 additions & 1 deletion lib/SpaUtils/SpaUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,11 @@ int getPumpSpeedMin(String pumpInstallState) {
return min;
}

bool generateStatusJson(SpaInterface &si, MQTTClientWrapper &mqttClient, String &output, bool prettyJson) {
#ifdef ENABLE_ESPA_CONTROL
#include "EspaControl.h"
#endif

bool generateStatusJson(SpaInterface &si, MQTTClientWrapper &mqttClient, String &output, bool prettyJson, EspaControl *espaControl) {
JsonDocument json;

json["temperatures"]["setPoint"] = si.getSTMP() / 10.0;
Expand All @@ -152,6 +156,25 @@ bool generateStatusJson(SpaInterface &si, MQTTClientWrapper &mqttClient, String
json["status"]["serial"] = si.getSerialNo1() + "-" + si.getSerialNo2();
json["status"]["siInitialised"] = si.isInitialised()?"true":"false";
json["status"]["mqtt"] = mqttClient.connected()?"connected":"disconnected";

#ifdef ENABLE_ESPA_CONTROL
// Add eSpa Control connection status
if (espaControl) {
if (espaControl->isPaired()) {
if (espaControl->isConnected()) {
json["status"]["espaControl"] = "connected";
} else {
json["status"]["espaControl"] = "disconnected";
}
} else {
json["status"]["espaControl"] = "not_paired";
}
} else {
json["status"]["espaControl"] = "unavailable";
}
#else
json["status"]["espaControl"] = "disabled";
#endif

json["eSpa"]["model"] = xstr(PIOENV);
json["eSpa"]["update"]["installed_version"] = xstr(BUILD_INFO);
Expand Down
5 changes: 4 additions & 1 deletion lib/SpaUtils/SpaUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ String getPumpPossibleStates(String pumpState);
int getPumpSpeedMax(String pumpState);
int getPumpSpeedMin(String pumpState);

bool generateStatusJson(SpaInterface &si, MQTTClientWrapper &mqttClient, String &output, bool prettyJson=false);
// Forward declaration (always present to allow function signature to compile)
class EspaControl;

bool generateStatusJson(SpaInterface &si, MQTTClientWrapper &mqttClient, String &output, bool prettyJson=false, EspaControl *espaControl=nullptr);

#endif // SPAUTILS_H
Loading