Skip to content

Commit cbf0bfe

Browse files
committed
Add support for WT32-ETH01 board
1 parent 93ff078 commit cbf0bfe

13 files changed

Lines changed: 252 additions & 168 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- name: Build All Environments
2828
run: |
2929
pio run -e esp32c3 -e esp32s3 -e esp32c6
30-
pio run -e esp32 -e esp32s2 -e esp8266
30+
pio run -e esp32 -e esp32s2 -e esp32-eth01 -e esp8266
3131
pio run -e pico -e pico2
3232
3333
- name: Upload Artifacts

.vscode/tasks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"label": "Build ESP Core 2.x",
66
"type": "shell",
7-
"command": "pio run -e esp32 -e esp32s2 -e esp8266",
7+
"command": "pio run -e esp32 -e esp32s2 -e esp32-eth01 -e esp8266",
88
"problemMatcher": [],
99
"presentation": { "reveal": "always", "panel": "shared" },
1010
"options": {

data/calibration.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
function setCalibration(gain, r, g, b) {
2+
const fields = { 'calGain': gain, 'calRed': r, 'calGreen': g, 'calBlue': b };
3+
4+
for (const [name, value] of Object.entries(fields)) {
5+
const el = document.querySelector(`input[name="${name}"]`);
6+
if (el) {
7+
el.value = value;
8+
}
9+
}
10+
}
11+
12+
function toggleCalibration() {
13+
const ledTypeSelect = document.getElementById('ledType');
14+
const calSection = document.getElementById('whiteCalibration');
15+
16+
calSection.style.display = (ledTypeSelect.value === "1") ? "block" : "none";
17+
}
18+
19+
function setupCalibration(){
20+
const ledTypeSelect = document.getElementById('ledType');
21+
ledTypeSelect.addEventListener('change', toggleCalibration);
22+
23+
document.getElementById('cal-cold')?.addEventListener('click', () => {
24+
setCalibration(255, 160, 160, 160);
25+
});
26+
27+
document.getElementById('cal-neutral')?.addEventListener('click', () => {
28+
setCalibration(255, 176, 176, 112);
29+
});
30+
31+
toggleCalibration();
32+
};

data/gpio.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
function setupPinValidator() {
2+
const hardwareLimits = {
3+
"ESP32-C6": { gpio: [0,1,2,3,4,5,6,7,8,10,15,18,19,20,21,22], spi: {6:5} },
4+
"ESP32-S3": { gpio: [1,2,4,5,6,7,8,10,16,17,18,48], spi: {11:12} },
5+
"ESP32-C3": { gpio: [0,1,2,3,4,5,6,7,8,10,20,21], spi: {7:6} },
6+
"ESP8266": { gpio: [2], spi: {19:18} },
7+
"ESP32": { gpio: null, spi: {23:18} },
8+
"ESP32-S2": { gpio: null, spi: {11:7} },
9+
"ESP32-ETH01": { gpio: [2,4,14], spi: {4:14} }
10+
};
11+
12+
const arch = (typeof cfgDeviceArchitecture !== 'undefined') ? cfgDeviceArchitecture : "";
13+
const els = { type: document.getElementById('ledType'), clkLabel: document.getElementById('clockPinLabel') };
14+
15+
if (!els.type || !els.clkLabel) {
16+
console.warn("LED Validator: Missing required DOM elements (ledType/clockPinLabel)");
17+
return;
18+
}
19+
20+
els.clkLabel.setAttribute('aria-live', 'polite');
21+
22+
const setField = (name, opts) => {
23+
const old = document.getElementsByName(name)[0];
24+
if (!old) return null;
25+
26+
const isSel = (opts != null);
27+
const signature = "sig_" + JSON.stringify(opts);
28+
29+
if ((old.tagName === 'SELECT') === isSel && old.dataset.sig === signature) return old;
30+
31+
const wasFocused = (document.activeElement === old);
32+
33+
const el = isSel ? document.createElement('select') : Object.assign(document.createElement('input'), {
34+
type: 'number', min: 0, max: (arch.includes('8266') ? 16 : 48), step: '1'
35+
});
36+
37+
el.name = name; el.id = old.id; el.required = true;
38+
el.dataset.sig = signature;
39+
40+
if (isSel) {
41+
opts.forEach(p => el.add(new Option(`GPIO ${p}`, p)));
42+
el.value = opts.includes(parseInt(old.value)) ? old.value : opts[0];
43+
44+
if (opts.length === 1) {
45+
el.disabled = true;
46+
el.title = "Only one valid GPIO for this mode/architecture";
47+
}
48+
} else {
49+
el.value = old.value || 0;
50+
}
51+
52+
old.replaceWith(el);
53+
el.addEventListener('change', updateUI);
54+
55+
if (wasFocused) el.focus();
56+
return el;
57+
};
58+
59+
function updateUI() {
60+
const isSpi = els.type.value == "2";
61+
const cfg = hardwareLimits[arch];
62+
63+
let validPins = cfg ? (isSpi ? Object.keys(cfg.spi).map(Number) : cfg.gpio) : null;
64+
const dataPinEditor = setField('dataPin', validPins);
65+
66+
const autoClk = (cfg && cfg.spi) ? (cfg.spi[dataPinEditor.value] ?? null) : null;
67+
const clockPinEditor = setField('clockPin', ((autoClk !== null) ? [autoClk] : null));
68+
69+
els.clkLabel.style.display = isSpi ? 'block' : 'none';
70+
clockPinEditor.disabled = clockPinEditor.disabled || !isSpi;
71+
}
72+
73+
els.type.onchange = updateUI;
74+
updateUI();
75+
};

data/ota.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,17 +184,20 @@ async function startOtaUpdate() {
184184
};
185185

186186
xhr.onload = () => {
187+
isUpdating = false;
187188
if (xhr.status === 200) {
188189
statusText.innerText = "✅ Update successful! Rebooting...";
189190
showToast(true);
190191
} else {
191192
statusText.innerText = `❌ Flash failed: ${xhr.responseText || xhr.statusText}`;
192-
progress.style.display = 'none';
193-
checkBtn.disabled = false;
193+
progress.style.display = 'none';
194194
}
195+
checkBtn.disabled = false;
196+
if (saveConfigBtn) saveConfigBtn.disabled = false;
195197
};
196198

197199
xhr.onerror = () => {
200+
isUpdating = false;
198201
statusText.innerText = "❌ Network error during upload. Device might have rebooted unexpectedly.";
199202
progress.style.display = 'none';
200203
checkBtn.disabled = false;
@@ -204,11 +207,10 @@ async function startOtaUpdate() {
204207
xhr.send(formData);
205208

206209
} catch (err) {
210+
isUpdating = false;
207211
statusText.innerText = `❌ Error: ${err.message}`;
208212
progress.style.display = 'none';
209213
checkBtn.disabled = false;
210214
if (saveConfigBtn) saveConfigBtn.disabled = false;
211-
}
212-
213-
isUpdating = false;
215+
}
214216
};

data/settings.html

Lines changed: 20 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ <h1>Settings</h1>
4848

4949
<!-- Settings -->
5050
<form action="/save_config" method="POST">
51-
<details>
51+
<details ontoggle="loadSubScript('gpio', 'setupPinValidator');loadSubScript('calibration', 'setupCalibration');">
5252
<summary role="button" class="outline secondary">LED Hardware</summary>
5353
<div>
5454
<label>LED type
@@ -60,11 +60,11 @@ <h1>Settings</h1>
6060
</label>
6161

6262
<label>Data Pin (GPIO)
63-
<select type="number" name="dataPin" required></select>
63+
<select name="dataPin" required></select>
6464
</label>
6565

6666
<label id="clockPinLabel">Clock Pin (GPIO)
67-
<select type="number" name="clockPin"></select>
67+
<select name="clockPin"></select>
6868
</label>
6969

7070
<label>Number of LEDs
@@ -79,8 +79,8 @@ <h5>White channel calibration</h5>
7979
<label>G <input type="number" name="calGreen" min="0" max="255" value="160"></label>
8080
<label>B <input type="number" name="calBlue" min="0" max="255" value="160"></label>
8181
<label>White <input type="number" name="calGain" min="0" max="255" value="255"></label>
82-
<button type="button" onclick="setCalibration(255, 160, 160, 160)">Set Cold</button>
83-
<button type="button" onclick="setCalibration(255, 176, 176, 112)">Set Neutral</button>
82+
<button type="button" id="cal-cold">Set Cold</button>
83+
<button type="button" id="cal-neutral">Set Neutral</button>
8484
</div>
8585
</div>
8686
</div>
@@ -119,7 +119,7 @@ <h5>White channel calibration</h5>
119119
</div>
120120
</details>
121121

122-
<details id="ota_page" style="display: none;" ontoggle="loadSubScript(this, 'ota', null)">
122+
<details id="ota_page" style="display: none;" ontoggle="loadSubScript('ota', null)">
123123
<summary role="button" class="outline secondary">Firmware Update (OTA)</summary>
124124
<div>
125125
<div style="margin-bottom: 1.5rem; padding: 1rem; border: 1px solid var(--pico-form-element-border-color); border-radius: var(--pico-border-radius); text-align: center; background: rgba(128, 128, 128, 0.05);">
@@ -185,8 +185,8 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
185185
let cfgDeviceVersion = "";
186186

187187
const loadedScripts = {};
188-
function loadSubScript(el, scriptName, initFunName) {
189-
if (el.open && !loadedScripts[scriptName]) {
188+
function loadSubScript(scriptName, initFunName) {
189+
if (!loadedScripts[scriptName]) {
190190
console.log(`Loading module: ${scriptName}`);
191191

192192
const script = document.createElement('script');
@@ -208,99 +208,6 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
208208
}
209209
}
210210

211-
function setupPinValidator() {
212-
const hardwareLimits = {
213-
"ESP32-C6": { gpio: [0,1,2,3,4,5,6,7,8,10,15,18,19,20,21,22], spi: {6:5} },
214-
"ESP32-S3": { gpio: [1,2,4,5,6,7,8,10,16,17,18,48], spi: {11:12} },
215-
"ESP32-C3": { gpio: [0,1,2,3,4,5,6,7,8,10,20,21], spi: {7:6} },
216-
"ESP8266": { gpio: [2], spi: {19:18} },
217-
"ESP32": { gpio: null, spi: {23:18} },
218-
"ESP32-S2": { gpio: null, spi: {19:18} }
219-
};
220-
221-
const arch = (typeof cfgDeviceArchitecture !== 'undefined') ? cfgDeviceArchitecture : "";
222-
const els = { type: document.getElementById('ledType'), clkLabel: document.getElementById('clockPinLabel') };
223-
224-
if (!els.type || !els.clkLabel) {
225-
console.warn("LED Validator: Missing required DOM elements (ledType/clockPinLabel)");
226-
return;
227-
}
228-
229-
els.clkLabel.setAttribute('aria-live', 'polite');
230-
231-
const setField = (name, opts) => {
232-
const old = document.getElementsByName(name)[0];
233-
if (!old) return null;
234-
235-
const isSel = (opts != null);
236-
const signature = "sig_" + JSON.stringify(opts);
237-
238-
if ((old.tagName === 'SELECT') === isSel && old.dataset.sig === signature) return old;
239-
240-
const wasFocused = (document.activeElement === old);
241-
242-
const el = isSel ? document.createElement('select') : Object.assign(document.createElement('input'), {
243-
type: 'number', min: 0, max: (arch.includes('8266') ? 16 : 48), step: '1'
244-
});
245-
246-
el.name = name; el.id = old.id; el.required = true;
247-
el.dataset.sig = signature;
248-
249-
if (isSel) {
250-
opts.forEach(p => el.add(new Option(`GPIO ${p}`, p)));
251-
el.value = opts.includes(parseInt(old.value)) ? old.value : opts[0];
252-
253-
if (opts.length === 1) {
254-
el.disabled = true;
255-
el.title = "Only one valid GPIO for this mode/architecture";
256-
}
257-
} else {
258-
el.value = old.value || 0;
259-
}
260-
261-
old.replaceWith(el);
262-
el.addEventListener('change', updateUI);
263-
264-
if (wasFocused) el.focus();
265-
return el;
266-
};
267-
268-
function updateUI() {
269-
const isSpi = els.type.value == "2";
270-
const cfg = hardwareLimits[arch];
271-
272-
let validPins = cfg ? (isSpi ? Object.keys(cfg.spi).map(Number) : cfg.gpio) : null;
273-
const dataPinEditor = setField('dataPin', validPins);
274-
275-
const autoClk = (cfg && cfg.spi) ? (cfg.spi[dataPinEditor.value] ?? null) : null;
276-
const clockPinEditor = setField('clockPin', ((autoClk !== null) ? [autoClk] : null));
277-
278-
els.clkLabel.style.display = isSpi ? 'block' : 'none';
279-
clockPinEditor.disabled = clockPinEditor.disabled || !isSpi;
280-
}
281-
282-
els.type.onchange = updateUI;
283-
updateUI();
284-
}
285-
286-
function setCalibration(gain, r, g, b) {
287-
const fields = { 'calGain': gain, 'calRed': r, 'calGreen': g, 'calBlue': b };
288-
289-
for (const [name, value] of Object.entries(fields)) {
290-
const el = document.querySelector(`input[name="${name}"]`);
291-
if (el) {
292-
el.value = value;
293-
}
294-
}
295-
}
296-
297-
function toggleCalibration() {
298-
const ledTypeSelect = document.getElementById('ledType');
299-
const calSection = document.getElementById('whiteCalibration');
300-
301-
calSection.style.display = (ledTypeSelect.value === "1") ? "block" : "none";
302-
}
303-
304211
function showToast(isReboot) {
305212
const t = document.getElementById('toast');
306213
const title = document.getElementById('toast-title');
@@ -355,7 +262,9 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
355262
if (el && l[field] !== undefined){
356263
if (field == 'dataPin' || field == 'clockPin')
357264
{
358-
el.add(new Option(`GPIO ${l[field]}`, l[field]));
265+
if (el.tagName === 'SELECT' && !(Array.from(el.options).some(opt => String(opt.value) === String(l[field])))) {
266+
el.add(new Option(`GPIO ${l[field]}`, l[field]));
267+
}
359268
}
360269
el.value = l[field];
361270
el.dispatchEvent(new Event('input'));
@@ -364,40 +273,25 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
364273

365274
if (l["apMode"] === true) {
366275
document.getElementById("wifi_configuration_page").style.display = "block";
367-
loadSubScript(this, 'wifi', "scanWifi");
276+
loadSubScript('wifi', "setupWifi");
368277
} else {
369278
document.getElementById("ota_page").style.display = "block";
370279
}
371280

372-
toggleCalibration();
373-
setupPinValidator();
281+
if (typeof toggleCalibration === "function") {
282+
toggleCalibration();
283+
}
284+
285+
if (typeof setupPinValidator === "function") {
286+
setupPinValidator();
287+
}
374288
}
375289
catch (e) {
376290
console.error("Config load failed:", e);
377291
}
378292
}
379293

380-
document.addEventListener('DOMContentLoaded', () => {
381-
const select = document.getElementById('ssid_select');
382-
const customDiv = document.getElementById('custom_ssid_wrapper');
383-
const customInput = document.getElementById('ssid_custom');
384-
const ledTypeSelect = document.getElementById('ledType');
385-
386-
select.addEventListener('change', () => {
387-
if (select.value === 'CUSTOM') {
388-
customDiv.style.display = 'block';
389-
setTimeout(() => customInput.focus(), 100);
390-
customInput.required = true;
391-
}
392-
else {
393-
customDiv.style.display = 'none';
394-
customInput.required = false;
395-
customInput.value = '';
396-
}
397-
});
398-
399-
ledTypeSelect.addEventListener('change', toggleCalibration);
400-
294+
document.addEventListener('DOMContentLoaded', () => {
401295
loadCurrentConfig();
402296
});
403297

0 commit comments

Comments
 (0)