diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..5cbe254
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,14 @@
+BasedOnStyle: LLVM
+IndentWidth: 4
+ColumnLimit: 100
+BraceWrapping:
+ AfterFunction: false
+ AfterControlStatement: false
+ AfterClass: false
+ AfterEnum: false
+ AfterStruct: false
+ AfterNamespace: false
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
diff --git a/ToDo.txt b/ToDo.txt
deleted file mode 100644
index ac29ba7..0000000
--- a/ToDo.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-
-1) OTA switch to Minio
-
-2) Global variable ( Circular or Linear LEDS)
-
-3) Long Hold to switch
-
-4) Fix white flasing at booth
-
-5)
\ No newline at end of file
diff --git a/data/booths/roamer-big.json b/data/booths/big-roam.json
similarity index 93%
rename from data/booths/roamer-big.json
rename to data/booths/big-roam.json
index 1a15f18..4041ad6 100644
--- a/data/booths/roamer-big.json
+++ b/data/booths/big-roam.json
@@ -1,5 +1,7 @@
{
+ "profile": "big-roam",
"mode": "roamer",
+ "limited-mode": false,
"button": 0,
"buttons":
[
@@ -58,7 +60,7 @@
"en": true,
"relay-index": 0,
"button-index": 0,
- "min": 0.0,
+ "min": 1.0,
"max": 100.0,
"step": 1.5,
"rate": 30.0,
@@ -66,10 +68,10 @@
"vision": true
},
{
- "en": false,
+ "en": true,
"relay-index": 1,
"button-index": 1,
- "min": 0.0,
+ "min": 1.0,
"max": 100.0,
"step": 1.5,
"rate": 30.0,
@@ -106,9 +108,9 @@
"size": 168,
"chip": "SK6812",
"rgb-order": "rgb",
- "shift":-5,
+ "shift":-42,
"offset": 0,
- "bright": 200,
+ "bright": 240,
"power-div": 0,
"i2s-ch": 0,
"core": 1
diff --git a/data/booths/custom.json b/data/booths/custom.json
index 9f7eb89..582a898 100644
--- a/data/booths/custom.json
+++ b/data/booths/custom.json
@@ -1,5 +1,7 @@
{
+ "id": "custom",
"mode": 0,
+ "limited-mode": false,
"buttons":
[
{
@@ -54,14 +56,26 @@
"ramp-lights":
[
{
- "en": true,
- "relay-index": 0,
- "button-index": 0
+ "en": true,
+ "relay-index": 0,
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
- "en": true,
- "relay-index": 1,
- "button-index": 1
+ "en": true,
+ "relay-index": 1,
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/helio-flare.json b/data/booths/flare-posh.json
similarity index 80%
rename from data/booths/helio-flare.json
rename to data/booths/flare-posh.json
index 50419d5..30e35bf 100644
--- a/data/booths/helio-flare.json
+++ b/data/booths/flare-posh.json
@@ -1,6 +1,8 @@
{
- "mode": 0,
- "buttons":
+ "id": "flare-posh",
+ "mode": 0,
+ "limited-mode": false,
+ "buttons":
[
{
"en": true
@@ -56,12 +58,24 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
- "en": false,
+ "en": true,
"relay-index": 1,
- "button-index": 1
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/helio-posh.json b/data/booths/helio-posh.json
deleted file mode 100644
index d43fabd..0000000
--- a/data/booths/helio-posh.json
+++ /dev/null
@@ -1,124 +0,0 @@
-{
- "buttons":
- [
- {
- "en": true
- },
- {
- "en": true
- },
- {
- "en": true
- }
- ],
-"pwmout":
- [
- {
- "en": true,
- "freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
- "vision": false,
- "deltarate": 0
- },
- {
- "en": true,
- "freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
- "vision": false,
- "deltarate": 0
- },
- {
- "en": true,
- "freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
- "vision": true,
- "deltarate": 0
- },
- {
- "en": true,
- "freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
- "vision": true,
- "deltarate": 0
- }
- ],
- "ramp-lights":
- [
- {
- "en": true,
- "relay-index": 0,
- "button-index": 0
- },
- {
- "en": false,
- "relay-index": 1,
- "button-index": 1
- }
- ],
- "oled": {
- "en": false,
- "height": 64,
- "width": 128
- },
- "t-sensor": {
- "en": true,
- "addr": 72,
- "sp1": 85.0,
- "fan-pwr1": 50.0,
- "sp2": 90.0,
- "fan-pwr2": 100.0,
- "hyst": 1.0,
- "relay": 3,
- "interval": 5000
- },
- "adc": {
- "ain1_factor": 1.0
- },
- "status-led":{
- "interval": 250
- },
- "strips":
- [
- {
- "en": true,
- "size": 138,
- "chip": "SK6812",
- "rgb-order": "rgb",
- "shift":-27,
- "offset": 0,
- "power-div": 0,
- "i2s-ch": 0,
- "core": 1
- },
- {
- "en": true,
- "size": 30,
- "chip": "SK6812",
- "rgb-order": "rgb",
- "shift":-27,
- "offset": 0,
- "power-div": 0,
- "i2s-ch": 0,
- "core": 1
- }
- ],
- "rx433": {
- "en": "true"
- },
- "tx433": {
- "en": "true"
- },
- "ble":{
- "en": true,
- "name": "Ata_Lights",
- "core": 1
- }
-}
diff --git a/data/booths/lumia-m.json b/data/booths/m-lumia.json
similarity index 83%
rename from data/booths/lumia-m.json
rename to data/booths/m-lumia.json
index d576c73..ff73390 100644
--- a/data/booths/lumia-m.json
+++ b/data/booths/m-lumia.json
@@ -1,4 +1,6 @@
{
+ "id": "m-lumia",
+ "limited-mode": false,
"mode": 0,
"buttons":
[
@@ -56,12 +58,24 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
"en": true,
"relay-index": 1,
- "button-index": 1
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/m1.json b/data/booths/m1.json
index b4fae80..5e8798d 100644
--- a/data/booths/m1.json
+++ b/data/booths/m1.json
@@ -1,4 +1,5 @@
{
+ "id": "m1",
"mode": 0,
"buttons":
[
@@ -56,12 +57,24 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
"en": true,
"relay-index": 1,
- "button-index": 1
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/marquee.json b/data/booths/marquee.json
index b4fae80..dae0d81 100644
--- a/data/booths/marquee.json
+++ b/data/booths/marquee.json
@@ -1,5 +1,7 @@
{
+ "id": "marquee",
"mode": 0,
+ "limited-mode": false,
"buttons":
[
{
@@ -56,12 +58,24 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
"en": true,
"relay-index": 1,
- "button-index": 1
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/roamer.json b/data/booths/orig-roam.json
similarity index 93%
rename from data/booths/roamer.json
rename to data/booths/orig-roam.json
index 64faa59..f79dab6 100644
--- a/data/booths/roamer.json
+++ b/data/booths/orig-roam.json
@@ -1,5 +1,7 @@
{
+ "id": "orig-roam",
"mode": "roamer",
+ "limited-mode": false,
"button": 0,
"buttons":
[
@@ -58,19 +60,21 @@
"en": true,
"relay-index": 0,
"button-index": 0,
- "min": 0.0,
+ "min": 1.0,
"max": 100.0,
"step": 1.5,
+ "rate": 30.0,
"skip-count": 5,
"vision": true
},
{
- "en": false,
+ "en": true,
"relay-index": 1,
"button-index": 1,
- "min": 0.0,
+ "min": 1.0,
"max": 100.0,
"step": 1.5,
+ "rate": 30.0,
"skip-count": 5,
"vision": true
}
diff --git a/data/booths/helio-sport.json b/data/booths/spectra.json
similarity index 82%
rename from data/booths/helio-sport.json
rename to data/booths/spectra.json
index d04844e..b0f38cb 100644
--- a/data/booths/helio-sport.json
+++ b/data/booths/spectra.json
@@ -1,5 +1,7 @@
{
+ "id": "spectra",
"mode": 0,
+ "limited-mode": false,
"buttons":
[
{
@@ -56,12 +58,24 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
- "en": false,
+ "en": true,
"relay-index": 1,
- "button-index": 1
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/lumia-spectra.json b/data/booths/sport.json
similarity index 83%
rename from data/booths/lumia-spectra.json
rename to data/booths/sport.json
index b4fae80..f4408ad 100644
--- a/data/booths/lumia-spectra.json
+++ b/data/booths/sport.json
@@ -1,5 +1,7 @@
{
+ "id": "sport",
"mode": 0,
+ "limited-mode": false,
"buttons":
[
{
@@ -56,12 +58,24 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
"en": true,
"relay-index": 1,
- "button-index": 1
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/light-stik.json b/data/booths/stik.json
similarity index 86%
rename from data/booths/light-stik.json
rename to data/booths/stik.json
index 21c6e2e..5e3390c 100644
--- a/data/booths/light-stik.json
+++ b/data/booths/stik.json
@@ -1,4 +1,5 @@
{
+ "profile": "stik",
"mode": "stik",
"buttons":
[
@@ -56,13 +57,25 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
"en": true,
"relay-index": 1,
- "button-index": 1
- }
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
+ }
],
"oled": {
"en": false,
diff --git a/data/booths/testbooth.json b/data/booths/testbooth.json
index 9f33075..21a19df 100644
--- a/data/booths/testbooth.json
+++ b/data/booths/testbooth.json
@@ -1,4 +1,5 @@
{
+ "profile": "testbooth",
"mode": 0,
"buttons":
[
@@ -54,24 +55,26 @@
"ramp-lights":
[
{
- "en": false,
- "relay-index": 0,
- "button-index": 0,
- "min": 5.0,
- "max": 100.0,
- "step": 1.5,
- "skip-count": 5,
- "vision": true
+ "en": true,
+ "relay-index": 0,
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
- "en": false,
- "relay-index": 1,
- "button-index": 1,
- "min": 5.0,
- "max": 100.0,
- "step": 1.5,
- "skip-count": 5,
- "vision": true
+ "en": true,
+ "relay-index": 1,
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/booths/lumia-xl.json b/data/booths/xl-lumia.json
similarity index 85%
rename from data/booths/lumia-xl.json
rename to data/booths/xl-lumia.json
index 76d799f..88ae171 100644
--- a/data/booths/lumia-xl.json
+++ b/data/booths/xl-lumia.json
@@ -1,5 +1,7 @@
{
+ "id": "xl-lumia",
"mode": 0,
+ "limited-mode": false,
"buttons":
[
{
@@ -56,12 +58,24 @@
{
"en": true,
"relay-index": 0,
- "button-index": 0
+ "button-index": 0,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
},
{
- "en": true,
+ "en": false,
"relay-index": 1,
- "button-index": 1
+ "button-index": 1,
+ "min": 1.0,
+ "max": 100.0,
+ "step": 1.5,
+ "rate": 30.0,
+ "skip-count": 5,
+ "vision": true
}
],
"oled": {
diff --git a/data/system/ble.json b/data/system/ble.json
index 82da5c8..c43135f 100644
--- a/data/system/ble.json
+++ b/data/system/ble.json
@@ -1,5 +1,5 @@
{
- "name": "ATALIGHTS",
+ "name": "SP110E-ATA",
"unique": true,
"lights-service": "FFE0",
"lights-char": "FFE1",
diff --git a/data/system/system.json b/data/system/system.json
index 83bdb32..c01e52c 100644
--- a/data/system/system.json
+++ b/data/system/system.json
@@ -1,4 +1,4 @@
{
"boardfile": "/boards/board15.json",
- "configfile": "/booths/roamer-big.json"
+ "configfile": "/booths/big-roam.json"
}
\ No newline at end of file
diff --git a/data/www/ata-boothifier-upgrade.html b/data/www/ata-boothifier-upgrade.html
index 53868db..64c5009 100644
--- a/data/www/ata-boothifier-upgrade.html
+++ b/data/www/ata-boothifier-upgrade.html
@@ -589,67 +589,67 @@ IMPORTANT NOTES:
try{
bleDevice = await navigator.bluetooth.requestDevice({
- filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }],
- optionalServices:[BLE_SERVICE_UUID]
- });
+ filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }],
+ optionalServices:[BLE_SERVICE_UUID]
+ });
- const server = await bleDevice.gatt.connect();
- const service = await server.getPrimaryService(BLE_SERVICE_UUID);
- bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
- bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
- await bleCharacteristic2.startNotifications();
- let prevProgressFound = false;
- bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
- try{
- //const view = e.target.value; // DataView
- //const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
-
- // Debug info
- //let debugInfo = `Received ${bytes.length} bytes: `;
- //for (let i = 0; i < bytes.length; i++) {
- // debugInfo += bytes[i].toString(16).padStart(2, '0') + ' ';
- // }
- //console.log(debugInfo);
-
- // Try to decode as text
- let txt = '';
- try {
- //txt = new TextDecoder().decode(bytes);
- txt = new TextDecoder().decode(e.target.value);
- // Remove null terminators and trim
- const nullIdx = txt.indexOf('\0');
- if (nullIdx !== -1) txt = txt.slice(0, nullIdx);
- txt = txt.trim();
- } catch (decodeErr) {
- console.error('Text decode error:', decodeErr);
- // Fallback to showing hex if text decode fails
- txt = '[Binary data]';
- }
-
- // Show both raw and processed data in console
- //console.log('--> Raw bytes:', bytes);
- //console.log('--> As text:', txt);
-
- //logMessage(`--> (${bytes.length} bytes) ${txt}`);
- // progress messages handling variable captured from outer scope
+ const server = await bleDevice.gatt.connect();
+ const service = await server.getPrimaryService(BLE_SERVICE_UUID);
+ bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
+ bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
+ await bleCharacteristic2.startNotifications();
+ let prevProgressFound = false;
+ bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
+ try{
+ //const view = e.target.value; // DataView
+ //const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
+
+ // Debug info
+ //let debugInfo = `Received ${bytes.length} bytes: `;
+ //for (let i = 0; i < bytes.length; i++) {
+ // debugInfo += bytes[i].toString(16).padStart(2, '0') + ' ';
+ // }
+ //console.log(debugInfo);
+
+ // Try to decode as text
+ let txt = '';
+ try {
+ //txt = new TextDecoder().decode(bytes);
+ txt = new TextDecoder().decode(e.target.value);
+ // Remove null terminators and trim
+ const nullIdx = txt.indexOf('\0');
+ if (nullIdx !== -1) txt = txt.slice(0, nullIdx);
+ txt = txt.trim();
+ } catch (decodeErr) {
+ console.error('Text decode error:', decodeErr);
+ // Fallback to showing hex if text decode fails
+ txt = '[Binary data]';
+ }
+
+ // Show both raw and processed data in console
+ //console.log('--> Raw bytes:', bytes);
+ //console.log('--> As text:', txt);
+
+ //logMessage(`--> (${bytes.length} bytes) ${txt}`);
+ // progress messages handling variable captured from outer scope
- if(prevProgressFound && txt.includes('progress')){
- logMessage(`${txt}`, true); // overwrite last line for progress updates
- }
- else{
- logMessage(`${txt}`);
- prevProgressFound = false; // reset if current message isn't progress
- }
+ if(prevProgressFound && txt.includes('progress')){
+ logMessage(`${txt}`, true); // overwrite last line for progress updates
+ }
+ else{
+ logMessage(`${txt}`);
+ prevProgressFound = false; // reset if current message isn't progress
+ }
- if(txt.includes('progress')){
- prevProgressFound = true;
- }
+ if(txt.includes('progress')){
+ prevProgressFound = true;
+ }
- } catch(err) {
- console.error('Processing error', err);
- logMessage('--> Error processing message: ' + err.message);
- }
+ } catch(err) {
+ console.error('Processing error', err);
+ logMessage('--> Error processing message: ' + err.message);
+ }
});
bleConnected=true;
diff --git a/data/www/usercfg.html b/data/www/usercfg.html
new file mode 100644
index 0000000..39598df
--- /dev/null
+++ b/data/www/usercfg.html
@@ -0,0 +1,1324 @@
+
+
+
+
+
+
+ ATA Lights Config
+
+
+
+
+
+
+
+ 🌡️ Temp: --.- °F
+
+ ATA Lights Config
+
+ ⚡ V-IN: --.- V
+
+
+
+
+
+
+
BLE Comm: Disconnected
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ReBoot Device?
+
This will reboot the connected device. Are you sure?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/SP110E Info.xlsx b/docs/SP110E Info.xlsx
new file mode 100644
index 0000000..c00563f
Binary files /dev/null and b/docs/SP110E Info.xlsx differ
diff --git a/docs/ToDo.txt b/docs/ToDo.txt
new file mode 100644
index 0000000..50f6811
--- /dev/null
+++ b/docs/ToDo.txt
@@ -0,0 +1,24 @@
+
+Board to do:
+2) make sure no fixed indexes to pwmOutput and other peripherals
+
+3) Implement sending packet with current values to ble appearance
+
+4) figure out how to compare profile comparison before savoing to system.json
+
+5) made mode string instead of number.
+
+6) Comments on json file Enable?
+ #define ARDUINOJSON_ENABLE_COMMENTS 1
+ #include
+
+ permits: // or /* lsslslslslsl */
+
+
+7) Use same struct of setting for to and from in ble
+
+8) learn about Blob in BLE
+
+######### Configuration Page ############
+done
+
\ No newline at end of file
diff --git a/docs/~$SP110E Info.xlsx b/docs/~$SP110E Info.xlsx
new file mode 100644
index 0000000..8b381df
Binary files /dev/null and b/docs/~$SP110E Info.xlsx differ
diff --git a/esp32s3_atabooth_8mb.code-workspace b/esp32s3_atabooth_8mb.code-workspace
index 2ce8fdd..c92c6d8 100644
--- a/esp32s3_atabooth_8mb.code-workspace
+++ b/esp32s3_atabooth_8mb.code-workspace
@@ -2,9 +2,6 @@
"folders": [
{
"path": "."
- },
- {
- "path": "../esp32s3_module_8mb"
}
],
"settings": {
diff --git a/include/ATALights.h b/include/ATALights.h
index 4545449..ef1cc89 100644
--- a/include/ATALights.h
+++ b/include/ATALights.h
@@ -5,13 +5,21 @@
#include "ColorPalettes.h"
#include "PWM_Output.h"
-#define PIXEL_INDEX -3
+#define SHIFT_INDEX -3
#define SOLID_COLOR_INDEX -2
#define OFF_INDEX -1
#define WITH_GAPS true
#define NO_GAPS false
+enum CHIP_TYPE {
+ CHIP_WS2812B = 0,
+ CHIP_SK6812,
+ CHIP_WS2811,
+ CHIP_WS2815,
+ CHIP_UNKNOWN
+};
+
extern uint32_t whiteTimeout;
typedef struct {
@@ -39,19 +47,53 @@ typedef struct {
int shift;
int offset;
int powerDiv;
- int effSize;
+ //int effSize;
uint8_t bright;
uint8_t i2sCh;
uint8_t core;
uint8_t pin;
}LEDSTRIP_SETTINGS;
+typedef struct __attribute__((packed)) {
+ uint8_t cmd;
+ uint8_t limitedMode;
+ float temperature;
+ float vIn;
+ char profile[10];
+
+ uint8_t ledChipset1;
+ char rgbOrder1[4]; // 3 chars + null terminator
+ uint8_t ledCount1;
+ uint8_t ledShift1;
+ uint8_t ledBrightness1;
+
+ uint8_t ledChipset2;
+ char rgbOrder2[4]; // 3 chars + null terminator
+ uint8_t ledCount2;
+ uint8_t ledShift2;
+ uint8_t ledBrightness2;
+
+ uint8_t frontLightMin;
+ uint8_t frontLightMax;
+ uint8_t rearLightMin;
+ uint8_t rearLightMax;
+ uint8_t fanLowerTemp;
+ uint8_t fanUpperTemp;
+} USER_SETTINGS;
+
+enum BOOTH_PROFILE {
+ PROFILE_CUSTOM=0,
+ PROFILE_ROAMER,
+ PROFILE_STIK,
+ PROFILE_COUNT
+};
+
extern LEDSTRIP_SETTINGS ledSettings[2];
void RGB_Lights_Control_Task(void *parameters);
-void Init_RGB_Lights_Task(void);
+void Init_RGB_Lights_Task(bool upgrade);
void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright);
@@ -72,6 +114,8 @@ void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack);
void loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src);
+void SetAndSaveUserSettings(USER_SETTINGS &userSettings);
+
//void Init_Ramp_Front_Light_Task(void);
diff --git a/include/AppUpgrade.h b/include/AppUpgrade.h
index a4696f7..e478038 100644
--- a/include/AppUpgrade.h
+++ b/include/AppUpgrade.h
@@ -8,6 +8,7 @@
#include
#include "AppVersion.h"
+
//#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/"
#define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/"
#define BUFFER_SIZE 2048 // Reduced from 4096 to use less memory
@@ -171,6 +172,7 @@ class AppUpdater {
fs::FS& fileSystem;
UpdateStatus status;
std::unique_ptr downloadBuffer;
+ size_t downloadBufferSize = 0; /**< Actual size allocated for downloadBuffer */
bool updateAvailable = false;
UpdateMode updateMode = UpdateMode::UPDATE_BOTH; // Default to updating both files and firmware
@@ -223,7 +225,7 @@ void firmwareUpdateTask(void* param);
void startFirmwareUpdateTask(AsyncEventSource* eventSrc);
-void loadUpdateJson(void);
+bool loadUpdateJson(void);
void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message);
@@ -243,3 +245,5 @@ void setUpdateModeFilesOnly();
void setUpdateModeFirmwareOnly();
void setUpdateModeBoth();
+
+
diff --git a/include/BLE_SP110E.h b/include/BLE_SP110E.h
index db2a470..28e5983 100644
--- a/include/BLE_SP110E.h
+++ b/include/BLE_SP110E.h
@@ -19,6 +19,11 @@
#define SET_SPEED 0x03
#define SET_AUTO_MODE 0x06
+// My custom commands
+#define SET_USER_SETTINGS 0xB0
+#define SET_SHIFT 0xB1
+
+
// Initializes the BLE server, services, and advertising
void Init_BLE_SP110E(NimBLEServer* pServer);
diff --git a/include/ColorPalettes.h b/include/ColorPalettes.h
index 2df61e1..d8b06bc 100644
--- a/include/ColorPalettes.h
+++ b/include/ColorPalettes.h
@@ -28,9 +28,9 @@ const COLOR_PACK colorPack_RED_WHITE PROGMEM = { 2, { CRGB::Red, CRGB::White } }
const COLOR_PACK colorPack_ORANGE_WHITE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::White } };
const COLOR_PACK colorPack_GREEN_WHITE PROGMEM = { 2, { CRGB::Green, CRGB::White } };
const COLOR_PACK colorPack_BLUE_WHITE PROGMEM = { 2, { CRGB::Blue, CRGB::White } };
-const COLOR_PACK colorPack_PINK_WHITE PROGMEM = { 2, { CRGB::Pink, CRGB::White } };
+const COLOR_PACK colorPack_MAGENTA_WHITE PROGMEM = { 2, { CRGB::Magenta, CRGB::White } };
const COLOR_PACK colorPack_PURPLE_WHITE PROGMEM = { 2, { CRGB::DarkViolet, CRGB::White } };
-const COLOR_PACK colorPack_PURPLE_PINK PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Pink } };
+const COLOR_PACK colorPack_PURPLE_MAGENTA PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Magenta } };
const COLOR_PACK colorPack_PURPLE_YELLOW PROGMEM = { 2, { CRGB::Purple, CRGB::Yellow } };
@@ -45,11 +45,10 @@ const COLOR_PACK colorPack_BLUE_RED PROGMEM = { 2, { CRGB::Blue, CRGB::Red } };
const COLOR_PACK colorPack_ORANGE_BLUE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Blue } };
const COLOR_PACK colorPack_RED_GREEN PROGMEM = { 2, { CRGB::Red, CRGB::Green } };
const COLOR_PACK colorPack_CYAN_RED PROGMEM = { 2, { CRGB::Cyan, CRGB::Red } };
-const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } };
// Warm/cool combinations
const COLOR_PACK colorPack_ORANGE_CYAN PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Cyan } };
-const COLOR_PACK colorPack_PINK_GREEN PROGMEM = { 2, { CRGB::Pink, CRGB::Green } };
+const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } };
const COLOR_PACK colorPack_VIOLET_LIME PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Lime } };
// Analogous combinations
@@ -67,28 +66,28 @@ const COLOR_PACK double_colorPacks[] PROGMEM = {
colorPack_RED_WHITE,
colorPack_RED_YELLOW,
colorPack_BLUE_WHITE,
- colorPack_PINK_WHITE,
- colorPack_GREEN_WHITE,
colorPack_PURPLE_WHITE,
- colorPack_GREEN_YELLOW,
- colorPack_PURPLE_PINK,
- colorPack_CYAN_RED,
-
+ colorPack_MAGENTA_WHITE,
+ colorPack_GREEN_WHITE,
colorPack_ORANGE_WHITE,
+ colorPack_PURPLE_MAGENTA,
+ colorPack_GREEN_YELLOW,
+ // 9 pack
+
- colorPack_PURPLE_PINK,
+ colorPack_CYAN_RED,
+ colorPack_PURPLE_MAGENTA,
colorPack_PURPLE_YELLOW,
-
colorPack_BLUE_YELLOW,
-
colorPack_BLUE_GREEN,
colorPack_BLUE_RED,
colorPack_ORANGE_BLUE,
+ // 16 pack
+
+
colorPack_RED_GREEN,
-
colorPack_MAGENTA_GREEN,
colorPack_ORANGE_CYAN,
- colorPack_PINK_GREEN,
colorPack_VIOLET_LIME,
colorPack_RED_ORANGE,
colorPack_BLUE_PURPLE,
@@ -101,12 +100,12 @@ const COLOR_PACK double_colorPacks[] PROGMEM = {
// Sectors
-const COLOR_PACK colorPack_RAINBOW PROGMEM = { 7, { CRGB::Red, CRGB::OrangeRed, CRGB::Yellow, CRGB::Green, CRGB::Blue, CRGB::BlueViolet, CRGB::MediumVioletRed } };
+const COLOR_PACK colorPack_RAINBOW PROGMEM = { 7, { CRGB::Red, CRGB::DarkOrange, CRGB::LightYellow, CRGB::Green, CRGB::Blue, CRGB::DarkViolet, CRGB::MediumVioletRed } };
// Triple Color Packs, Common Flags
const COLOR_PACK colorPack_RED_WHITE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Blue } };
const COLOR_PACK colorPack_RED_WHITE_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Green } };
const COLOR_PACK colorPack_RED_YELLOW_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Blue } };
-const COLOR_PACK colorPack_RED_ORANGE_YELLOW PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Yellow } };
+const COLOR_PACK colorPack_RED_ORANGE_YELLOW PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Yellow } };
const COLOR_PACK colorPack_RED_YELLOW_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Green } };
const COLOR_PACK colorPack_RED_PURPLE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Purple, CRGB::Blue } };
@@ -114,13 +113,11 @@ const COLOR_PACK colorPack_GREEN_WHITE_RED PROGMEM = { 3, { CRGB::Green, CRGB::
const COLOR_PACK colorPack_GREEN_WHITE_ORANGE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::DarkOrange } };
const COLOR_PACK colorPack_GREEN_WHITE_BLUE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Blue } };
-
-const COLOR_PACK colorPack_BLUE_WHITE_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::White, CRGB::Green } };
const COLOR_PACK colorPack_BLUE_YELLOW_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Green } };
const COLOR_PACK colorPack_BLUE_YELLOW_RED PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Red } };
// Additional triple color combinations
-const COLOR_PACK colorPack_PURPLE_PINK_CYAN PROGMEM = { 3, { CRGB::Purple, CRGB::Pink, CRGB::Cyan } };
+const COLOR_PACK colorPack_MAGENTA_WHITE_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::White, CRGB::Cyan } };
const COLOR_PACK colorPack_ORANGE_YELLOW_LIME PROGMEM = { 3, { CRGB::DarkOrange, CRGB::Yellow, CRGB::Lime } };
const COLOR_PACK colorPack_MAGENTA_YELLOW_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::Yellow, CRGB::Cyan } };
const COLOR_PACK colorPack_LIME_CYAN_MAGENTA PROGMEM = { 3, { CRGB::Lime, CRGB::Cyan, CRGB::Magenta } };
@@ -149,27 +146,30 @@ const COLOR_PACK tripple_colorPacks[] PROGMEM = {
colorPack_GREEN_WHITE_ORANGE,
colorPack_BLUE_YELLOW_GREEN,
colorPack_RED_GREEN_BLUE,
- colorPack_PURPLE_PINK_CYAN,
+ colorPack_MAGENTA_WHITE_CYAN,
colorPack_YELLOW_ORANGE_RED,
colorPack_BLUE_CYAN_LIME,
- colorPack_PINK_PURPLE_MAGENTA,
-
colorPack_RED_YELLOW_BLUE,
+ // 9 pack
+
+
colorPack_RED_ORANGE_YELLOW,
colorPack_RED_YELLOW_GREEN,
colorPack_RED_PURPLE_BLUE,
colorPack_GREEN_WHITE_RED,
colorPack_GREEN_WHITE_BLUE,
- colorPack_BLUE_WHITE_GREEN,
colorPack_BLUE_YELLOW_RED,
+ colorPack_GREEN_CYAN_BLUE,
+ // 16 pack
+
+
colorPack_ORANGE_YELLOW_LIME,
colorPack_MAGENTA_YELLOW_CYAN,
colorPack_LIME_CYAN_MAGENTA,
colorPack_RED_ORANGE_PINK,
colorPack_PURPLE_BLUE_CYAN,
- colorPack_GREEN_CYAN_BLUE,
colorPack_YELLOW_PURPLE_ORANGE,
- colorPack_PINK_LIME_PURPLE
+
};
const COLOR_PACK colorPack_grad_blueish PROGMEM = { 3, { CRGB::Blue, CRGB::Cyan, CRGB::Green } };
@@ -200,7 +200,7 @@ const COLOR_PACK colorPack_Single_Viloet PROGMEM = { 1, { CRGB::DarkViolet } };
const COLOR_PACK colorPack_Single_Magenta PROGMEM = { 1, { CRGB::Magenta } };
const COLOR_PACK colorPack_Single_White PROGMEM = { 1, { CRGB::White } };
-// 9 Single Color Packs
+// 8 Single Color Packs
const COLOR_PACK single_colorPacks[] PROGMEM = {
colorPack_Single_White,
colorPack_Single_Red,
diff --git a/include/PWM_Output.h b/include/PWM_Output.h
index 8cf92c2..21d5978 100644
--- a/include/PWM_Output.h
+++ b/include/PWM_Output.h
@@ -38,6 +38,8 @@ class PWM_Output {
float standardFactor;
float visionFactor;
+ int8_t pin;
+ bool initialized = false;
};
diff --git a/include/Ramp_Lights.h b/include/Ramp_Lights.h
index 4210ff6..edbdabc 100644
--- a/include/Ramp_Lights.h
+++ b/include/Ramp_Lights.h
@@ -18,7 +18,7 @@ private:
float currentValue;
bool IsOn = false;
RAMP_STATE rampState;
- int tickCount;
+ int tickCount = 0;
void tick();
void singleClick();
diff --git a/include/global.h b/include/global.h
index f29adc0..71f5d70 100644
--- a/include/global.h
+++ b/include/global.h
@@ -60,3 +60,5 @@ void Log_CPU_Load(void);
void print_task_watermarks(void);
+float updateLowpass(float currentValue, float newValue, float alpha);
+
diff --git a/include/my_device.h b/include/my_device.h
index a9ae4f9..f6a6581 100644
--- a/include/my_device.h
+++ b/include/my_device.h
@@ -9,6 +9,10 @@ extern SYS_SETTINGS sys_settings;
extern PWM_Output *pwmOutputs[4];
extern RAMP_LIGHT *rampLight1;
extern RAMP_LIGHT *rampLight2;
+extern String booth_file_path;
+extern float PowerVin;
+extern float PowerVinAlpha;
+extern float prevPowerVin;
void Init_ADC(void);
float readBoardInputVoltage(void);
diff --git a/include/my_wifi.h b/include/my_wifi.h
index 2745c16..3de48b2 100644
--- a/include/my_wifi.h
+++ b/include/my_wifi.h
@@ -23,7 +23,7 @@ void handleGET_Query(AsyncWebServerRequest *request);
void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
-void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&));
+bool sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&));
String fileManagerHtmlProcessor(const String& var);
String HomeHtmlProcessor(const String& var);
String listDirAsHtml(String directoryList[], int count);
diff --git a/include/system.h b/include/system.h
index ad74af9..8e96366 100644
--- a/include/system.h
+++ b/include/system.h
@@ -5,6 +5,9 @@
#include
#include "ATALights.h"
+
+enum LED_CHIPSET { LED_CHIPSET_WS2812B=0, LED_CHIPSET_SK6812=1, LED_CHIPSET_WS2811_400=2, LED_CHIPSET_WS2815=3, LED_CHIPSET_NONE=255 };
+
typedef struct {
bool enabled;
}BTN_SETTINGS;
@@ -79,6 +82,8 @@ typedef struct {
enum BOOTH_MODE { BOOTH_MODE_NONE=0, BOOTH_MODE_ROAMER=1, BOOTH_MODE_STIK=2 };
typedef struct {
+ String profile;
+ bool limitedMode;
BOOTH_MODE mode;
BOARD_PINS boardPins;
BTN_SETTINGS btnSettings[3];
diff --git a/platformio.ini b/platformio.ini
index 9ff1f0c..04331e5 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -24,6 +24,7 @@ lib_deps =
jeremycole/I2C Temperature Sensors derived from the LM75 @ ^1.0.3
mathertel/OneButton @ ^2.6.1
h2zero/NimBLE-Arduino @ ^1.4.1
+ #h2zero/NimBLE-Arduino @ ^2.3.6
adafruit/Adafruit SSD1306 @ ^2.5.7
fastled/FastLED @ ^3.9.4
marian-craciunescu/ESP32Ping@^1.7
@@ -35,6 +36,11 @@ build_flags =
#-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO
-D CONFIG_ARDUHAL_LOG_COLORS=1
+ #-Os
+ #-ffunction-sections
+ #-fdata-sections
+ #-Wl,--gc-sections
+
upload_port = COM5
debug_init_break = tbreak setup
monitor_port = COM5
diff --git a/src/ATALights.cpp b/src/ATALights.cpp
index faeacde..1ee214a 100644
--- a/src/ATALights.cpp
+++ b/src/ATALights.cpp
@@ -9,16 +9,16 @@
#include "system.h"
#include "ColorPalettes.h"
#include "global.h"
-//#include
#include "PWM_Output.h"
#include "my_device.h"
#include "system.h"
#include "my_device.h"
+#include "LittleFS.h"
#define FASTLED_CORE 0
static const char* tag = "strips";
-uint32_t whiteTimeout = 0;
+//uint32_t whiteTimeout = 0;
TaskHandle_t Animation_Task_Handle;
TaskHandle_t Ramp_Front_Light_Task_Handle;
@@ -27,7 +27,13 @@ volatile bool AnimationLooping = false;
ANIM_EVENT prevAnimEvent = {0};
QueueHandle_t animationQueue = xQueueCreate( 4, sizeof( ANIM_EVENT ) );
-bool fillAnimationActive = false;
+bool upgradeMode = false;
+
+#define LIMITED_ANIMATION true
+
+#define DEFAULT_ANIMATION 23
+
+//bool fillAnimationActive = false;
void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){
@@ -47,7 +53,8 @@ void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t b
}
void RGB_Animations_ON(){
- RGB_Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu);
+ //RGB_Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu);
+ RGB_Lights_Set_Animation(DEFAULT_ANIMATION, 0, 0, 0); // set comet rainbow animation
}
void RGB_Animations_OFF(){
@@ -63,7 +70,9 @@ void Lights_Set_White(uint8_t val){
//pwmOut[0]->setOutput(val / 2.5f);
}
-void Init_RGB_Lights_Task(void){
+
+void Init_RGB_Lights_Task(bool upgrade){
+ upgradeMode = upgrade;
ledSettings[0].leds = new CRGB[ledSettings[0].size];
Init_RGB_Strip(ledSettings[0].leds, ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip, ledSettings[0].bright);
ESP_LOGD(tag, "Initializing Strip1: Pin: %d, size: %d, order: %s, chip: %s", ledSettings[0].pin, ledSettings[0].size, ledSettings[0].rgbOrder, ledSettings[0].chip);
@@ -234,31 +243,61 @@ inline int calcPixelIndex(LEDSTRIP_SETTINGS& strip, int index) {
return (x +strip.offset) % strip.effSize;
}
*/
+// ...existing code...
+inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) {
+ // Guard against invalid configuration that can cause crashes (null pointer or zero size)
+ if (leds.size <= 0 || leds.leds == nullptr) {
+ ESP_LOGW(tag, "setPixel1: invalid strip (Size=%d, leds=%p)", leds.size, (void*)leds.leds);
+ return;
+ }
+ // Use a signed type so negative pixelIndex is preserved
+ int x = pixelIndex + leds.shift;
+ int n = leds.size;
+
+ // If strip.effSize is power of 2: (n & (n - 1)) == 0
+ int idx;
+ if ((n & (n - 1)) == 0) {
+ // Masking is safe if we treat x as unsigned for masking,
+ // this yields the correct wrapped index for two's-complement
+ idx = static_cast( (static_cast(x)) & (static_cast(n) - 1u) );
+ } else {
+ // General modulo: C++ '%' can be negative, so normalize
+ idx = x % n;
+ if (idx < 0) idx += n;
+ }
+
+ // Normalize final position robustly (handles negative offsets)
+ int pos = (idx + leds.offset) % n;
+ if (pos < 0) pos += n;
+
+ leds.leds[pos] = col;
+}
+/*
inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) {
register uint16_t x = pixelIndex + ledSettings[0].shift;
- // If strip.effSize is power of 2, use faster bit masking
- if ((leds.effSize & (leds.effSize - 1)) == 0) {
- x = (x < 0) ? ((x + leds.effSize) & (leds.effSize - 1)) : (x & (leds.effSize - 1));
- leds.leds[(x + leds.offset) & (leds.effSize - 1)] = col;
+ // If strip.size is power of 2, use faster bit masking
+ if ((leds.size & (leds.size - 1)) == 0) {
+ x = (x < 0) ? ((x + leds.size) & (leds.size - 1)) : (x & (leds.size - 1));
+ leds.leds[(x + leds.offset) & (leds.size - 1)] = col;
} else {
// For non-power-of-2 sizes, still need modulo
- x = (x < 0) ? ((x + ledSettings[0].effSize) % ledSettings[0].effSize) : (x % ledSettings[0].effSize);
- ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].effSize] = col;
+ x = (x < 0) ? ((x + ledSettings[0].size) % ledSettings[0].size) : (x % ledSettings[0].size);
+ ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].size] = col;
}
}
-
+*/
inline void setPixel2(int pixelIndex, const CRGB col) {
register uint16_t x = pixelIndex + ledSettings[1].shift;
- // If strip.effSize is power of 2, use faster bit masking
- if ((ledSettings[1].effSize & (ledSettings[1].effSize - 1)) == 0) {
- x = (x < 0) ? ((x + ledSettings[1].effSize) & (ledSettings[1].effSize - 1)) : (x & (ledSettings[1].effSize - 1));
- ledSettings[1].leds[(x + ledSettings[1].offset) & (ledSettings[1].effSize - 1)] = col;
+ // If strip.size is power of 2, use faster bit masking
+ if ((ledSettings[1].size & (ledSettings[1].size - 1)) == 0) {
+ x = (x < 0) ? ((x + ledSettings[1].size) & (ledSettings[1].size - 1)) : (x & (ledSettings[1].size - 1));
+ ledSettings[1].leds[(x + ledSettings[1].offset) & (ledSettings[1].size - 1)] = col;
} else {
// For non-power-of-2 sizes, still need modulo
- x = (x < 0) ? ((x + ledSettings[1].effSize) % ledSettings[1].effSize) : (x % ledSettings[1].effSize);
- ledSettings[1].leds[(x + ledSettings[1].offset) % ledSettings[1].effSize] = col;
+ x = (x < 0) ? ((x + ledSettings[1].size) % ledSettings[1].size) : (x % ledSettings[1].size);
+ ledSettings[1].leds[(x + ledSettings[1].offset) % ledSettings[1].size] = col;
}
}
@@ -286,7 +325,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
if(pin == 3){
// First level: Chip type selection
- if (chipUpper == "WS2812B" || chipUpper == "SK6812") {
+ if (chipUpper == "WS2812B") {
switch(rgbOrder) {
case RGB: FastLED.addLeds(leds, size); break;
case RBG: FastLED.addLeds(leds, size); break;
@@ -308,7 +347,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
default: FastLED.addLeds(leds, size); break;
}
}
- else if (chipUpper == "WS2811_400") {
+ else if (chipUpper == "WS2811") {
switch(rgbOrder) {
case RGB: FastLED.addLeds(leds, size); break;
case RBG: FastLED.addLeds(leds, size); break;
@@ -361,7 +400,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
default: FastLED.addLeds(leds, size); break;
}
}
- else if (chipUpper == "WS2811_400") {
+ else if (chipUpper == "WS2811") {
switch(rgbOrder) {
case RGB: FastLED.addLeds(leds, size); break;
case RBG: FastLED.addLeds(leds, size); break;
@@ -392,7 +431,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
}
else if(pin == 46){
// First level: Chip type selection
- if (chipUpper == "WS2812B" || chipUpper == "SK6812") {
+ if (chipUpper == "WS2812B") {
switch(rgbOrder) {
case RGB: FastLED.addLeds(leds, size); break;
case RBG: FastLED.addLeds(leds, size); break;
@@ -414,7 +453,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
default: FastLED.addLeds(leds, size); break;
}
}
- else if (chipUpper == "WS2811_400") {
+ else if (chipUpper == "WS2811") {
switch(rgbOrder) {
case RGB: FastLED.addLeds(leds, size); break;
case RBG: FastLED.addLeds(leds, size); break;
@@ -439,7 +478,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
else {
// Default to WS2812B if unknown chip type
ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str());
- FastLED.addLeds(leds, size);
+ FastLED.addLeds(leds, size);
}
ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin);
}
@@ -449,147 +488,392 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
void RGB_Lights_Control_Task(void *parameters){
+ // Wait for other tasks to initialize
+ vTaskDelay(pdMS_TO_TICKS(200)); // wait for everything to settle
+ RGB_Lights_Set_Brightness(ledSettings[0].bright);
+
+ if(upgradeMode){
+ // fill with 3 colors equally magenta, cyan, lime
+ RGB_Lights_Set_Brightness(64);
+ fill_gradient_RGB(ledSettings[0].leds, ledSettings[0].size, CRGB(CRGB::Magenta), CRGB(CRGB::Cyan), CRGB(CRGB::Lime), CRGB(CRGB::Magenta));
+ FastLED.show();
+ }else{
+ RGB_Lights_Set_Animation(DEFAULT_ANIMATION, 0, 0, 0); // set comet rainbow animation
+ }
ANIM_EVENT AnimEvent;
- CRGB col;
- COLOR_PACK colorPack;
- CRGBPalette16 firePalette;
+
+ if (LIMITED_ANIMATION){
+ while (true) {
+ if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
+ ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
+ switch (AnimEvent.AnimationIndex) {
- // Wait for other tasks to initialize (including front light task)
- vTaskDelay(pdMS_TO_TICKS(500)); // wait for everything to settle
-
- RGB_Lights_Set_Brightness(48);
- RGB_Lights_Set_Animation(23, 0, 0, 0); // set comet rainbow animation
-
-
- while (true) {
- if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
- ESP_LOGI(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
- switch (AnimEvent.AnimationIndex) {
- case -3: // Set Pixel by index
- if (AnimEvent.data.data[7] >= 0 && AnimEvent.data.data[7] < ledSettings[0].size) {
- ledSettings[0].leds[AnimEvent.data.data[7]] = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu);
+ case -3: // Set Shift
+ if (AnimEvent.data.data[0] >= 0 && AnimEvent.data.data[0] < ledSettings[0].size) {
+ fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
+ ledSettings[0].shift = AnimEvent.data.data[0];
+ vTaskDelay(25 / portTICK_PERIOD_MS);
+ setPixel1(ledSettings[0], 0, CRGB::White * FastLED.getBrightness() / 255);
+ FastLED.show();
+ ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift);
+ } else {
+ ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[0]);
+ }
+ break;
+ case -2: // Fill Static Color
+ fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu));
FastLED.show();
- } else {
- ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[7]);
+ ESP_LOGD(tag, "Static Color");
+ break;
+ case -1:
+ FastLED.clear();
+ FastLED.show();
+ ESP_LOGD(tag, "LEDs Off");
+ break;
+ case 0:
+ fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
+ FastLED.show();
+ break;
+ case 1 ... 5: { // Timed Fill Animations
+ int timeDuration = AnimEvent.AnimationIndex * 1000 - 400;
+ int whiteDelay = timeDuration - 1000;
+ if (whiteDelay < 800) whiteDelay = 800; // minimum 8 seconds
+ Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 20000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift);
+ break;
}
- break;
- case -2: // Fill Static Color
- col = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu);
- fill_solid(ledSettings[0].leds, ledSettings[0].size, col);
- FastLED.show();
- ESP_LOGD(tag, "Static Color");
- break;
- case -1:
- FastLED.clear();
- FastLED.show();
- ESP_LOGD(tag, "LEDs Off");
- break;
- case 0:
- fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
- FastLED.show();
- break;
- case 1: case 2: case 3: case 4: case 5:{ // Timed Fill Animations
- int timeDuration = AnimEvent.AnimationIndex * 1000;
- int whiteDelay = timeDuration - 1000;
- Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 10000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift);
- break;
- }
- case 6:
- // RGB White and Dedicated White all on with timeout and brightness reduction
- if (pwmOutputs[0] != nullptr) {
+ case 6:
+ // RGB White and Dedicated White all on with timeout and brightness reduction
Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000);
+ break;
+ case 7:
+ Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60);
+ break;
+ case 8 ... 12:
+ Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80);
+ break;
+ case 13 ... 17: { // Fire Animations
+ COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable
+ CRGBPalette16 firePalette;
+ createFirePalette(firePalette, fp);
+ Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift);
+ break;
}
- break;
- case 7:
- Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60);
- break;
- case 8: case 9: case 10: case 11: case 12:
- Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80);
- break;
- case 13: case 14: case 15: case 16: case 17: { // Fire Animations
- COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable
- createFirePalette(firePalette, fp);
- Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift);
- break;
+ case 18 ... 20: // Sparkle/Twinkle
+ Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
+ break;
+ case 21:
+ Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90);
+ break;
+ case 22: // Rain Animation
+ Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40);
+ break;
+
+ // Comets
+ case 23:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1);
+ break;
+ case 24 ... 31:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6);
+ break;
+ case 32 ... 40:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3);
+ break;
+ case 41 ... 49:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 41], 40, 85, RANDOM_DECAY, 2);
+ break;
+
+ // Snakes
+ case 50:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50);
+ break;
+ case 51 ... 58:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 51], 60, 6);
+ break;
+ case 59 ... 67:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 59], 60, 3);
+ break;
+ case 68 ... 76:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 68], 60, 2);
+ break;
+
+ // Sectors
+ case 77:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90);
+ break;
+ case 78 ... 86:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 78], NO_GAPS, 3, 40, 90);
+ break;
+ case 87 ... 95:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 87], NO_GAPS, 2, 40, 90);
+ break;
+
+ // Dashes
+ case 96:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90);
+ break;
+ case 97 ... 104:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 97], WITH_GAPS, 6, 40, 90);
+ break;
+ case 105 ... 113:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 105], WITH_GAPS, 3, 40, 90);
+ break;
+ case 114 ... 122:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 114], WITH_GAPS, 2, 40, 90);
+ break;
+ default:
+ ESP_LOGW(tag, "Loop default");
+ break;
}
- case 18: case 19: case 20:// Sparkle/Twinkle
- Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
- break;
- case 21:
- //Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90);
- Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40);
- break;
- case 22: // Rain Animation
- break;
-
- // Comets
- case 23:
- Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1);
- break;
- case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31:
- Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6);
- break;
- case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40:
- Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3);
- break;
- case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49:
- Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 41], 40, 85, RANDOM_DECAY, 2);
- break;
-
- // Snakes
- case 50:
- Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50);
- break;
- case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58:
- Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 51], 60, 6);
- break;
- case 59: case 60: case 61: case 62: case 63: case 64: case 65: case 66: case 67:
- Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 59], 60, 3);
- break;
- case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76:
- Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 68], 60, 2);
- break;
-
- // Sectors
- case 77:
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90);
- break;
- case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86:
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 78], NO_GAPS, 3, 40, 90);
- break;
- case 87: case 88: case 89: case 90: case 91: case 92: case 93: case 94: case 95:
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 87], NO_GAPS, 2, 40, 90);
- break;
-
- // Dashes
- case 96:
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90);
- break;
- case 97:case 98:case 99: case 100: case 101: case 102: case 103: case 104:
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 97], WITH_GAPS, 6, 40, 90);
- break;
- case 105: case 106: case 107: case 108: case 109: case 110: case 111: case 112: case 113:
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 105], WITH_GAPS, 3, 40, 90);
- break;
- case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122:
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 114], WITH_GAPS, 2, 40, 90);
- break;
-
- default:
- ESP_LOGW(tag, "Loop default");
- break;
+ AnimationLooping = false;
+ prevAnimEvent = AnimEvent;
+ ESP_LOGD(tag, "Going to Queue to Wait");
}
+ }
+ } else {
+ // Extended Animation Set
+ while (true) {
+ if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
+ ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
+ switch (AnimEvent.AnimationIndex) {
+ case -3: // Set Shift
+ if (AnimEvent.data.data[0] >= 0 && AnimEvent.data.data[0] < ledSettings[0].size) {
+ fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
+ ledSettings[0].shift = AnimEvent.data.data[0];
- AnimationLooping = false;
- prevAnimEvent = AnimEvent;
- ESP_LOGD(tag, "Going to Queue to Wait");
+ ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift);
+ vTaskDelay(25 / portTICK_PERIOD_MS);
+ setPixel1(ledSettings[0], 0, CRGB::White);
+
+ FastLED.show();
+ } else {
+ ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[0]);
+ }
+ break;
+ case -2: // Fill Static Color
+ fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu));
+ FastLED.show();
+ ESP_LOGD(tag, "Static Color");
+ break;
+ case -1:
+ FastLED.clear();
+ FastLED.show();
+ ESP_LOGD(tag, "LEDs Off");
+ break;
+ case 0:
+ fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
+ FastLED.show();
+ break;
+ case 1 ... 5: { // Timed Fill Animations
+ int timeDuration = AnimEvent.AnimationIndex * 1000 - 400;
+ int whiteDelay = timeDuration - 1000;
+ if (whiteDelay < 800) whiteDelay = 800; // minimum 8 seconds
+ Anim_TimedFill_Flash(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, pwmOutputs[0], &sys_settings.pwmOutSettings[0], whiteDelay, 10000, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift);
+ break;
+ }
+ case 6:
+ // RGB White and Dedicated White all on with timeout and brightness reduction
+ Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000);
+ break;
+ case 7:
+ Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60);
+ break;
+ case 8 ... 12:
+ Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80);
+ break;
+ case 13 ... 17: { // Fire Animations
+ COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable
+ CRGBPalette16 firePalette;
+ createFirePalette(firePalette, fp);
+ Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift);
+ break;
+ }
+ case 18 ... 20: // Sparkle/Twinkle
+ Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
+ break;
+ case 21:
+ Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90);
+ break;
+ case 22: // Rain Animation
+ Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40);
+ break;
+
+ // Comets
+ case 23:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1);
+ break;
+ case 24 ... 31:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 24], 40, 85, RANDOM_DECAY, 6);
+ break;
+ case 32 ... 47:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 32], 40, 85, RANDOM_DECAY, 3);
+ break;
+ case 48 ... 63:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 48], 40, 85, RANDOM_DECAY, 2);
+ break;
+
+ // Snakes
+ case 64:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 50);
+ break;
+ case 65 ... 72:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 65], 60, 6);
+ break;
+ case 73 ... 88:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 73], 60, 3);
+ break;
+ case 89 ... 104:
+ Anim_Snakes(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 89], 60, 2);
+ break;
+
+ // Sectors
+ case 105:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, NO_GAPS, 1, 40, 90);
+ break;
+ case 106 ... 121:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 106], NO_GAPS, 3, 40, 90);
+ break;
+ case 122 ... 137:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 122], NO_GAPS, 2, 40, 90);
+ break;
+
+ // Dashes
+ case 138:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, WITH_GAPS, 1, 40, 90);
+ break;
+ case 139 ... 146:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, single_colorPacks[AnimEvent.AnimationIndex - 139], WITH_GAPS, 6, 40, 90);
+ break;
+ case 147 ... 162:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, double_colorPacks[AnimEvent.AnimationIndex - 147], WITH_GAPS, 3, 40, 90);
+ break;
+ case 163 ... 178:
+ Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[AnimEvent.AnimationIndex - 163], WITH_GAPS, 2, 40, 90);
+ break;
+ default:
+ ESP_LOGW(tag, "Loop default");
+ break;
+ }
+
+ AnimationLooping = false;
+ prevAnimEvent = AnimEvent;
+ ESP_LOGD(tag, "Going to Queue to Wait");
+ }
}
}
}
+void SetAndSaveUserSettings(USER_SETTINGS &userSettings) {
+
+ // Apply the settings to sys_settings
+ sys_settings.limitedMode = userSettings.limitedMode;
+
+ sys_settings.ledStripSettings[0]->chip = userSettings.ledChipset1;
+ sys_settings.ledStripSettings[0]->shift = userSettings.ledShift1;
+ sys_settings.ledStripSettings[0]->bright = userSettings.ledBrightness1;
+ RGB_Lights_Set_Brightness(userSettings.ledBrightness1);
+
+ sys_settings.ledStripSettings[0]->chip = userSettings.ledChipset2;
+ sys_settings.ledStripSettings[0]->shift = userSettings.ledShift2;
+ sys_settings.ledStripSettings[0]->bright = userSettings.ledBrightness2;
+
+ sys_settings.tSensorSettings.setpoint1 = userSettings.fanLowerTemp;
+ sys_settings.tSensorSettings.setpoint2 = userSettings.fanUpperTemp;
+ sys_settings.rampLightSettings[0].min = userSettings.frontLightMin;
+ sys_settings.rampLightSettings[0].max = userSettings.frontLightMax;
+ sys_settings.rampLightSettings[1].min = userSettings.rearLightMin;
+ sys_settings.rampLightSettings[1].max = userSettings.rearLightMax;
+
+ if (!LittleFS.begin()) {
+ ESP_LOGE(tag, "Failed to mount file system");
+ return;
+ }
+
+ // First, open the existing file for reading so we preserve unrelated keys
+ File file = LittleFS.open(booth_file_path, "r");
+ JsonDocument jsonDocument;
+
+ if (file) {
+ // Try to deserialize existing content; if it fails, start with an empty document
+ DeserializationError err = deserializeJson(jsonDocument, file);
+ if (err) {
+ ESP_LOGW(tag, "Failed to parse existing JSON (%s), starting fresh", err.c_str());
+ jsonDocument.clear();
+ }
+ file.close();
+ } else {
+ ESP_LOGW(tag, "Settings file not found, a new one will be created");
+ }
+
+ // Update only the keys we care about
+ jsonDocument["limied-mode"] = userSettings.limitedMode;
+
+ jsonDocument["ramp-lights"][0]["min"] = userSettings.frontLightMin;
+ jsonDocument["ramp-lights"][0]["max"] = userSettings.frontLightMax;
+ jsonDocument["ramp-lights"][1]["min"] = userSettings.rearLightMin;
+ jsonDocument["ramp-lights"][1]["max"] = userSettings.rearLightMax;
+
+ String chipName = "Unknown";
+ if (userSettings.ledChipset1 == CHIP_WS2812B) chipName = "WS2812B";
+ else if (userSettings.ledChipset1 == CHIP_SK6812) chipName = "SK6812";
+ else if (userSettings.ledChipset1 == CHIP_WS2811) chipName = "WS2811";
+ else if (userSettings.ledChipset1 == CHIP_WS2815) chipName = "WS2815";
+ jsonDocument["strips"][0]["chip"] = chipName;
+ userSettings.rgbOrder1[3] = '\0'; // Ensure null-terminated string
+ jsonDocument["strips"][0]["rgb-order"] = String(userSettings.rgbOrder1);
+ jsonDocument["strips"][0]["shift"] = userSettings.ledShift1;
+ jsonDocument["strips"][0]["bright"] = userSettings.ledBrightness1;
+ jsonDocument["strips"][0]["count"] = userSettings.ledCount1;
+
+
+ if (userSettings.ledChipset2 == CHIP_WS2812B) chipName = "WS2812B";
+ else if (userSettings.ledChipset2 == CHIP_SK6812) chipName = "SK6812";
+ else if (userSettings.ledChipset2 == CHIP_WS2811) chipName = "WS2811";
+ else if (userSettings.ledChipset2 == CHIP_WS2815) chipName = "WS2815";
+ jsonDocument["strips"][1]["chip"] = chipName;
+ userSettings.rgbOrder2[3] = '\0'; // Ensure null-terminated string
+ jsonDocument["strips"][1]["rgb-order"] = String(userSettings.rgbOrder2);
+ jsonDocument["strips"][1]["shift"] = userSettings.ledShift2;
+ jsonDocument["strips"][1]["bright"] = userSettings.ledBrightness2;
+ jsonDocument["strips"][1]["count"] = userSettings.ledCount2;
+
+ jsonDocument["t-sensor"]["sp1"] = userSettings.fanLowerTemp;
+ jsonDocument["t-sensor"]["sp2"] = userSettings.fanUpperTemp;
+
+ //jsonDocument["stripsr"][0]["chip"] = settings.ledChipset;
+
+ // Now open the file for writing (overwrite) and serialize back
+ File out = LittleFS.open(booth_file_path, "w");
+ if (!out) {
+ ESP_LOGE(tag, "Failed to open file for writing");
+ return;
+ }
+
+ if (serializeJson(jsonDocument, out) == 0) {
+ ESP_LOGE(tag, "Failed to write to file");
+ } else {
+ ESP_LOGI(tag, "User settings saved successfully");
+ }
+
+ out.close();
+
+
+ // Modify system.json with booth type
+ String sysProf = String(sys_settings.profile);
+ String userProf = String(userSettings.profile);
+ sysProf.toLowerCase();
+ userProf.toLowerCase();
+ ESP_LOGI(tag, "System Profile: '%s', User Profile: '%s'", sysProf.c_str(), userProf.c_str());
+
+ if (userProf.length() > 0 && sysProf.indexOf(userProf) >= 0) {
+ // userSettings.profile is contained in sys_settings.profile (case-insensitive)
+ }
+
+}
+
+
void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack) {
for (uint8_t i = 0; i < 16; i++) {
if (i < 3) palette[i] = CRGB::Black;
diff --git a/src/Animations.cpp b/src/Animations.cpp
index 430ac81..b59d15e 100644
--- a/src/Animations.cpp
+++ b/src/Animations.cpp
@@ -27,6 +27,7 @@ void Animation_Init(void){
}
// Animation Loop Template
+/*
void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function callback) {
if (!callback) {
ESP_LOGE("Animation_Loop", "Invalid callback function");
@@ -65,7 +66,62 @@ void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function callback) {
+ if (!callback) {
+ ESP_LOGE("Animation_Loop", "Invalid callback function");
+ return;
+ }
+ loop_active_flag = true;
+ speed = constrain(speed, 0, MaxSpeed);
+
+ // compute desired loop delay in milliseconds and clamp
+ int loopDelayMs = max(MaxSpeed - speed, MinLoopDelay);
+ const int MAX_LOOP_DELAY_MS = 60 * 1000; // 60s safety cap
+ if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
+
+ // Convert ms -> RTOS ticks
+ TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
+
+ ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications
+
+ TickType_t xLastWakeTime;
+ TickType_t elapsedTicks;
+ TickType_t delayTicks;
+ int retVal = 0;
+ while(!retVal && loop_active_flag) {
+ xLastWakeTime = xTaskGetTickCount();
+
+ try {
+ retVal = callback(); // Call animation function
+ } catch (const std::exception& e) {
+ ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what());
+ break;
+ } catch (...) {
+ ESP_LOGE("Animation_Loop", "Callback unknown exception");
+ break;
+ }
+
+ if(!loop_active_flag) break;
+
+ // compute elapsed ticks since callback start and remaining ticks
+ elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
+ delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
+
+ if (delayTicks == 0) {
+ // yield a tick to avoid busy-spin
+ vTaskDelay(1);
+ if (ulTaskNotifyTake(pdTRUE, 0)) break; // notified -> exit
+ } else {
+ if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; } // notified -> exit
+ }
+ }
+
+ loop_active_flag = false;
+}
+
+/*
void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function callback) {
if (!callback) {
ESP_LOGE("Animation_Loop", "Invalid callback function");
@@ -105,8 +161,74 @@ void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function callback) {
+ if (!callback) {
+ ESP_LOGE("Animation_Loop", "Invalid callback function");
+ return;
+ }
+
+ loop_active_flag = true;
+
+ // clear any pending notification
+ ulTaskNotifyTake(pdTRUE, 0);
+
+ TickType_t xLastWakeTime;
+ TickType_t elapsedTicks;
+ TickType_t delayTicks;
+
+ // Define sensible upper bound for delay (ms) to avoid indefinite blocking due to bad callback
+ const int MAX_LOOP_DELAY_MS = 60 * 1000; // 60 seconds
+
+ while (loop_active_flag) {
+ xLastWakeTime = xTaskGetTickCount();
+
+ int loopDelayMs = 0;
+ try {
+ loopDelayMs = callback(); // callback returns desired delay in milliseconds
+ } catch (const std::exception& e) {
+ ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what());
+ break;
+ } catch (...) {
+ ESP_LOGE("Animation_Loop", "Callback unknown exception");
+ break;
+ }
+
+ if (!loop_active_flag) break;
+
+ // sanitize returned ms value and enforce minimum/maximum
+ if (loopDelayMs < (int)MinLoopDelay) loopDelayMs = MinLoopDelay;
+ if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
+
+ // convert ms -> RTOS ticks (safe conversion)
+ TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
+
+ // compute elapsed ticks since callback start
+ elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
+
+ // compute remaining delay (in ticks)
+ delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
+
+ if (delayTicks == 0) {
+ // Ensure we yield at least one tick to avoid busy-looping
+ vTaskDelay(1);
+ // Check if notified during the yield
+ if (ulTaskNotifyTake(pdTRUE, 0)) break;
+ } else {
+ // Wait for either a notification (termination) or the timeout
+ if (ulTaskNotifyTake(pdTRUE, delayTicks)) {
+ // notified => exit loop
+ break;
+ }
+ }
+ }
+
+ loop_active_flag = false;
+}
// Animation Loop Template
+/*
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function callback) {
loop_active_flag = true;
speed = constrain(speed, 0, MaxSpeed);
@@ -159,8 +281,72 @@ void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickTyp
loop_active_flag = false;
}
+*/
+
+void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function callback) {
+ loop_active_flag = true;
+ speed = constrain(speed, 0, MaxSpeed);
+
+ // treat durationMs as milliseconds (convert to ticks)
+ const TickType_t durationTicks = pdMS_TO_TICKS(durationMs);
+
+ ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications
+ TickType_t startTicks = xTaskGetTickCount();
+ TickType_t xLastWakeTime;
+
+ const int MAX_LOOP_DELAY_MS = 60 * 1000; // safety cap
+ while (loop_active_flag) {
+ xLastWakeTime = xTaskGetTickCount();
+
+ // Call animation function
+ int speedIncrease = 0;
+ try {
+ speedIncrease = callback();
+ } catch (const std::exception& e) {
+ ESP_LOGE("Animation_Loop_Duration", "Callback exception: %s", e.what());
+ break;
+ } catch (...) {
+ ESP_LOGE("Animation_Loop_Duration", "Callback unknown exception");
+ break;
+ }
+
+ if(!loop_active_flag) break;
+
+ // Calculate combined speed with bounds protection
+ int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed);
+
+ // Calculate delay with minimum protection (ms)
+ int loopDelayMs = MaxSpeed - totalSpeed;
+ loopDelayMs = max(loopDelayMs, MinLoopDelay);
+ if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
+ TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
+
+ // Calculate remaining time with overflow protection
+ TickType_t elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
+ TickType_t delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
+
+ // Delay and Check for termination request
+ if (delayTicks == 0) {
+ vTaskDelay(1);
+ if (ulTaskNotifyTake(pdTRUE, 0)) break;
+ } else {
+ if (ulTaskNotifyTake(pdTRUE, delayTicks)) break;
+ }
+
+ // Check if duration reached and wait for loop_active_flag
+ if (durationTicks > 0) {
+ TickType_t totalElapsed = xTaskGetTickCount() - startTicks;
+ if (totalElapsed >= durationTicks) {
+ break; // Auto-terminate when duration reached
+ }
+ }
+ }
+
+ loop_active_flag = false;
+}
// Animation Loop Template
+/*
void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function callback) {
loop_active_flag = true;
uint32_t loop_cycle_count = 0;
@@ -211,6 +397,66 @@ void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t
loop_active_flag = false;
}
+*/
+
+void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function callback) {
+ loop_active_flag = true;
+ uint32_t loop_cycle_count = 0;
+ speed = constrain(speed, 0, MaxSpeed);
+
+ ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications
+ TickType_t xLastWakeTime;
+ TickType_t elapsedTicks;
+ TickType_t delayTicks;
+
+ const int MAX_LOOP_DELAY_MS = 60 * 1000; // safety cap
+ for(;;) {
+ xLastWakeTime = xTaskGetTickCount();
+
+ int speedIncrease = 0;
+ try {
+ speedIncrease = callback(); // Call animation function
+ } catch (const std::exception& e) {
+ ESP_LOGE("Animation_Loop_Cycles", "Callback exception: %s", e.what());
+ break;
+ } catch (...) {
+ ESP_LOGE("Animation_Loop_Cycles", "Callback unknown exception");
+ break;
+ }
+
+ if(!loop_active_flag) break;
+
+ // Calculate combined speed with bounds protection
+ int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed);
+
+ // Calculate delay with minimum protection (ms)
+ int loopDelayMs = MaxSpeed - totalSpeed;
+ loopDelayMs = max(loopDelayMs, MinLoopDelay);
+ if (loopDelayMs > MAX_LOOP_DELAY_MS) loopDelayMs = MAX_LOOP_DELAY_MS;
+ TickType_t loopDelayTicks = pdMS_TO_TICKS(loopDelayMs);
+
+ // Calculate remaining time with overflow protection
+ elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
+ delayTicks = (elapsedTicks < loopDelayTicks) ? (loopDelayTicks - elapsedTicks) : 0;
+
+ // Delay and Check for termination request
+ if (delayTicks == 0) {
+ vTaskDelay(1);
+ if (ulTaskNotifyTake(pdTRUE, 0)) break;
+ } else {
+ if (ulTaskNotifyTake(pdTRUE, delayTicks)) break;
+ }
+
+ // Check if cycles reached and exit
+ if (loop_cycle_count >= loop_cycles) {
+ break;
+ }
+
+ loop_cycle_count++;
+ }
+
+ loop_active_flag = false;
+}
/********************************************************************************
@@ -399,76 +645,6 @@ void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const C
//#define COMET_FADE_FACTOR COMET_FADE_FACTOR2
#define MAX_COMETS 8 // Maximum number of comets supported
/*
-void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) {
- // Validate inputs
- int numComets = colorPack.size;
- if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0) return;
-
- // Calculate comet size
- int cometSize = (size / (numComets * cometMultiplier)) * COMET_SIZE_FACTOR;
- if (cometSize < 1) cometSize = 1;
-
- // Set fade factor
- uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1;
-
- // Initialize comet positions to be equally spaced
- std::unique_ptr cometPositions(new (std::nothrow) int[numComets * cometMultiplier]);
- if (!cometPositions) return;
- int totalComets = numComets * cometMultiplier;
- int spacing = size / totalComets;
- for (int i = 0; i < totalComets; i++) {
- cometPositions[i] = i * spacing;
- }
-
- // Animation loop
- bool direction = true; // true = forward, false = backward
- int loopCounter = 0;
- try {
- const int loopTick = 30; // ms per animation tick
- Animation_Loop(activeFlag, loopTick, [&]() -> int {
- // Fade all LEDs
- for (int i = 0; i < size; i++) {
- if(!randomDecay) {
- leds[i].fadeToBlackBy(fadeFactor);
- } else if(random(10) > 5){
- leds[i].fadeToBlackBy(fadeFactor);
- }
- }
-
- // Move and draw comets
- for (int i = 0; i < totalComets; i++) {
- if (direction) {
- cometPositions[i] = (cometPositions[i] + 1) % size;
- } else {
- cometPositions[i] = (cometPositions[i] - 1 + size) % size;
- }
-
- // Draw comet with solid color
- CRGB color = colorPack.col[i % colorPack.size];
- for (int j = 0; j < cometSize; j++) {
- int pos = (cometPositions[i] - j + size) % size;
- leds[pos] += color;
- }
- }
-
- loopCounter++;
- if(loopCounter >= (size * CYCLES_PER_DIRECTION)){
- direction = !direction;
- loopCounter = 0;
- }
-
- FastLED.show();
- return 0;
- });
- } catch (const std::exception& e) {
- ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what());
- } catch (...) {
- ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
- }
- // No need to delete cometPositions as it is managed by std::unique_ptr
-}
-*/
-
void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) {
// Validate inputs
int numComets = colorPack.size;
@@ -479,8 +655,8 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
return;
}
- // Calculate comet size
- int cometSize = (size / totalComets) * COMET_SIZE_FACTOR;
+ // Calculate comet size (use float math and round to avoid integer-division truncation)
+ int cometSize = (int)(((float)size * (float)COMET_SIZE_FACTOR) / (float)totalComets + 0.5f);
if (cometSize < 1) cometSize = 1;
// Set fade factor
@@ -500,9 +676,14 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
int pos;
CRGB color;
int speed = minSpeed;
- int loopDuration = (size * CYCLES_PER_DIRECTION);
+ int loopDuration = size * CYCLES_PER_DIRECTION;
+ // Defensive: ensure loopDuration isn't zero to avoid divide-by-zero later
+ if (loopDuration < 1) loopDuration = 1;
int thirdLoop = loopDuration / 3;
- int twoThirdLoop = (2 * loopDuration) / 3;
+ // Defensive: ensure thirdLoop is at least 1 for progress calculations
+ if (thirdLoop < 1) thirdLoop = 1;
+ // Keep twoThirdLoop consistent with thirdLoop to avoid rounding surprises
+ int twoThirdLoop = 2 * thirdLoop;
try {
Animation_Loop_Variable(activeFlag, [&]() -> int {
// Fade all LEDs
@@ -558,7 +739,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
FastLED.show();
- return max(MaxSpeed - speed, MinLoopDelay);
+ // Compute delay to return to Animation_Loop_Variable.
+ // Ensure we never return a value less than MinLoopDelay.
+ int computed = MaxSpeed - speed;
+ if (computed < MinLoopDelay) computed = MinLoopDelay;
+ return computed;
});
fill_solid(leds, size, CRGB::Black);
@@ -569,6 +754,131 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
}
}
+*/
+void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) {
+ // Validate inputs
+ int numComets = colorPack.size;
+ int totalComets = numComets * cometMultiplier;
+
+ if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0 || totalComets > MAX_COMETS) {
+ ESP_LOGE("Anim_Comets", "Invalid input parameters or too many comets (max: %d, requested: %d)", MAX_COMETS, totalComets);
+ return;
+ }
+
+ // Sanitize speed inputs to known bounds
+ minSpeed = constrain(minSpeed, 0, MaxSpeed);
+ maxSpeed = constrain(maxSpeed, 0, MaxSpeed);
+ if (minSpeed > maxSpeed) { int t=minSpeed; minSpeed=maxSpeed; maxSpeed=t; }
+
+ // Calculate comet size (use float math and round to avoid integer-division truncation)
+ int cometSize = (int)(((float)size * (float)COMET_SIZE_FACTOR) / (float)totalComets + 0.5f);
+ if (cometSize < 1) cometSize = 1;
+
+ // Set fade factor
+ uint8_t fadeFactor = map(totalComets, 1, MAX_COMETS, COMET_FADE_FACTOR1, COMET_FADE_FACTOR2);
+ fadeFactor = constrain(fadeFactor, COMET_FADE_FACTOR1, COMET_FADE_FACTOR2);
+
+ // Initialize comet positions with fixed array, evenly distributed
+ int cometPositions[MAX_COMETS] = {0};
+ for (int i = 0; i < totalComets; i++) {
+ // Even distribution even when size not divisible by totalComets
+ cometPositions[i] = (i * size) / totalComets;
+ }
+
+ // Animation loop
+ bool direction = true; // true = forward, false = backward
+ int loopCounter = 0;
+ int pos;
+ CRGB color;
+ int speed = minSpeed;
+
+ // Cache size/limits locally for speed
+ const int localSize = size;
+ const int localTotalComets = totalComets;
+ int loopDuration = localSize * CYCLES_PER_DIRECTION;
+ // Defensive: ensure loopDuration isn't zero to avoid divide-by-zero later
+ if (loopDuration < 1) loopDuration = 1;
+ int thirdLoop = loopDuration / 3;
+ if (thirdLoop < 1) thirdLoop = 1;
+ int twoThirdLoop = 2 * thirdLoop;
+
+ try {
+ Animation_Loop_Variable(activeFlag, [&]() -> int {
+ // Fade all LEDs (keep this O(N) operation but keep it tight)
+ for (int i = 0; i < localSize; i++) {
+ if (!randomDecay) {
+ leds[i].fadeToBlackBy(fadeFactor);
+ } else if (getRandomValue(10) > 5) {
+ leds[i].fadeToBlackBy(fadeFactor);
+ }
+ }
+
+ // Move and draw comets
+ for (int i = 0; i < localTotalComets; i++) {
+ if (direction) {
+ cometPositions[i] = (cometPositions[i] + 1) % localSize;
+ } else {
+ cometPositions[i] = (cometPositions[i] - 1 + localSize) % localSize;
+ }
+
+ // Draw comet with solid color (safe modulo)
+ color = colorPack.col[i % colorPack.size];
+ for (int j = 0; j < cometSize; j++) {
+ // Tail follows the direction of movement
+ pos = direction ? (cometPositions[i] - j) : (cometPositions[i] + j);
+ pos = (pos % localSize + localSize) % localSize; // safe modulus
+ leds[pos] += color;
+ }
+ }
+
+ // Speed ramping: 1/3 aggressive ramp up, 1/3 constant, 1/3 aggressive ramp down
+ if (loopCounter < thirdLoop) {
+ // First third: ease-out quartic implemented without pow()
+ float progress = (float)loopCounter / (float)thirdLoop; // 0.0 to 1.0
+ float oneMinus = 1.0f - progress;
+ float eased = 1.0f - (oneMinus * oneMinus * oneMinus * oneMinus); // 1 - (1-t)^4
+ speed = minSpeed + (int)((maxSpeed - minSpeed) * eased);
+ }
+ else if (loopCounter < twoThirdLoop) {
+ // Middle third: constant at maxSpeed
+ speed = maxSpeed;
+ }
+ else {
+ // Last third: ease-in quartic implemented without pow()
+ float progress = (float)(loopCounter - twoThirdLoop) / (float)thirdLoop; // 0.0 to 1.0
+ if (progress < 0.0f) progress = 0.0f; if (progress > 1.0f) progress = 1.0f;
+ float eased = (progress * progress * progress * progress); // t^4
+ speed = maxSpeed - (int)((maxSpeed - minSpeed) * eased);
+ }
+
+ if (++loopCounter >= loopDuration) {
+ direction = !direction;
+ loopCounter = 0;
+ speed = minSpeed;
+ }
+
+ FastLED.show();
+
+ // Compute delay to return to Animation_Loop_Variable.
+ // Ensure we never return a value less than MinLoopDelay.
+ int computed = MaxSpeed - speed;
+ if (computed < MinLoopDelay) computed = MinLoopDelay;
+ return computed;
+ });
+
+ fill_solid(leds, size, CRGB::Black);
+
+ } catch (const std::exception& e) {
+ ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what());
+ } catch (...) {
+ ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
+ }
+
+ // NOTE: For production/stability consider:
+ // - Replacing volatile activeFlag with std::atomic or task-notify mechanism.
+ // - If FastLED.show() blocks too long for very long strips consider chunked updates or an alternate driver.
+ // - If pow() usage was widespread, replace with fixed-point or inline multiplies as done above.
+}
void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0)
@@ -698,6 +1008,8 @@ void Anim_TimedFill_Flash(bool volatile& activeFlag, CRGB* leds, int size, PWM_O
if (flashElapsed >= flashTimeout) {
// Flash timeout expired, set PWM to minimum
pwmOut->setOutput(pwmMin);
+ // exit the loop
+ activeFlag = false;
ESP_LOGI("Anim_TimedFill_Flash", "Flash timeout expired, PWM set to minimum");
}
// Continue in infinite loop regardless of timeout status
@@ -754,9 +1066,8 @@ void Anim_SolidWhite(bool volatile& activeFlag, CRGB* leds, PWM_Output* pwmOut,
// No animation, just maintain the solid white state
return 0;
});
- if(pwmOut){
- pwmOut->setOutput(0); // Turn off PWM output
- }
+
+ if(pwmOut){ pwmOut->setOutput(0); }// Turn off PWM output
FastLED.setBrightness(origBright); // Restore original brightness
}
diff --git a/src/AppUpgrade.cpp b/src/AppUpgrade.cpp
index c6b6f28..ba10eac 100644
--- a/src/AppUpgrade.cpp
+++ b/src/AppUpgrade.cpp
@@ -32,6 +32,7 @@ AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, con
if (buffer_size < 1024) buffer_size = 1024; // Absolute minimum is 1KB
downloadBuffer.reset(new uint8_t[buffer_size]);
+ downloadBufferSize = buffer_size;
baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
// Ensure baseUrl ends with a single '/'
@@ -81,6 +82,7 @@ AppUpdater::ManifestCheckResult AppUpdater::checkManifest() {
http.end();
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
}
+
if(payload.isEmpty()){
ESP_LOGE(TAG, "Failed to fetch manifest after retries");
return ManifestCheckResult::ERROR_FETCH_FAILED;
@@ -159,6 +161,7 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const
} else {
ESP_LOGI(TAG, "Local file does not exist: %s", localPath);
}
+
if(skip){
ESP_LOGI(TAG, "File already up to date: %s", localPath);
updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
@@ -179,9 +182,12 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const
http.end();
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
}
+
if (httpCode != HTTP_CODE_OK) {
ESP_LOGE(TAG, "Download failed for %s: HTTP code %d", localPath, httpCode);
- updateProgress(UpdateStatus::ERROR, 0, String(String("Download failed: ") + localPath).c_str());
+ char buf[128];
+ snprintf(buf, sizeof(buf), "Download failed: %s", localPath);
+ updateProgress(UpdateStatus::ERROR, 0, buf);
http.end();
return false;
}
@@ -215,33 +221,36 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const
bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* remotePath, const char* expectedMd5)
{
const int MAX_RETRIES = 2; // Maximum number of retries for MD5 failure
-
+
for (int retry = 0; retry <= MAX_RETRIES; retry++) {
+ HTTPClient retryHttp; // kept alive for the duration of this iteration if used
+ WiFiClient* activeStream = stream;
+ size_t activeContentLength = contentLength;
+
if (retry > 0) {
ESP_LOGW(TAG, "Retrying download of %s (attempt %d/%d)", localPath, retry, MAX_RETRIES);
// Need to re-fetch the file for retry - use the REMOTE path, not local path
String url = buildUrl(remotePath);
- HTTPClient http;
- http.begin(url);
- int httpCode = http.GET();
+ retryHttp.begin(url);
+ int httpCode = retryHttp.GET();
if (httpCode != HTTP_CODE_OK) {
ESP_LOGE(TAG, "Retry download failed: %d", httpCode);
- http.end();
+ retryHttp.end();
continue; // Try next retry if available
}
- stream = http.getStreamPtr();
- contentLength = http.getSize();
+ activeStream = retryHttp.getStreamPtr();
+ activeContentLength = retryHttp.getSize();
}
-
+
MD5Builder md5;
md5.begin();
size_t totalRead = 0;
-
+
// Create temporary filename in the same directory as the target file
String targetDir = String(localPath);
int lastSlash = targetDir.lastIndexOf('/');
String tempPath;
-
+
if (lastSlash >= 0) {
// Extract directory and filename
String directory = targetDir.substring(0, lastSlash + 1);
@@ -251,62 +260,84 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
// File is in root directory
tempPath = "/temp_" + String(localPath) + ".download";
}
-
+
// Clean up any existing temp file first
if (fileSystem.exists(tempPath.c_str())) {
ESP_LOGW(TAG, "Removing existing temp file: %s", tempPath.c_str());
fileSystem.remove(tempPath.c_str());
}
-
+
ESP_LOGI(TAG, "Using temp file path: %s for target: %s", tempPath.c_str(), localPath);
-
+ ESP_LOGI(TAG, "verifyAndSaveFile: downloadBufferSize=%u", (unsigned)downloadBufferSize);
+
+ // Check available space if content length is known
+ if (activeContentLength > 0) {
+ size_t freeBytes = LittleFS.totalBytes() - LittleFS.usedBytes();
+ // Leave a small headroom (5%) to avoid filling filesystem completely
+ if (activeContentLength > freeBytes * 95 / 100) {
+ ESP_LOGE(TAG, "Not enough LittleFS space for %s (%u bytes needed, %u bytes free)",
+ localPath, (unsigned)activeContentLength, (unsigned)freeBytes);
+ if (retry > 0) retryHttp.end();
+ continue;
+ }
+ }
+
// Open temporary file for writing (LittleFS will create directories automatically)
File file = fileSystem.open(tempPath.c_str(), FILE_WRITE, true); // true = create if not exists
if (!file) {
ESP_LOGE(TAG, "Failed to open temporary file for writing: %s", tempPath.c_str());
-
+
// Try to diagnose the issue
- ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes",
- LittleFS.usedBytes(), LittleFS.totalBytes());
-
+ ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes",
+ (unsigned)LittleFS.usedBytes(), (unsigned)LittleFS.totalBytes());
+
// Check if we're out of space
if (LittleFS.usedBytes() >= LittleFS.totalBytes() * 0.95) {
ESP_LOGE(TAG, "LittleFS nearly full - may not have space for temp file");
}
-
+
+ if (retry > 0) retryHttp.end();
return false;
}
-
- //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
-
- if (contentLength > 0) {
+
+ // Defensive: ensure download buffer exists
+ if (!downloadBuffer || downloadBufferSize == 0) {
+ ESP_LOGE(TAG, "No download buffer available");
+ file.close();
+ fileSystem.remove(tempPath.c_str());
+ if (retry > 0) retryHttp.end();
+ return false;
+ }
+
+ if (activeContentLength > 0) {
// Single pass with known content length
- while (totalRead < contentLength) {
- if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
- size_t available = stream->available();
+ while (totalRead < activeContentLength) {
+ if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; }
+ size_t available = activeStream->available();
if (available) {
- size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE)));
-
+ size_t readLen = activeStream->readBytes(downloadBuffer.get(), std::min(available, downloadBufferSize));
+
// Write to temp file and update MD5
if (file.write(downloadBuffer.get(), readLen) != readLen) {
ESP_LOGE(TAG, "Failed to write to temporary file");
-
+
file.close();
fileSystem.remove(tempPath.c_str());
+ if (retry > 0) retryHttp.end();
return false;
}
-
+
md5.add(downloadBuffer.get(), readLen);
totalRead += readLen;
- updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / contentLength , localPath);
+ updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / activeContentLength , localPath);
}
yield();
}
} else {
// Unknown content length: read until stream ends
for (;;) {
- if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
- size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
+ if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; }
+ size_t readLen = activeStream->readBytes(downloadBuffer.get(), downloadBufferSize);
if (readLen == 0) {
break;
}
@@ -314,6 +345,7 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
ESP_LOGE(TAG, "Failed to write to temporary file");
file.close();
fileSystem.remove(tempPath.c_str());
+ if (retry > 0) retryHttp.end();
return false;
}
md5.add(downloadBuffer.get(), readLen);
@@ -326,44 +358,45 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
yield();
}
}
-
+
file.close();
md5.calculate();
String calculatedMd5 = md5.toString();
-
+
// Verify MD5 hash
updateProgress(UpdateStatus::VERIFYING, 90, localPath);
-
- ESP_LOGI(TAG, "MD5 verification for %s: Expected='%s', Calculated='%s'",
+
+ ESP_LOGI(TAG, "MD5 verification for %s: Expected='%s', Calculated='%s'",
localPath, expectedMd5, calculatedMd5.c_str());
-
+
// Compare MD5 case-insensitively (in case there are case differences)
String expectedMd5Lower = String(expectedMd5);
expectedMd5Lower.toLowerCase();
String calculatedMd5Lower = calculatedMd5;
calculatedMd5Lower.toLowerCase();
-
+
if (!calculatedMd5Lower.equals(expectedMd5Lower)) {
- ESP_LOGE(TAG, "MD5 mismatch for %s (attempt %d/%d). Expected: %s, Got: %s",
+ ESP_LOGE(TAG, "MD5 mismatch for %s (attempt %d/%d). Expected: %s, Got: %s",
localPath, retry+1, MAX_RETRIES+1, expectedMd5, calculatedMd5.c_str());
- ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars",
- strlen(expectedMd5), calculatedMd5.length());
+ ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars",
+ (int)strlen(expectedMd5), (int)calculatedMd5.length());
fileSystem.remove(tempPath.c_str());
-
+
if (retry < MAX_RETRIES) {
// Will retry in next loop iteration
+ if (retry > 0) retryHttp.end();
continue;
}
-
+
// Special case for certain file types - allow them to be used even with MD5 mismatch
// This is a fallback option for non-critical files like HTML pages
bool isNonCriticalFile = false;
- if (String(localPath).endsWith(".html") ||
- String(localPath).endsWith(".css") ||
+ if (String(localPath).endsWith(".html") ||
+ String(localPath).endsWith(".css") ||
String(localPath).endsWith(".js")) {
isNonCriticalFile = true;
}
-
+
if (isNonCriticalFile) {
ESP_LOGW(TAG, "Using file %s despite MD5 mismatch (non-critical file)", localPath);
// We'll still keep this file but report it as a verification failure
@@ -383,26 +416,29 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
}
}
}
-
+
// Rename the temp file to the final location
if (fileSystem.exists(localPath)) {
fileSystem.remove(localPath);
}
if (!fileSystem.rename(tempPath.c_str(), localPath)) {
- ESP_LOGE(TAG, "Failed to rename temporary file for non-critical use: %s -> %s",
+ ESP_LOGE(TAG, "Failed to rename temporary file for non-critical use: %s -> %s",
tempPath.c_str(), localPath);
fileSystem.remove(tempPath.c_str());
+ if (retry > 0) retryHttp.end();
return false;
}
+ if (retry > 0) retryHttp.end();
// Return false to indicate verification failure, but the file will still be used
return false;
}
-
+
+ if (retry > 0) retryHttp.end();
return false;
}
-
+
updateProgress(UpdateStatus::VERIFYING, 95, localPath);
-
+
// Ensure target directory exists before rename
String dirPath = String(localPath);
int targetLastSlash = dirPath.lastIndexOf('/');
@@ -419,7 +455,7 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
}
}
}
-
+
// Replace original file with verified temp file
if (fileSystem.exists(localPath)) {
fileSystem.remove(localPath);
@@ -427,13 +463,15 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
if (!fileSystem.rename(tempPath.c_str(), localPath)) {
ESP_LOGE(TAG, "Failed to rename temporary file: %s -> %s", tempPath.c_str(), localPath);
fileSystem.remove(tempPath.c_str());
+ if (retry > 0) retryHttp.end();
return false;
}
-
+
updateProgress(UpdateStatus::VERIFYING, 100, localPath);
+ if (retry > 0) retryHttp.end();
return true;
}
-
+
return false; // All retries failed
}
@@ -450,7 +488,7 @@ String AppUpdater::getLocalMD5(const char* filePath){
size_t totalRead = 0;
size_t readLen = 0;
while (totalRead < fileSize) {
- readLen = file.readBytes(reinterpret_cast(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE)));
+ readLen = file.readBytes(reinterpret_cast(downloadBuffer.get()), std::min(fileSize - totalRead, downloadBufferSize));
md5Builder.add(downloadBuffer.get(), readLen);
totalRead += readLen;
}
@@ -569,6 +607,7 @@ bool AppUpdater::updateApp() {
if (httpCode != HTTP_CODE_OK) {
ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
+ http.end();
return false;
}
@@ -662,12 +701,12 @@ bool AppUpdater::updateApp() {
while (remaining > 0 && failedReads < MAX_FAILED_READS) {
// Check for cancellation
- if(g_UpdateCancelFlag) {
- ESP_LOGE(TAG, "Update cancelled by user");
- Update.abort();
- http.end();
- return false;
- }
+ if(g_UpdateCancelFlag) {
+ ESP_LOGE(TAG, "Update cancelled by user");
+ Update.abort();
+ http.end();
+ return false;
+ }
// Check WiFi status every 5 seconds
if (millis() - lastWatchdogKick > 5000) {
@@ -735,8 +774,9 @@ bool AppUpdater::updateApp() {
// Send periodic progress updates to keep client informed
if (millis() - lastWatchdogKick > 5000) {
int percent = (totalReceived * 100) / firmwareSize;
- updateProgress(UpdateStatus::DOWNLOADING, percent,
- String("Firmware: " + String(percent) + "% - waiting for data...").c_str());
+ char buf[64];
+ snprintf(buf, sizeof(buf), "Firmware: %d%% - waiting for data...", percent);
+ updateProgress(UpdateStatus::DOWNLOADING, percent, buf);
}
delay(100); // Short delay to prevent CPU hogging
@@ -782,8 +822,9 @@ bool AppUpdater::updateApp() {
lastProgressTime = millis();
// Just update with received byte count since we don't know total
- updateProgress(UpdateStatus::DOWNLOADING, 0,
- String("Firmware: " + String(totalReceived / 1024) + "KB received").c_str());
+ char buf2[64];
+ snprintf(buf2, sizeof(buf2), "Firmware: %uKB received", (unsigned)(totalReceived / 1024));
+ updateProgress(UpdateStatus::DOWNLOADING, 0, buf2);
} else {
emptyReads++;
delay(100);
@@ -878,115 +919,122 @@ void firmwareUpdateTask(void* parameter) {
esp_task_wdt_init(60, true); // 60 second timeout, panic on timeout
esp_task_wdt_add(NULL); // Add current task to watchdog
- try {
- loadUpdateJson();
-
- esp_task_wdt_reset(); // Reset watchdog timer after JSON loading
-
- // Initialize updater with smart pointer
- std::unique_ptr updater(new AppUpdater(
- LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin"));
- updater->setProgressCallback(updateProgress);
-
- ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
-
- // Check and perform updates
- auto manifestResult = updater->checkManifest();
-
- if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) {
- // Handle different error cases
- std::string errorMsg;
- switch (manifestResult) {
- case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED:
- errorMsg = "Failed to fetch manifest";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE:
- errorMsg = "Manifest file too large";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED:
- errorMsg = "Failed to parse manifest";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION:
- errorMsg = "Manifest missing files section";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION:
- errorMsg = "Manifest missing version section";
- break;
- case AppUpdater::ManifestCheckResult::VERSION_CURRENT:
- errorMsg = "Current version is up to date";
- // This is not actually an error
- ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str());
- updateProgress(AppUpdater::UpdateStatus::MESSAGE, 0, errorMsg.c_str());
- // Don't throw, just exit gracefully
- break;
- default:
- errorMsg = "Unknown manifest check error";
- }
-
- if (manifestResult != AppUpdater::ManifestCheckResult::VERSION_CURRENT) {
- ESP_LOGE(TAG, "Manifest check failed: %s", errorMsg.c_str());
- updateProgress(AppUpdater::UpdateStatus::ERROR, 0, errorMsg.c_str());
- }
- }
-
- if (updater->IsUpdateAvailable()) {
- bool filesUpdated = true;
- bool firmwareUpdated = false; // Initialize to false - only set to true if firmware is actually updated
-
- // Update files based on update mode
- if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
- ESP_LOGI(TAG, "Update mode includes files, updating files...");
- filesUpdated = updater->updateFilesArray();
- if (!filesUpdated) {
- ESP_LOGW(TAG, "Some files failed to update");
- if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY) {
- ESP_LOGE(TAG, "Files-only update failed");
- updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update files");
- // Skip to cleanup since this is files-only mode and it failed
- goto cleanup;
- } else {
- ESP_LOGW(TAG, "File update failed, but continuing with firmware update");
- }
- }
- } else {
- ESP_LOGI(TAG, "Skipping file updates (mode: firmware only)");
- }
-
- // Update firmware based on update mode
- if (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
- ESP_LOGI(TAG, "Update mode includes firmware, updating firmware...");
- firmwareUpdated = updater->updateApp();
- if (!firmwareUpdated) {
- ESP_LOGE(TAG, "Failed to update firmware");
- updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update firmware");
- // Skip to cleanup since firmware update failed
- goto cleanup;
- }
- } else {
- ESP_LOGI(TAG, "Skipping firmware update (mode: files only)");
- }
-
- // Determine if we need to restart
- bool needsRestart = (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) && firmwareUpdated;
-
- if (needsRestart) {
- ESP_LOGI(TAG, "Firmware update successful, restarting...");
- sendUpdateMessage("Restarting... ", true, 100);
- vTaskDelay(2000);
- ESP.restart();
- } else {
- ESP_LOGI(TAG, "Update completed successfully (no restart required)");
- updateProgress(AppUpdater::UpdateStatus::COMPLETE, 100, "Update completed successfully");
- }
- }
-
- } catch (const std::exception& e) {
- ESP_LOGE(TAG, "Update failed with exception: %s", e.what());
- updateProgress(AppUpdater::UpdateStatus::ERROR, 0, e.what());
- } catch (...) {
- ESP_LOGE(TAG, "Update failed with unknown exception");
- updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Unknown error during update");
+ // Load update.json; proceed only if successful
+ if (!loadUpdateJson()) {
+ ESP_LOGE(TAG, "Failed to load update.json, aborting update task");
+ // Clean up watchdog and exit task
+ esp_task_wdt_delete(NULL);
+ Update_Task_Handle = NULL;
+ vTaskDelete(NULL);
+ return;
}
+
+ esp_task_wdt_reset(); // Reset watchdog timer after JSON loading
+
+ // Initialize updater with smart pointer
+ std::unique_ptr updater(new AppUpdater(
+ LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin"));
+ updater->setProgressCallback(updateProgress);
+
+ ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
+
+ // Check and perform updates
+ auto manifestResult = updater->checkManifest();
+
+ if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) {
+ // Handle different error cases
+ std::string errorMsg;
+ switch (manifestResult) {
+ case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED:
+ errorMsg = "Failed to fetch manifest";
+ break;
+ case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE:
+ errorMsg = "Manifest file too large";
+ break;
+ case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED:
+ errorMsg = "Failed to parse manifest";
+ break;
+ case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION:
+ errorMsg = "Manifest missing files section";
+ break;
+ case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION:
+ errorMsg = "Manifest missing version section";
+ break;
+ case AppUpdater::ManifestCheckResult::VERSION_CURRENT:
+ errorMsg = "Current version is up to date";
+ // This is not actually an error
+ ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str());
+ updateProgress(AppUpdater::UpdateStatus::MESSAGE, 0, errorMsg.c_str());
+ // Don't throw, just exit gracefully
+ break;
+ default:
+ errorMsg = "Unknown manifest check error";
+ }
+
+ if (manifestResult != AppUpdater::ManifestCheckResult::VERSION_CURRENT) {
+ ESP_LOGE(TAG, "Manifest check failed: %s", errorMsg.c_str());
+ updateProgress(AppUpdater::UpdateStatus::ERROR, 0, errorMsg.c_str());
+ }
+ }
+
+ if (updater->IsUpdateAvailable()) {
+ bool filesUpdated = true;
+ bool firmwareUpdated = false; // Initialize to false - only set to true if firmware is actually updated
+
+ // Update files based on update mode
+ if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
+ ESP_LOGI(TAG, "Update mode includes files, updating files...");
+ filesUpdated = updater->updateFilesArray();
+ if (!filesUpdated) {
+ ESP_LOGW(TAG, "Some files failed to update");
+ if (g_UpdateMode == UpdateMode::UPDATE_FILES_ONLY) {
+ ESP_LOGE(TAG, "Files-only update failed");
+ updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update files");
+ // Clean up and exit task
+ esp_task_wdt_delete(NULL);
+ Update_Task_Handle = NULL;
+ vTaskDelete(NULL);
+ return;
+ } else {
+ ESP_LOGW(TAG, "File update failed, but continuing with firmware update");
+ }
+ }
+ } else {
+ ESP_LOGI(TAG, "Skipping file updates (mode: firmware only)");
+ }
+
+ // Update firmware based on update mode
+ if (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) {
+ ESP_LOGI(TAG, "Update mode includes firmware, updating firmware...");
+ firmwareUpdated = updater->updateApp();
+ if (!firmwareUpdated) {
+ ESP_LOGE(TAG, "Failed to update firmware");
+ updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Failed to update firmware");
+ // Clean up and exit task
+ esp_task_wdt_delete(NULL);
+ Update_Task_Handle = NULL;
+ vTaskDelete(NULL);
+ return;
+ }
+ } else {
+ ESP_LOGI(TAG, "Skipping firmware update (mode: files only)");
+ }
+
+ // Determine if we need to restart
+ bool needsRestart = (g_UpdateMode == UpdateMode::UPDATE_FIRMWARE_ONLY || g_UpdateMode == UpdateMode::UPDATE_BOTH) && firmwareUpdated;
+
+ if (needsRestart) {
+ ESP_LOGI(TAG, "Firmware update successful, restarting...");
+ sendUpdateMessage("Restarting... ", true, 100);
+ vTaskDelay(2000);
+ ESP.restart();
+ } else {
+ ESP_LOGI(TAG, "Update completed successfully (no restart required)");
+ updateProgress(AppUpdater::UpdateStatus::COMPLETE, 100, "Update completed successfully");
+ }
+ }
+
+ // No C++ exceptions used - errors are handled inline and via return codes
cleanup:
// Clean up watchdog before exit
@@ -1007,7 +1055,12 @@ void startVersionCheckTask() {
void versionCheckTask(void* parameter){
if(updateUrl == ""){
- loadUpdateJson();
+ if(!loadUpdateJson()){
+ ESP_LOGE(TAG, "versionCheckTask: failed to load update.json");
+ versionCheckTask_Handle = NULL;
+ vTaskDelete(NULL);
+ return;
+ }
}
AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
@@ -1025,35 +1078,36 @@ void versionCheckTask(void* parameter){
vTaskDelete(NULL);
}
-void loadUpdateJson(void) {
- try {
- ESP_LOGD(TAG, "loadUpdateJaon function...");
- if(updateUrl == "") {
- String updateJsonPath = "/system/update.json";
+bool loadUpdateJson(void) {
+ ESP_LOGD(TAG, "loadUpdateJson function...");
+ if(updateUrl == "") {
+ String updateJsonPath = "/system/update.json";
- // Read and parse update.json
- File file = LittleFS.open(updateJsonPath);
- if (!file) {
- throw std::runtime_error("Failed to open update.json");
- }
-
- JsonDocument doc;
- DeserializationError error = deserializeJson(doc, file);
- file.close();
-
- if (error) { throw std::runtime_error("Failed to parse update.json"); }
-
- // Get update configuration
- JsonObject jObj = doc.as();
- String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
- String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
- updateUrl = baseUrl + folderName;
-
- ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
+ // Read and parse update.json
+ File file = LittleFS.open(updateJsonPath);
+ if (!file) {
+ ESP_LOGE(TAG, "Failed to open update.json");
+ return false;
}
- } catch (const std::exception& e) {
- ESP_LOGE(TAG, "Update failed: %s", e.what());
+
+ JsonDocument doc;
+ DeserializationError error = deserializeJson(doc, file);
+ file.close();
+
+ if (error) {
+ ESP_LOGE(TAG, "Failed to parse update.json: %s", error.c_str());
+ return false;
+ }
+
+ // Get update configuration
+ JsonObject jObj = doc.as();
+ String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
+ String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
+ updateUrl = baseUrl + folderName;
+
+ ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
}
+ return true;
}
void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {
diff --git a/src/BLE_SP110E.cpp b/src/BLE_SP110E.cpp
index 15ed1b0..f6d5b04 100644
--- a/src/BLE_SP110E.cpp
+++ b/src/BLE_SP110E.cpp
@@ -5,6 +5,9 @@
#include "ATALights.h"
#include "BleSettings.h"
#include
+#include "my_device.h"
+#include "global.h" // for get_chip_mac
+#include "esp_log.h"
static const char *tag = "BLE_SP110E";
@@ -33,7 +36,6 @@ TaskHandle_t LightStick_Client_Task_Handle = NULL;
//#define UPGRADE_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0"
//#define UPGRADE_CHARACTERISTIC_UUID "abcdef01-2345-6789-1234-56789abcdef1"
-
//typedef enum {SM16703,TM1804,UCS1903,WS2811,WS2801,SK6812,LPD6803,LPD8806,APA102,APA105,DMX512,TM1914,TM1913,P9813,INK1003,P943S,P9411,P9413,TX1812,TX1813,GS8206,GS8208,SK9822,TM1814,SK6812_RGBW,P9414,PG412,IC_MODEL_COUNT } IC_MODELS;
//typedef enum {RGB,RBG,GRB,GBR,BRG,BGR,SEQUENCE_COUNT} SEQUENCES;
@@ -133,6 +135,78 @@ public:
process_BLE_SP110E_Command(data, procLen, pCharacteristic);
}
+ // Respond to read requests by returning the current led_status structure
+ void onRead(NimBLECharacteristic* pCharacteristic) override {
+ if (!pCharacteristic) return;
+
+ USER_SETTINGS userSettings = {};
+
+ userSettings.cmd = 0; // Indicate this is a status response
+ userSettings.limitedMode = sys_settings.limitedMode ? 1 : 0;
+ userSettings.profile[9] = '\0'; // Ensure null-terminated
+ {
+ // sys_settings.profile is an Arduino String; use c_str() to obtain a const char*
+ const char* src = sys_settings.profile.c_str();
+ // copy up to size-1 characters to ensure null-termination
+ size_t maxCopy = sizeof(userSettings.profile) - 1;
+ size_t srcLen = strlen(src);
+ size_t copyLen = srcLen < maxCopy ? srcLen : maxCopy;
+ if (copyLen) memcpy(userSettings.profile, src, copyLen);
+ // null terminate right after copied data
+ userSettings.profile[copyLen] = '\0';
+ // ensure the remaining bytes are zeroed out
+ for (size_t i = copyLen + 1; i < sizeof(userSettings.profile); ++i) userSettings.profile[i] = '\0';
+ }
+
+ userSettings.temperature = boardTemperature; // Placeholder, implement actual temperature reading if needed
+ userSettings.vIn = PowerVin; // Placeholder, implement actual voltage reading if needed
+
+ // Determine chipset with case‐insensitive substring match
+ std::string chip = std::string(sys_settings.ledStripSettings[0]->chip.c_str());
+ std::string chipLower;
+ chipLower.resize(chip.size());
+ std::transform(chip.begin(), chip.end(), chipLower.begin(), [](unsigned char c){ return std::tolower(c); });
+ if (chipLower.find("WS2812b") != std::string::npos) userSettings.ledChipset1 = 0;
+ else if (chipLower.find("SK6812") != std::string::npos) userSettings.ledChipset1 = 1;
+ else if (chipLower.find("WS2811") != std::string::npos) userSettings.ledChipset1 = 2;
+ else if (chipLower.find("WS2815") != std::string::npos) userSettings.ledChipset1 = 3;
+ else userSettings.ledChipset1 = 0;
+
+
+ strncpy(userSettings.rgbOrder1, sys_settings.ledStripSettings[0]->rgbOrder.c_str(), 4);
+ userSettings.ledCount1 = sys_settings.ledStripSettings[0]->size;
+ userSettings.ledShift1 = sys_settings.ledStripSettings[0]->shift;
+ userSettings.ledBrightness1= sys_settings.ledStripSettings[0]->bright;
+
+ std::string chip2 = std::string(sys_settings.ledStripSettings[1]->chip.c_str());
+ std::string chipLower2;
+ chipLower2.resize(chip2.size());
+ std::transform(chip2.begin(), chip2.end(), chipLower2.begin(),
+ [](unsigned char c){ return std::tolower(c); });
+ if (chipLower2.find("WS2812b") != std::string::npos) userSettings.ledChipset2 = 0;
+ else if (chipLower2.find("SK6812") != std::string::npos) userSettings.ledChipset2 = 1;
+ else if (chipLower2.find("WS2811") != std::string::npos) userSettings.ledChipset2 = 2;
+ else if (chipLower2.find("WS2815") != std::string::npos) userSettings.ledChipset2 = 3;
+ else userSettings.ledChipset2 = 0;
+
+ strncpy(userSettings.rgbOrder2, sys_settings.ledStripSettings[1]->rgbOrder.c_str(), 4);
+ userSettings.ledCount2 = sys_settings.ledStripSettings[1]->size;
+ userSettings.ledShift2 = sys_settings.ledStripSettings[1]->shift;
+ userSettings.ledBrightness2= sys_settings.ledStripSettings[1]->bright;
+
+ userSettings.frontLightMin = sys_settings.rampLightSettings[0].min;
+ userSettings.frontLightMax = sys_settings.rampLightSettings[0].max;
+ userSettings.rearLightMin = sys_settings.rampLightSettings[1].min;
+ userSettings.rearLightMax = sys_settings.rampLightSettings[1].max;
+ userSettings.fanLowerTemp = sys_settings.tSensorSettings.setpoint1;
+ userSettings.fanUpperTemp = sys_settings.tSensorSettings.setpoint2;
+
+ // Provide the full INFO_PACK as the characteristic value so readers get device info
+ pCharacteristic->setValue((uint8_t *)&userSettings, sizeof(USER_SETTINGS));
+ // Do not automatically notify on read (notify is for subscriptions), but keep a log
+ ESP_LOGD(tag, "onRead: returning INFO_PACK (%zu bytes)", sizeof(INFO_PACK));
+ }
+
private:
static void logBytes(const uint8_t* data, size_t len) {
if (!data) return;
@@ -222,20 +296,24 @@ void sendToAllClients(const uint8_t *data, size_t len) {
void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacteristic* bleChar) {
- if (!val) {
- ESP_LOGE(tag, "Null command data received");
- return;
- }
-
- if (len < 4) {
- ESP_LOGW(tag, "Command too short: %d bytes, expected at least 4", len);
- return;
+ //uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer
+
+ if (!val) { ESP_LOGE(tag, "Null command data received"); return;}
+ if (len < 4) { ESP_LOGW(tag, "Command too short: %d bytes, expected at least 4", len); return; }
+
+ //ESP_LOGI(tag, "USER_SETTING size is: %d", sizeof(USER_SETTINGS));
+
+ uint8_t command = 0;
+ if (len == 4){
+ command = val[3];
+ }else if (len == sizeof(USER_SETTINGS)){
+ command = val[0];
+ //ESP_LOGI(tag, "Command received: 0x%02X, length: %d", command, len);
+ //ESP_LOGI(tag, "byte1: %d byte2: %d byte3: %d byte4: %d byte5: %d byte6: %d byte7: %d byte8: %d",
+ // val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8]);
}
- uint8_t command = val[3];
- //ESP_LOGI(tag, "Command received: 0x%02X, length: %d", command, len);
-
- uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer
+ ESP_LOGI(tag, "Command received: 0x%02X, data0: %d, data1: %d, data2: %d, length: %d", command, val[0], val[1], val[2], len);
// Handle different commands
switch (command) {
@@ -273,7 +351,7 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
break;
case SET_SPEED:
led_status.speed = val[0];
- ESP_LOGI(tag, "Mode set to %d", led_status.speed);
+ ESP_LOGI(tag, "Speed set to %d", led_status.speed);
break;
case GET_CHECK_DEVICE: // This prepends a checksum
led_status.checksum = calculateChecksum(val);
@@ -307,9 +385,42 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
case SET_DEVICE_NAME:
ESP_LOGI(tag, "Set Device Name");
break;
+ case SET_SHIFT:
+ RGB_Lights_Set_Animation(SHIFT_INDEX, val[0], val[1], val[2]);
+ ESP_LOGI(tag, "Set Shift to %d", val[0]);
+ break;
+ case SET_USER_SETTINGS:{
+ USER_SETTINGS userSettings;
+
+ if (len < sizeof(USER_SETTINGS)) {
+ ESP_LOGW(tag, "SET_USER_SETTINGS command too short: %d bytes, expected at least %d", len, sizeof(USER_SETTINGS));
+ break;
+ }
+ memcpy(&userSettings, &val[0], sizeof(USER_SETTINGS) );
+
+ // Print out all the received settings for debugging
+ /*
+ ESP_LOGI(tag, "Received User Settings:");
+ ESP_LOGI(tag, " Limited Mode: %d", userSettings.limitedMode);
+
+ ESP_LOGI(tag, " LED Chip Type: %d", userSettings.ledChipset1);
+ ESP_LOGI(tag, " LED Color Order: %s", userSettings.rgbOrder1);
+ ESP_LOGI(tag, " LED Count: %d", userSettings.ledCount1);
+ ESP_LOGI(tag, " LED Shift: %d", userSettings.ledShift1);
+ ESP_LOGI(tag, " LED Brightness: %d", userSettings.ledBrightness1);
+
+ ESP_LOGI(tag, " Fan Lower Temp: %d", userSettings.fanLowerTemp);
+ ESP_LOGI(tag, " Fan Upper Temp: %d", userSettings.fanUpperTemp);
+ */
+
+ SetAndSaveUserSettings(userSettings);
+
+ }
+ break;
+
default:
ESP_LOGW(tag, "Unknown command: 0x%02X", command);
- break;
+ break;
}
}
@@ -331,7 +442,7 @@ void Init_BLE_SP110E(NimBLEServer* pServer) {
// Create FFE1 Characteristic with WRITE and NOTIFY properties
pSP110ECharacteristic = pService->createCharacteristic(
BTSP110ECharacteristicUUID.c_str(),
- NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
+ NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
);
// Register the callback with the characteristic
@@ -422,11 +533,8 @@ void BLE_LightStick_Client_Task(void *parameter) {
// Get the characteristic.
pRemoteCharacteristic = pRemoteService->getCharacteristic(BTStickCharacteristicUUID.c_str());
if (pRemoteCharacteristic != nullptr && pRemoteCharacteristic->canNotify()) {
- pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic,
- uint8_t* pData, size_t length, bool isNotify) {
- Serial.print("Notification received: ");
- Serial.write(pData, length);
- Serial.println();
+ pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
+ ESP_LOGD(tag, "Notification received: %zu bytes", length);
process_BLE_SP110E_Command(pData, length, nullptr);
});
} else {
@@ -483,7 +591,7 @@ class MyAdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {
NimBLEDevice::getScan()->stop();
myDevice = advertisedDevice;
masterFound = true;
- Serial.println("Device found!");
+ ESP_LOGE(tag, "Device found!");
}
}
};
\ No newline at end of file
diff --git a/src/BleServer.cpp b/src/BleServer.cpp
index 76fb565..0165181 100644
--- a/src/BleServer.cpp
+++ b/src/BleServer.cpp
@@ -38,35 +38,87 @@ class ServerCallbacks : public NimBLEServerCallbacks {
class ServerCallbacks : public NimBLEServerCallbacks {
public:
- void onConnect(NimBLEServer* /*pServer*/) override {
- ESP_LOGI(tag, "Client connected");
- ensureAdvertising("onConnect");
+ ServerCallbacks() : connectedClients(0) {}
+
+ void onConnect(NimBLEServer* pServer) override {
+ // Use server's connected count to avoid double-handling between overloads
+ int count = 0;
+ if (pServer) count = pServer->getConnectedCount();
+ connectedClients = count;
+ ESP_LOGI(tag, "Client connected (count=%d) [no-desc overload]", connectedClients);
+ if (connectedClients == 1) {
+ NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
+ if (adv && adv->isAdvertising()) {
+ if (adv->stop()) ESP_LOGI(tag, "Advertising stopped after client connected");
+ else ESP_LOGE(tag, "Failed to stop advertising after connect");
+ }
+ } else if (connectedClients > 1) {
+ ESP_LOGW(tag, "Additional client connected while one is active (no-desc)");
+ }
Buzzer_Play_Tune(TUNE_CONNECTED, 1);
}
+ // This overload provides connection descriptor details (conn handle) so we can reject extra clients.
+ void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) override {
+ // desc contains the connection handle for this new client
+ int connHandle = desc ? desc->conn_handle : -1;
+ // Use server's connected count to determine how many clients are attached
+ int count = 0;
+ if (pServer) count = pServer->getConnectedCount();
+ connectedClients = count;
+ ESP_LOGI(tag, "Client connected (count=%d) conn_handle=%d", connectedClients, connHandle);
+
+ // If this is the first client, stop advertising so no additional clients can connect
+ if (connectedClients == 1) {
+ NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
+ if (adv && adv->isAdvertising()) {
+ if (adv->stop()) ESP_LOGI(tag, "Advertising stopped after client connected");
+ else ESP_LOGE(tag, "Failed to stop advertising after connect");
+ }
+ } else if (connectedClients > 1) {
+ // Reject this new connection immediately to enforce single-client policy
+ ESP_LOGW(tag, "Rejecting extra client conn_handle=%d (server count=%d)", connHandle, connectedClients);
+ if (pServer) {
+ try {
+ pServer->disconnect(connHandle);
+ ESP_LOGI(tag, "Disconnected extra client conn_handle=%d", connHandle);
+ } catch (...) {
+ ESP_LOGE(tag, "Exception while disconnecting extra client conn_handle=%d", connHandle);
+ }
+ }
+ // Update connectedClients after rejecting (get fresh count if possible)
+ if (pServer) connectedClients = pServer->getConnectedCount();
+ }
+ //Buzzer_Play_Tune(TUNE_CONNECTED, 1);
+ }
+
void onDisconnect(NimBLEServer* /*pServer*/) override {
- ESP_LOGI(tag, "Client disconnected");
- ensureAdvertising("onDisconnect");
+ if (connectedClients > 0) connectedClients--;
+ ESP_LOGI(tag, "Client disconnected (count=%d)", connectedClients);
+
+ // If there are no clients left, restart advertising so a new client can connect
+ if (connectedClients == 0) {
+ NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
+ if (!adv) {
+ ESP_LOGE(tag, "Advertising object unavailable on disconnect");
+ return;
+ }
+ if (adv->isAdvertising()) {
+ ESP_LOGD(tag, "Advertising already running on disconnect");
+ return;
+ }
+ if (adv->start()) {
+ ESP_LOGI(tag, "Advertising restarted after client disconnect");
+ } else {
+ ESP_LOGE(tag, "Failed to start advertising after disconnect");
+ }
+ }
+
Buzzer_Play_Tune(TUNE_DISCONNECTED, 1);
}
private:
- void ensureAdvertising(const char* reason) {
- NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
- if (!adv) {
- ESP_LOGE(tag, "[%s] Advertising object unavailable", reason);
- return;
- }
- if (adv->isAdvertising()) {
- ESP_LOGD(tag, "[%s] Advertising already running", reason);
- return;
- }
- if (adv->start()) {
- ESP_LOGI(tag, "[%s] Advertising (re)started", reason);
- } else {
- ESP_LOGE(tag, "[%s] Failed to start advertising", reason);
- }
- }
+ int connectedClients;
};
diff --git a/src/BleSettings.cpp b/src/BleSettings.cpp
index 33dfebf..3425158 100644
--- a/src/BleSettings.cpp
+++ b/src/BleSettings.cpp
@@ -70,8 +70,6 @@ void Load_BLE_Settings(const String &configPath) {
BTDeviceName += macSuffix[0]; // Add first character
BTDeviceName += macSuffix[1]; // Add second character
- BTDeviceName = "SP110E";
-
ESP_LOGI(tag, "Loaded BLE config: name=%s svc=%s char1=%s stick=%s upg_svc=%s upg1=%s upg2=%s",
BTDeviceName.c_str(), BTServiceUUID.c_str(), BTSP110ECharacteristicUUID.c_str(),
BTStickCharacteristicUUID.c_str(), BTUpgradeServiceUUID.c_str(),
diff --git a/src/PWM_Output.cpp b/src/PWM_Output.cpp
index c6ca700..62f4f2e 100644
--- a/src/PWM_Output.cpp
+++ b/src/PWM_Output.cpp
@@ -3,11 +3,7 @@
#include "global.h"
-const char* tag = "pwmout";
-
-const float binaryPow[17] = {
- 0, 2, 4, 8, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535
-};
+static const char* tag = "pwmout";
PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected){
this->currDuty = 0;
@@ -15,17 +11,21 @@ PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float
this->maxDuty = maxDuty;
this->visionCorrected = visionCorrected;
this->freq = freq;
+ this->pin = pin;
setResolution(res);
pinMode(pin, OUTPUT);
uint32_t actualFreq = ledcSetup(ch, freq, res);
if (actualFreq != freq) {
ESP_LOGE(tag, "pwmOut-> ch:%d ledcSetup failed! Requested freq: %d, Actual freq: %d", ch, freq, actualFreq);
+ // continue but mark as uninitialized to prevent usage
+ this->initialized = false;
return;
}
ledcAttachPin(pin, ch);
setOutput(this->currDuty);
+ this->initialized = true;
}
void PWM_Output::setMaxDuty(float duty) {
@@ -41,6 +41,10 @@ void PWM_Output::setMaxDuty(float duty) {
// Range is 0 to 100%
void PWM_Output::setOutput(float duty){
+ if(!this->initialized){
+ ESP_LOGW(tag, "setOutput called on uninitialized PWM_Output (ch=%d)", this->ch);
+ return;
+ }
// Clamp the duty cycle to the range [0.0, maxDuty]
if (duty < 0.0) {
duty = 0.0;
@@ -50,15 +54,14 @@ void PWM_Output::setOutput(float duty){
// calculate correct duty value
int outDutyVal;
- if(this->visionCorrected){
- outDutyVal = linearizeOutput(duty);
- }
- else{
+ if(this->visionCorrected){
+ outDutyVal = linearizeOutput(duty);
+ } else {
outDutyVal = static_cast(duty * this->standardFactor);
}
- // Clamp to valid resolution range [0, 2^res - 1]
- int maxVal = static_cast(binaryPow[this->res]);
+ // Clamp to valid resolution range [0, (1<res >= 31) ? INT32_MAX : ((1 << this->res) - 1);
if (outDutyVal < 0) outDutyVal = 0;
if (outDutyVal > maxVal) outDutyVal = maxVal;
@@ -72,8 +75,10 @@ void PWM_Output::setFreq(uint32_t fq){
uint32_t newFreq;
if(this->freq != fq){
newFreq = ledcChangeFrequency(this->ch, fq, this->res);
- if(newFreq){
- this->freq = fq;
+ if(newFreq > 0){
+ this->freq = newFreq;
+ } else {
+ ESP_LOGW(tag, "ledcChangeFrequency failed for ch:%d requested:%u", this->ch, fq);
}
}
}
@@ -84,8 +89,9 @@ void PWM_Output::setResolution(uint8_t res){
if(this->res > 16) this->res = 16;
// Use the clamped resolution when computing factors
- this->standardFactor = binaryPow[this->res] * 0.01f;
- this->visionFactor = binaryPow[this->res] * 0.0001f;
+ int maxVal = (this->res >= 31) ? INT32_MAX : ((1 << this->res) - 1);
+ this->standardFactor = static_cast(maxVal) * 0.01f;
+ this->visionFactor = static_cast(maxVal) * 0.0001f;
ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor);
}
diff --git a/src/Ramp_Lights.cpp b/src/Ramp_Lights.cpp
index b696ce9..a91e14b 100644
--- a/src/Ramp_Lights.cpp
+++ b/src/Ramp_Lights.cpp
@@ -14,9 +14,12 @@ RAMP_LIGHT::RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, float min, floa
button->attachLongPressStop([](void* context) { static_cast(context)->longPressStop(); }, this);
button->attachDuringLongPress([](void* context) { static_cast(context)->duringLongPress(); }, this);
- if(min < 0.0) min = 0.0;
- if(max > 100.0) max = 100.0;
- currentValue = min;
+
+ if(min < 0.0) this->min = 0.0;
+ if(max > 100.0) this->max = 100.0;
+ if(this->max < this->min) this->max = this->min;
+ if(step <= 0.0) this->step = 1.0;
+ currentValue = this->min;
IsOn = false;
rampState = RampingUp;
}
@@ -52,11 +55,17 @@ void RAMP_LIGHT::longPressStop(){
void RAMP_LIGHT::duringLongPress(){
if(IsOn){
if (tickCount > 0 && --tickCount == 0) {
- // When ramping down, currentValue may go below min if step is large; constrain ensures bounds.
- // Ensure currentValue stays within [min, max] bounds for safe PWM operation
- currentValue = constrain(currentValue, min, max);
- pwmOutput->setOutput(currentValue);
- //ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput->currOutVal, pwmOutput->getOutVal());
+ // Adjust currentValue based on ramp direction
+ if(rampState == RampingUp){
+ currentValue += step;
+ } else {
+ currentValue -= step;
+ }
+
+ // Constrain and apply
+ currentValue = constrain(currentValue, this->min, this->max);
+ if(pwmOutput) pwmOutput->setOutput(currentValue);
+ ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput? pwmOutput->currOutVal : -1, pwmOutput? pwmOutput->getOutVal() : -1);
tickCount = TickDelayCount;
}
}
diff --git a/src/common/fileSystem.cpp b/src/common/fileSystem.cpp
index 73c4d6a..3120d67 100644
--- a/src/common/fileSystem.cpp
+++ b/src/common/fileSystem.cpp
@@ -23,23 +23,31 @@ void Init_File_System(void){
//printAllSystemFiles();
}
-void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){
+void getAllDirectories(String directoryList[], int &count){
File root = LittleFS.open("/");
if (!root || !root.isDirectory()){
ESP_LOGE("FileSystem", "Failed to open root directory or root is not a directory");
return;
}
+ // Initialize with root directory
+ if (MAX_DIRECTORIES <= 0) {
+ ESP_LOGW("FileSystem", "MAX_DIRECTORIES is not set or invalid");
+ root.close();
+ return;
+ }
directoryList[0] = "/";
count = 1;
File file = root.openNextFile();
while (file) {
if (file.isDirectory()){
- if (count < 10){ // Ensure we don't overflow the array
- directoryList[count] = '/' + String(file.name());
+ if (count < MAX_DIRECTORIES){ // Ensure we don't overflow the caller's array
+ String entry = String(file.name());
+ if (!entry.startsWith("/")) entry = "/" + entry;
+ directoryList[count] = entry;
count++;
- }else{
+ } else {
ESP_LOGW("FileSystem", "Directory list array is full");
break;
}
diff --git a/src/common/fileSystem.h b/src/common/fileSystem.h
index 13a1414..1a5cfbc 100644
--- a/src/common/fileSystem.h
+++ b/src/common/fileSystem.h
@@ -15,7 +15,7 @@ void writeFilesToSerial(void);
//void getAllDirectoriesRecursive(const String& path, String directoryList[], int& count, int maxSize);
//void getAllDirectories(String directoryList[], int& count, int maxSize);
-void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int& count);
+void getAllDirectories(String directoryList[], int& count);
void printFilesInDirectories(const String directoryList[], int count);
diff --git a/src/global.cpp b/src/global.cpp
index b0bdf77..fa1447b 100644
--- a/src/global.cpp
+++ b/src/global.cpp
@@ -412,4 +412,9 @@ void print_task_watermarks(void) {
} else {
ESP_LOGE(tag, "Failed to get current task handle");
}
-}
\ No newline at end of file
+}
+
+
+float updateLowpass(float currentValue, float newValue, float alpha) {
+ return (alpha * newValue) + ((1 - alpha) * currentValue);
+}
diff --git a/src/main.cpp b/src/main.cpp
index 028827f..89f0ca5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -82,6 +82,9 @@ TimerHandle_t upgradeHeartbeatTimer = NULL;
TimerHandle_t diagnosticsTimer = NULL;
#define diagnosticsInterval 60000 // ms
+TimerHandle_t analogInputTimer = NULL;
+#define analogInputInterval 3000 // ms
+
void setupLogLevels(esp_log_level_t logLevel);
@@ -120,6 +123,13 @@ void DiagnosticsCallback(TimerHandle_t xTimer) {
#endif
}
+void AnalogInputCallback(TimerHandle_t xTimer) {
+ float v = readBoardInputVoltage();
+ PowerVin = updateLowpass(prevPowerVin, v*1.01, PowerVinAlpha);
+ prevPowerVin = PowerVin;
+ //ESP_LOGI(tag, "Input Voltage = %f", PowerVin);
+}
+
void checkLEDCChannels()
{
@@ -167,7 +177,7 @@ void setup()
printAllSystemFiles();
}
- String board_file_path, booth_file_path;
+ String board_file_path;
Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);
// Load Board Pins
@@ -205,15 +215,10 @@ void setup()
// Initialize Temperature Sensor
Init_TSensor(72, &sys_settings.tSensorSettings);
-
- float val = readBoardInputVoltage();
- ESP_LOGI(tag, "Input Volage = %f", val);
-
// Initialize BLE & Wifi
// If button 1 is held during boot, enable upgrade mode
Load_BLE_Settings("/system/ble.json");
- if (digitalRead(sys_settings.boardPins.btn[0]) == LOW)
- {
+ if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){
setStatusPin1(true);
ESP_LOGW(tag, "Upgrade Mode Triggered");
ESP_LOGW(tag, "Enabling BLE and Update Service");
@@ -223,8 +228,7 @@ void setup()
UpgradeMode = true;
upgradeHeartbeatTimer = xTimerCreate("UpgradeHeartbeat", pdMS_TO_TICKS(5000), pdTRUE, NULL, UpgradeHeartbeatCallback);
}
- else
- {
+ else {
ESP_LOGI(tag, "Enabling BLE, No Update Service");
Init_BleServer(true, false); // Dont start the Upgrade service
}
@@ -236,20 +240,18 @@ void setup()
}
#if OLED_ENABLED
- // Init OLED
Init_OLED(sys_settings.oledSettings.width, sys_settings.oledSettings.height, sys_settings.boardPins.oled_mosi, sys_settings.boardPins.oled_sck, sys_settings.boardPins.oled_dc, sys_settings.boardPins.oled_rst, sys_settings.boardPins.oled_cs);
#endif
#if STRIPS_ENABLED
- Init_RGB_Lights_Task();
- //vTaskDelay(100);
- //Init_Ramp_Front_Light_Task();
+ Init_RGB_Lights_Task(UpgradeMode);
#endif
// Create and start software timers
buttonScanTimer = xTimerCreate("ButtonScan", pdMS_TO_TICKS(buttonScanInterval), pdTRUE, NULL, ButtonScanCallback);
temperatureTimer = xTimerCreate("Temperature", pdMS_TO_TICKS(sys_settings.tSensorSettings.intervalMs), pdTRUE, NULL, TemperatureCallback);
statusLedTimer = xTimerCreate("StatusLED", pdMS_TO_TICKS(statusLedInterval), pdTRUE, NULL, StatusLedCallback);
+ analogInputTimer = xTimerCreate("AnalogInput", pdMS_TO_TICKS(analogInputInterval), pdTRUE, NULL, AnalogInputCallback);
#if FREERTOs_DIAGNOSTICS
@@ -257,10 +259,11 @@ void setup()
#endif
// Start the timers
- if (buttonScanTimer) xTimerStart(buttonScanTimer, 25);
+ //if (buttonScanTimer) xTimerStart(buttonScanTimer, 100);
if (temperatureTimer && sys_settings.tSensorSettings.enabled) xTimerStart(temperatureTimer, 100);
if (statusLedTimer) xTimerStart(statusLedTimer, 0);
if (upgradeHeartbeatTimer && UpgradeMode) xTimerStart(upgradeHeartbeatTimer, upgradeHeartbeatInterval);
+ if (analogInputTimer) xTimerStart(analogInputTimer, 0);
#if FREERTOS_DIAGNOSTICS
if (diagnosticsTimer) xTimerStart(diagnosticsTimer, diagnosticsInterval);
@@ -315,9 +318,20 @@ void loop()
}
}
#endif
+
+ /*
+ for (int i = 0; i < 3; i++) {
+ if (boardButtons[i] != NULL) {
+ boardButtons[i]->tick();
+ }
+ }
+ vTaskDelay(buttonScanInterval);
+ */
// Small delay to prevent busy-waiting
- vTaskDelay(pdMS_TO_TICKS(100));
+ //vTaskDelay(pdMS_TO_TICKS(100));
+
+ vTaskDelay(portMAX_DELAY);
}
void setupLogLevels(esp_log_level_t logLevel)
diff --git a/src/my_board.cpp b/src/my_board.cpp
index bf7a7fc..b73d6fc 100644
--- a/src/my_board.cpp
+++ b/src/my_board.cpp
@@ -4,124 +4,143 @@
#include
#include
-#include "global.h"
#include "JsonConstrain.h"
+#include "global.h"
#include "system.h"
-static const char* tag = "board";
+static const char *tag = "board";
-BOARD_PINS* thisBoardPins;
+BOARD_PINS *thisBoardPins = nullptr;
// Basic validator for ESP32-S3 GPIOs; rejects common reserved/USB/strap pins
static bool isValidGpio(int pin) {
- if (pin < 0 || pin > 48) return false;
- switch (pin) {
+ if (pin < 0 || pin > 48)
+ return false;
+ switch (pin) {
case 19: // USB D-
case 20: // USB D+
case 45: // strapping
case 46: // strapping
- return false;
+ return false;
default:
- return true;
- }
+ return true;
+ }
}
-bool Load_Board_Pins(BOARD_PINS& boardPins, const String& path){
- // Default initialize to -1 to avoid stale values on partial loads
- memset(&boardPins, -1, sizeof(boardPins));
- thisBoardPins = &boardPins;
- File file = LittleFS.open(path);
+bool Load_Board_Pins(BOARD_PINS &boardPins, const String &path) {
+ // Default initialize to -1 to avoid stale values on partial loads
+ memset(&boardPins, -1, sizeof(boardPins));
+ thisBoardPins = &boardPins;
+ File file = LittleFS.open(path);
- if (!file) {
- ESP_LOGE(tag, "Error opening %s...", path.c_str());
- return false;
- }
+ if (!file) {
+ ESP_LOGE(tag, "Error opening %s...", path.c_str());
+ return false;
+ }
- JsonDocument doc;
- DeserializationError error = deserializeJson(doc, file);
- file.close();
+ JsonDocument doc;
+ DeserializationError error = deserializeJson(doc, file);
+ file.close();
- if (error) {
- ESP_LOGE(tag, "%s deserialize error!..", path.c_str());
- return false;
- }
+ if (error) {
+ ESP_LOGE(tag, "%s deserialize error: %s", path.c_str(), error.c_str());
+ return false;
+ }
- JsonObject boardJson = doc.as();
- boardPins.rgb1 = jsonConstrain(tag, boardJson, "rgb1", -1, 48, -1);
- boardPins.rgb2 = jsonConstrain(tag, boardJson, "rgb2", -1, 48, -1);
- boardPins.btn[0] = jsonConstrain(tag, boardJson, "btn1", -1, 48, -1);
- boardPins.btn[1] = jsonConstrain(tag, boardJson, "btn2", -1, 48, -1);
- boardPins.btn[2] = jsonConstrain(tag, boardJson, "btn3", -1, 48, -1);
- boardPins.buzzer = jsonConstrain(tag, boardJson, "buzzer", -1, 48, -1);
- boardPins.touch[0] = jsonConstrain(tag, boardJson, "touch1", -1, 48, -1);
- boardPins.touch[1] = jsonConstrain(tag, boardJson, "touch2", -1, 48, -1);
- boardPins.touch[2] = jsonConstrain(tag, boardJson, "touch3", -1, 48, -1);
- boardPins.touch[3] = jsonConstrain(tag, boardJson, "touch4", -1, 48, -1);
- boardPins.touch[4] = jsonConstrain(tag, boardJson, "touch5", -1, 48, -1);
- boardPins.shield = jsonConstrain(tag, boardJson, "shield", -1, 48, -1);
- boardPins.relay[0] = jsonConstrain(tag, boardJson, "relay1", -1, 48, -1);
- boardPins.relay[1] = jsonConstrain(tag, boardJson, "relay2", -1, 48, -1);
- boardPins.relay[2] = jsonConstrain(tag, boardJson, "relay3", -1, 48, -1);
- boardPins.relay[3] = jsonConstrain(tag, boardJson, "relay4", -1, 48, -1);
- boardPins.stat[0] = jsonConstrain(tag, boardJson, "stat1", -1, 48, -1);
- boardPins.stat[1] = jsonConstrain(tag, boardJson, "stat2", -1, 48, -1);
- boardPins.adc1 = jsonConstrain(tag, boardJson, "adc1", -1, 48, -1);
- boardPins.oled_dc = jsonConstrain(tag, boardJson, "oled_dc", -1, 48, -1);
- boardPins.oled_rst = jsonConstrain(tag, boardJson, "oled_rst", -1, 48, -1);
- boardPins.oled_mosi = jsonConstrain(tag, boardJson, "oled_mosi", -1, 48, -1);
- boardPins.oled_sck = jsonConstrain(tag, boardJson, "oled_sck", -1, 48, -1);
- boardPins.oled_cs = jsonConstrain(tag, boardJson, "oled_cs", -1, 48, -1);
- boardPins.ext[0] = jsonConstrain(tag, boardJson, "ext1", -1, 48, -1);
- boardPins.ext[1] = jsonConstrain(tag, boardJson, "ext2", -1, 48, -1);
- boardPins.rf433tx = jsonConstrain(tag, boardJson, "rf433tx", -1, 48, -1);
- boardPins.rf433rx = jsonConstrain(tag, boardJson, "rf433rx", -1, 48, -1);
+ JsonObject boardJson = doc.as();
+ boardPins.rgb1 = jsonConstrain(tag, boardJson, "rgb1", -1, 48, -1);
+ boardPins.rgb2 = jsonConstrain(tag, boardJson, "rgb2", -1, 48, -1);
+ boardPins.btn[0] = jsonConstrain(tag, boardJson, "btn1", -1, 48, -1);
+ boardPins.btn[1] = jsonConstrain(tag, boardJson, "btn2", -1, 48, -1);
+ boardPins.btn[2] = jsonConstrain(tag, boardJson, "btn3", -1, 48, -1);
+ boardPins.buzzer = jsonConstrain(tag, boardJson, "buzzer", -1, 48, -1);
+ boardPins.touch[0] = jsonConstrain(tag, boardJson, "touch1", -1, 48, -1);
+ boardPins.touch[1] = jsonConstrain(tag, boardJson, "touch2", -1, 48, -1);
+ boardPins.touch[2] = jsonConstrain(tag, boardJson, "touch3", -1, 48, -1);
+ boardPins.touch[3] = jsonConstrain(tag, boardJson, "touch4", -1, 48, -1);
+ boardPins.touch[4] = jsonConstrain(tag, boardJson, "touch5", -1, 48, -1);
+ boardPins.shield = jsonConstrain(tag, boardJson, "shield", -1, 48, -1);
+ boardPins.relay[0] = jsonConstrain(tag, boardJson, "relay1", -1, 48, -1);
+ boardPins.relay[1] = jsonConstrain(tag, boardJson, "relay2", -1, 48, -1);
+ boardPins.relay[2] = jsonConstrain(tag, boardJson, "relay3", -1, 48, -1);
+ boardPins.relay[3] = jsonConstrain(tag, boardJson, "relay4", -1, 48, -1);
+ boardPins.stat[0] = jsonConstrain(tag, boardJson, "stat1", -1, 48, -1);
+ boardPins.stat[1] = jsonConstrain(tag, boardJson, "stat2", -1, 48, -1);
+ boardPins.adc1 = jsonConstrain(tag, boardJson, "adc1", -1, 48, -1);
+ boardPins.oled_dc = jsonConstrain(tag, boardJson, "oled_dc", -1, 48, -1);
+ boardPins.oled_rst = jsonConstrain(tag, boardJson, "oled_rst", -1, 48, -1);
+ boardPins.oled_mosi = jsonConstrain(tag, boardJson, "oled_mosi", -1, 48, -1);
+ boardPins.oled_sck = jsonConstrain(tag, boardJson, "oled_sck", -1, 48, -1);
+ boardPins.oled_cs = jsonConstrain(tag, boardJson, "oled_cs", -1, 48, -1);
+ boardPins.ext[0] = jsonConstrain(tag, boardJson, "ext1", -1, 48, -1);
+ boardPins.ext[1] = jsonConstrain(tag, boardJson, "ext2", -1, 48, -1);
+ boardPins.rf433tx = jsonConstrain(tag, boardJson, "rf433tx", -1, 48, -1);
+ boardPins.rf433rx = jsonConstrain(tag, boardJson, "rf433rx", -1, 48, -1);
- // Validate pins against reserved GPIOs
- auto clampPin = [](int v){ return isValidGpio(v) ? v : -1; };
- boardPins.rgb1 = clampPin(boardPins.rgb1);
- boardPins.rgb2 = clampPin(boardPins.rgb2);
- for (int i=0;i<3;i++) boardPins.btn[i] = clampPin(boardPins.btn[i]);
- boardPins.buzzer = clampPin(boardPins.buzzer);
- for (int i=0;i<5;i++) boardPins.touch[i] = clampPin(boardPins.touch[i]);
- boardPins.shield = clampPin(boardPins.shield);
- for (int i=0;i<4;i++) boardPins.relay[i] = clampPin(boardPins.relay[i]);
- for (int i=0;i<2;i++) boardPins.stat[i] = clampPin(boardPins.stat[i]);
- boardPins.adc1 = clampPin(boardPins.adc1);
- boardPins.oled_dc = clampPin(boardPins.oled_dc);
- boardPins.oled_rst = clampPin(boardPins.oled_rst);
- boardPins.oled_mosi = clampPin(boardPins.oled_mosi);
- boardPins.oled_sck = clampPin(boardPins.oled_sck);
- boardPins.oled_cs = clampPin(boardPins.oled_cs);
- for (int i=0;i<2;i++) boardPins.ext[i] = clampPin(boardPins.ext[i]);
- boardPins.rf433tx = clampPin(boardPins.rf433tx);
- boardPins.rf433rx = clampPin(boardPins.rf433rx);
+ // Validate pins against reserved GPIOs
+ auto clampPin = [](int v) { return isValidGpio(v) ? v : -1; };
+ boardPins.rgb1 = clampPin(boardPins.rgb1);
+ boardPins.rgb2 = clampPin(boardPins.rgb2);
+ for (int i = 0; i < 3; i++)
+ boardPins.btn[i] = clampPin(boardPins.btn[i]);
+ boardPins.buzzer = clampPin(boardPins.buzzer);
+ for (int i = 0; i < 5; i++)
+ boardPins.touch[i] = clampPin(boardPins.touch[i]);
+ boardPins.shield = clampPin(boardPins.shield);
+ for (int i = 0; i < 4; i++)
+ boardPins.relay[i] = clampPin(boardPins.relay[i]);
+ for (int i = 0; i < 2; i++)
+ boardPins.stat[i] = clampPin(boardPins.stat[i]);
+ boardPins.adc1 = clampPin(boardPins.adc1);
+ boardPins.oled_dc = clampPin(boardPins.oled_dc);
+ boardPins.oled_rst = clampPin(boardPins.oled_rst);
+ boardPins.oled_mosi = clampPin(boardPins.oled_mosi);
+ boardPins.oled_sck = clampPin(boardPins.oled_sck);
+ boardPins.oled_cs = clampPin(boardPins.oled_cs);
+ for (int i = 0; i < 2; i++)
+ boardPins.ext[i] = clampPin(boardPins.ext[i]);
+ boardPins.rf433tx = clampPin(boardPins.rf433tx);
+ boardPins.rf433rx = clampPin(boardPins.rf433rx);
- ESP_LOGI(tag, "loaded Pins from %s", path.c_str());
- return true;
+ ESP_LOGI(tag, "loaded Pins from %s", path.c_str());
+ return true;
}
+void Init_Board_Basic(BOARD_PINS &boardPins) {
-void Init_Board_Basic(BOARD_PINS& boardPins)
-{
-
- if(boardPins.stat[0] >= 0){ pinMode(boardPins.stat[0], OUTPUT); }
- if(boardPins.stat[1] >= 0){ pinMode(boardPins.stat[1], OUTPUT); }
+ if (boardPins.stat[0] >= 0) {
+ pinMode(boardPins.stat[0], OUTPUT);
+ }
+ if (boardPins.stat[1] >= 0) {
+ pinMode(boardPins.stat[1], OUTPUT);
+ }
+ if (boardPins.btn[0] >= 0) {
+ pinMode(boardPins.btn[0], INPUT_PULLUP);
+ }
+ if (boardPins.btn[1] >= 0) {
+ pinMode(boardPins.btn[1], INPUT_PULLUP);
+ }
+ if (boardPins.btn[2] >= 0) {
+ pinMode(boardPins.btn[2], INPUT);
+ }
+ if (boardPins.rgb1 >= 0) {
+ pinMode(boardPins.rgb1, OUTPUT);
+ }
+ if (boardPins.rgb2 >= 0) {
+ pinMode(boardPins.rgb2, OUTPUT);
+ }
+ if (boardPins.relay[0] >= 0) {
+ pinMode(boardPins.relay[0], OUTPUT);
+ }
+ if (boardPins.relay[1] >= 0) {
+ pinMode(boardPins.relay[1], OUTPUT);
+ }
+ if (boardPins.relay[2] >= 0) {
+ pinMode(boardPins.relay[2], OUTPUT);
+ }
+ if (boardPins.relay[3] >= 0) {
+ pinMode(boardPins.relay[3], OUTPUT);
+ }
- if(boardPins.btn[0] >= 0){ pinMode(boardPins.btn[0], INPUT_PULLUP); }
- if(boardPins.btn[1] >= 0){ pinMode(boardPins.btn[1], INPUT_PULLUP); }
- if(boardPins.btn[2] >= 0){ pinMode(boardPins.btn[2], INPUT); }
-
- if(boardPins.rgb1 >= 0){ pinMode(boardPins.rgb1, OUTPUT); }
- if(boardPins.rgb2 >= 0){ pinMode(boardPins.rgb2, OUTPUT); }
-
- if(boardPins.relay[0] >= 0){ pinMode(boardPins.relay[0], OUTPUT); }
- if(boardPins.relay[1] >= 0){ pinMode(boardPins.relay[1], OUTPUT); }
- if(boardPins.relay[2] >= 0){ pinMode(boardPins.relay[2], OUTPUT); }
- if(boardPins.relay[3] >= 0){ pinMode(boardPins.relay[3], OUTPUT); }
-
- ESP_LOGI(tag, "Board pins initialized...");
+ ESP_LOGI(tag, "Board pins initialized...");
}
-
-
-
-
diff --git a/src/my_buttons.cpp b/src/my_buttons.cpp
index 39d945f..151d2bf 100644
--- a/src/my_buttons.cpp
+++ b/src/my_buttons.cpp
@@ -5,11 +5,10 @@
#include "AppUpgrade.h"
static const char* tag = "button";
-OneButton *boardButtons[3];
+OneButton *boardButtons[3] = { nullptr, nullptr, nullptr };
void Init_ButtonEvents(int8_t (&pin)[3]){
-
-
+ // Initialize buttons if pins are valid and not already initialized
if (pin[0] >= 0) {
if (boardButtons[0] == nullptr) {
boardButtons[0] = new OneButton(pin[0], true, true);
diff --git a/src/my_buzzer.cpp b/src/my_buzzer.cpp
index 930b82f..2d9918e 100644
--- a/src/my_buzzer.cpp
+++ b/src/my_buzzer.cpp
@@ -17,7 +17,7 @@ static const char* tag = "buzzer";
// Define static constexpr member from RtttlPlayer class
constexpr uint16_t RtttlPlayer::LUT4[12];
-RtttlPlayer *player;
+RtttlPlayer *player = nullptr;
BUZZ_TUNE buzzTune[TUNE_MAX_COUNT];
int8_t buzzPin;
int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer
@@ -105,7 +105,7 @@ void Buzzer_Load_Tunes(const char* tunesPath){
buzzTune[tuneIndex].cycles = jsonConstrain(tag, obj, "cycles", 1, 100, 1);
buzzTune[tuneIndex].pause = jsonConstrain(tag, obj, "pause", 0, 100, 0);
buzzTune[tuneIndex].melody = jsonConstrainString(tag, obj, "tune", DEFAULT_MELODY);
- ESP_LOGI(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...",
+ ESP_LOGD(tag, "Loaded tune %d: cycles=%d, pause=%d, melody=%.40s...",
tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause,
buzzTune[tuneIndex].melody.c_str());
tuneIndex++;
diff --git a/src/my_device.cpp b/src/my_device.cpp
index c2a477e..3c90010 100644
--- a/src/my_device.cpp
+++ b/src/my_device.cpp
@@ -1,79 +1,94 @@
#include "my_device.h"
#include "JsonConstrain.h"
-#include "global.h"
-#include "system.h"
-#include "my_buttons.h"
#include "PWM_Output.h"
#include "Ramp_Lights.h"
#include "esp_log.h"
-#include
-#include
+#include "global.h"
+#include "my_buttons.h"
+#include "system.h"
#include
#include
-#include
-#include
-#include
+#include
+#include
+#include
#include
#include
-#include
+#include
+#include
+#include
#include
#include
static const char *tag = "my_device";
SYS_SETTINGS sys_settings;
-PWM_Output *pwmOutputs[4];
-RAMP_LIGHT *rampLight1;
-RAMP_LIGHT *rampLight2;
+PWM_Output *pwmOutputs[4] = { nullptr, nullptr, nullptr, nullptr };
+RAMP_LIGHT *rampLight1 = nullptr;
+RAMP_LIGHT *rampLight2 = nullptr;
+
+String booth_file_path;
+
+float PowerVinAlpha = 0.5; // Low-pass filter alpha
+float prevPowerVin = 0.0;
+float PowerVin = 0.0;
+
+
// TODO Restore original setOutput code..
#define RELAY_RES 10
-void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4])
-{
- // Initialize all pointers to nullptr first
+void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]) {
+ // Delete any existing objects to avoid leaks on re-init
for (int i = 0; i < 4; i++) {
- pwmOutputs[i] = nullptr;
+ if (pwmOutputs[i]) {
+ delete pwmOutputs[i];
+ pwmOutputs[i] = nullptr;
+ }
}
-
- for (int i = 0; i < 4; i++)
- {
+
+ for (int i = 0; i < 4; i++) {
int chIndex = findUnusedLedcChannel();
- if (chIndex < 0)
- {
+ if (chIndex < 0) {
ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i);
continue;
}
- pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, pwmSettings[i].max, false);
- pwmOutputs[i]->setOutput(pwmSettings[i].def);
+ pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq,
+ pwmSettings[i].max, false);
+ if (pwmOutputs[i]) {
+ pwmOutputs[i]->setOutput(pwmSettings[i].def);
+ } else {
+ ESP_LOGE(tag, "Allocation failed for PWM Output%d", i);
+ }
// pwmOutputs[i]->setOutput(5.0);
- ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq, chIndex);
+ ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq,
+ chIndex);
}
}
-
-void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4])
-{
- if (settings[0].enabled)
- {
- rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], settings[0].min, settings[0].max, settings[0].step);
- ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 1, settings[0].btnIndex, settings[0].pwmOutIndex);
+void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]) {
+ if (settings[0].enabled) {
+ if (rampLight1) { delete rampLight1; rampLight1 = nullptr; }
+ rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], settings[0].min, settings[0].max, settings[0].step);
+ if (rampLight1)
+ ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d, min=%f, max=%f, step=%f", 1, settings[0].btnIndex, settings[0].pwmOutIndex, settings[0].min, settings[0].max, settings[0].step);
+ else
+ ESP_LOGE(tag, "Failed to allocate RampLight1");
}
- if (settings[1].enabled)
- {
- rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], settings[1].min, settings[1].max, settings[1].step);
- ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 2, settings[1].btnIndex, settings[1].pwmOutIndex);
+ if (settings[1].enabled) {
+ if (rampLight2) { delete rampLight2; rampLight2 = nullptr; }
+ rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], settings[1].min, settings[1].max, settings[1].step);
+ if (rampLight2)
+ ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d, min=%f, max=%f, step=%f", 2, settings[1].btnIndex, settings[1].pwmOutIndex, settings[1].min, settings[1].max, settings[1].step);
+ else
+ ESP_LOGE(tag, "Failed to allocate RampLight2");
}
}
-
// Get the files that should be used to setup the system
-void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, String &boothPath)
-{
+void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, String &boothPath) {
File file = LittleFS.open(sysPath);
- if (!file)
- {
+ if (!file) {
ESP_LOGE(tag, "Error opening %s...", sysPath);
return;
}
@@ -82,23 +97,21 @@ void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, Stri
DeserializationError error = deserializeJson(doc, file);
file.close();
- if (error)
- {
+ if (error) {
ESP_LOGE(tag, "%s deserialize error!..", sysPath);
return;
}
// get hardware version string
- boardPath = jsonConstrainString(tag, doc.as(), "boardfile", "/cfg/boards/board15.json");
- boothPath = jsonConstrainString(tag, doc.as(), "configfile", "/cfg/booths/custom.json");
+ boardPath =
+ jsonConstrainString(tag, doc.as(), "boardfile", "/cfg/boards/board15.json");
+ boothPath =
+ jsonConstrainString(tag, doc.as(), "configfile", "/cfg/booths/custom.json");
}
-
-void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
-{
+void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath) {
File file = LittleFS.open(boothPath);
- if (!file)
- {
+ if (!file) {
ESP_LOGE(tag, "Error opening %s...", boothPath.c_str());
return;
}
@@ -107,139 +120,150 @@ void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
DeserializationError error = deserializeJson(doc, file);
file.close();
- if (error)
- {
+ if (error) {
ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str());
return;
}
+ sys_settings.profile = jsonConstrainString(tag, doc.as(), "profile", "custom");
+
// ********** Mode ***********
String modeStr = jsonConstrainString(tag, doc.as(), "mode", "booth");
- if (modeStr == "roamer")
- {
+ if (modeStr == "roamer") {
sys_settings.mode = BOOTH_MODE_ROAMER;
- }
- else if (modeStr == "stik")
- {
+ } else if (modeStr == "stik") {
sys_settings.mode = BOOTH_MODE_STIK;
- }
- else
- {
+ } else {
sys_settings.mode = BOOTH_MODE_NONE;
}
// ********** PWM Out ***********
JsonArray pwmJsonArray = doc["pwmout"];
- if (!pwmJsonArray.isNull())
- {
+ if (!pwmJsonArray.isNull()) {
int pwmIndex = 0;
- for (JsonObject obj : pwmJsonArray)
- {
- if (pwmIndex >= sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0]))
+ for (JsonObject obj : pwmJsonArray) {
+ if (pwmIndex >=
+ sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0]))
break;
sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
- sys_settings.pwmOutSettings[pwmIndex].freq = jsonConstrain(tag, obj, "freq", 100, 5000, 250);
- sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0);
- sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0);
- sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain(tag, obj, "default", 0.0, 100.0, 0.0);
+ sys_settings.pwmOutSettings[pwmIndex].freq =
+ jsonConstrain(tag, obj, "freq", 100, 5000, 250);
+ sys_settings.pwmOutSettings[pwmIndex].min =
+ jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0);
+ sys_settings.pwmOutSettings[pwmIndex].max =
+ jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0);
+ sys_settings.pwmOutSettings[pwmIndex].def =
+ jsonConstrain(tag, obj, "default", 0.0, 100.0, 0.0);
sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
// sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true);
pwmIndex++;
}
ESP_LOGI(tag, "Loaded PWmOutput settings...");
- }
- else
- {
+ } else {
ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath);
}
// ********** Ramp Lights ***********
JsonArray rampJsonArray = doc["ramp-lights"];
- if (!rampJsonArray.isNull())
- {
+ if (!rampJsonArray.isNull()) {
int rampIndex = 0;
- for (JsonObject obj : rampJsonArray)
- {
- if (rampIndex >= sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0]))
+ for (JsonObject obj : rampJsonArray) {
+ if (rampIndex >=
+ sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0]))
break;
- sys_settings.rampLightSettings[rampIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
- sys_settings.rampLightSettings[rampIndex].vision = jsonConstrainBool(tag, obj, "vision", true);
- sys_settings.rampLightSettings[rampIndex].pwmOutIndex = jsonConstrain(tag, obj, "relay-index", 0, 1, 0);
- sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain(tag, obj, "button-index", 0, 1, 0);
- sys_settings.rampLightSettings[rampIndex].min = jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0);
- sys_settings.rampLightSettings[rampIndex].max = jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0);
- sys_settings.rampLightSettings[rampIndex].step = jsonConstrain(tag, obj, "step", 0.1, 10.0, 1.5);
+ sys_settings.rampLightSettings[rampIndex].enabled =
+ jsonConstrainBool(tag, obj, "en", true);
+ sys_settings.rampLightSettings[rampIndex].vision =
+ jsonConstrainBool(tag, obj, "vision", true);
+ sys_settings.rampLightSettings[rampIndex].pwmOutIndex =
+ jsonConstrain(tag, obj, "relay-index", 0, 1, 0);
+ sys_settings.rampLightSettings[rampIndex].btnIndex =
+ jsonConstrain(tag, obj, "button-index", 0, 1, 0);
+ sys_settings.rampLightSettings[rampIndex].min =
+ jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0);
+ sys_settings.rampLightSettings[rampIndex].max =
+ jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0);
+ sys_settings.rampLightSettings[rampIndex].step =
+ jsonConstrain(tag, obj, "step", 0.1, 10.0, 1.5);
rampIndex++;
}
ESP_LOGI(tag, "Loaded Ramp Lights settings...");
- }
- else
- {
+ } else {
ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath);
}
// ********** Fan ***********
JsonObject sensorJson = doc["t-sensor"];
- if (!sensorJson.isNull())
- {
+ if (!sensorJson.isNull()) {
sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true);
- sys_settings.tSensorSettings.pwmIndex = jsonConstrain(tag, sensorJson, "relay", 0, 3, 3);
- sys_settings.tSensorSettings.setpoint1 = jsonConstrain(tag, sensorJson, "sp1", 50.0, 100.0, 80.0);
- sys_settings.tSensorSettings.setpoint2 = jsonConstrain(tag, sensorJson, "sp2", 60.0, 110.0, 90.0);
- sys_settings.tSensorSettings.fanPower1 = jsonConstrain(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
- sys_settings.tSensorSettings.fanPower2 = jsonConstrain(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0);
- sys_settings.tSensorSettings.hyst = jsonConstrain(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
- sys_settings.tSensorSettings.intervalMs = jsonConstrain(tag, sensorJson, "interval", 1000, 30000, 5000);
+ sys_settings.tSensorSettings.pwmIndex =
+ jsonConstrain(tag, sensorJson, "relay", 0, 3, 3);
+ sys_settings.tSensorSettings.setpoint1 =
+ jsonConstrain(tag, sensorJson, "sp1", 50.0, 100.0, 80.0);
+ sys_settings.tSensorSettings.setpoint2 =
+ jsonConstrain(tag, sensorJson, "sp2", 60.0, 110.0, 90.0);
+ sys_settings.tSensorSettings.fanPower1 =
+ jsonConstrain(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
+ sys_settings.tSensorSettings.fanPower2 =
+ jsonConstrain(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0);
+ sys_settings.tSensorSettings.hyst =
+ jsonConstrain(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
+ sys_settings.tSensorSettings.intervalMs =
+ jsonConstrain(tag, sensorJson, "interval", 1000, 30000, 5000);
ESP_LOGI(tag, "Loaded TSensor settings...");
- ESP_LOGI(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1, sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
- }
- else
- {
+ ESP_LOGI(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1,
+ sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
+ } else {
ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath);
}
// ********** RGB Strips ***********
JsonArray stripsJsonArray = doc["strips"];
- if (!stripsJsonArray.isNull())
- {
+ if (!stripsJsonArray.isNull()) {
int stripIndex = 0;
- for (JsonObject obj : stripsJsonArray)
- {
+ for (JsonObject obj : stripsJsonArray) {
if (stripIndex >= 2)
break;
- sys_settings.ledStripSettings[stripIndex]->enabled = jsonConstrainBool(tag, obj, "en", true);
- sys_settings.ledStripSettings[stripIndex]->size = jsonConstrain(tag, obj, "size", 1, 250, 25);
- sys_settings.ledStripSettings[stripIndex]->chip = jsonConstrainString(tag, obj, "chip", "WS2812B");
- sys_settings.ledStripSettings[stripIndex]->rgbOrder = jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
- sys_settings.ledStripSettings[stripIndex]->shift = jsonConstrain(tag, obj, "shift", -250, 250, 0);
- sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain(tag, obj, "offset", -250, 250, 0);
- sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain(tag, obj, "bright", 5, 255, 200);
- sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
- sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
- sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain(tag, obj, "core", 0, 1, 0);
+ if (sys_settings.ledStripSettings[stripIndex]) {
+ sys_settings.ledStripSettings[stripIndex]->enabled =
+ jsonConstrainBool(tag, obj, "en", true);
+ sys_settings.ledStripSettings[stripIndex]->size =
+ jsonConstrain(tag, obj, "size", 1, 250, 25);
+ sys_settings.ledStripSettings[stripIndex]->chip =
+ jsonConstrainString(tag, obj, "chip", "WS2812B");
+ sys_settings.ledStripSettings[stripIndex]->rgbOrder =
+ jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
+ sys_settings.ledStripSettings[stripIndex]->shift =
+ jsonConstrain(tag, obj, "shift", -250, 250, 0);
+ sys_settings.ledStripSettings[stripIndex]->offset =
+ jsonConstrain(tag, obj, "offset", -250, 250, 0);
+ sys_settings.ledStripSettings[stripIndex]->bright =
+ jsonConstrain(tag, obj, "bright", 5, 255, 200);
+ sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
+ sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
+ sys_settings.ledStripSettings[stripIndex]->core =
+ jsonConstrain(tag, obj, "core", 0, 1, 0);
+ } else {
+ ESP_LOGW(tag, "ledStripSettings[%d] is null, skipping config", stripIndex);
+ }
stripIndex++;
}
sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1;
sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2;
ESP_LOGI(tag, "Loaded LED Strip settings...");
- }
- else
- {
+ } else {
ESP_LOGE(tag, "Error!, %s key: strips not found..");
}
// ********** BLE ***********
JsonObject bleJson = doc["ble"];
- if (!bleJson.isNull())
- {
+ if (!bleJson.isNull()) {
sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true);
sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS");
ESP_LOGI(tag, "Loaded BLE settings...");
- }
- else
- {
+ } else {
ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath);
}
@@ -255,14 +279,11 @@ void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
*/
}
-
-void Init_ADC(void)
-{
+void Init_ADC(void) {
// Configure ADC
analogReadResolution(12); // 12-bit ADC
analogSetAttenuation(ADC_11db);
- if (sys_settings.boardPins.adc1 >= 0)
- {
+ if (sys_settings.boardPins.adc1 >= 0) {
analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db);
}
@@ -271,35 +292,26 @@ void Init_ADC(void)
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars);
// Check calibration success
- if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK)
- {
+ if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
ESP_LOGI(tag, "ADC calibration: Using Two Point values from eFuse");
- }
- else if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK)
- {
+ } else if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse");
- }
- else
- {
+ } else {
ESP_LOGW(tag, "ADC calibration: Using default reference voltage");
}
}
-
-float readBoardInputVoltage(void)
-{
+float readBoardInputVoltage(void) {
const int SAMPLES = 64;
uint32_t reading = 0;
- if (sys_settings.boardPins.adc1 < 0)
- {
+ if (sys_settings.boardPins.adc1 < 0) {
ESP_LOGE(tag, "ADC Pin not valid");
return 0.0;
}
// Multiple readings for averaging
- for (int i = 0; i < SAMPLES; i++)
- {
+ for (int i = 0; i < SAMPLES; i++) {
reading += analogRead(sys_settings.boardPins.adc1);
delayMicroseconds(50); // Small delay between samples
}
@@ -311,8 +323,15 @@ float readBoardInputVoltage(void)
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
voltage_mv = esp_adc_cal_raw_to_voltage(reading, &adc_chars);
- // Scale to 12V range
- float voltage = (voltage_mv / 1000.0f) * (10470.0f / 470.0f);
+ // Voltage divider: R1 = 10k (series to input), R2 = 470 (to ground). Measurement is across R2.
+ // Vin = Vout * (R1 + R2) / R2
+ const float R1 = 10000.0f;
+ const float R2 = 470.0f;
+ const float dividerFactor = (R1 + R2) / R2;
- return voltage;
+ // Convert mV to V and apply divider factor
+ float vout = voltage_mv / 1000.0f;
+ float vin = vout * dividerFactor;
+
+ return vin;
}
\ No newline at end of file
diff --git a/src/my_oled.cpp b/src/my_oled.cpp
index 1b8de4d..8857b90 100644
--- a/src/my_oled.cpp
+++ b/src/my_oled.cpp
@@ -1,24 +1,25 @@
#include "my_oled.h"
-#include
#include
+#include
-static const char* tag = "oled";
-Adafruit_SSD1306 *oled;
+static const char *tag = "oled";
+Adafruit_SSD1306 *oled = nullptr;
-void Init_OLED(uint8_t width, uint8_t height, uint8_t mosiPin, uint8_t sckPin, uint8_t dcPin, uint8_t rstPin, uint8_t csPin)
-{
- oled = new Adafruit_SSD1306(width, height, mosiPin, sckPin, dcPin, rstPin, csPin);
+void Init_OLED(uint8_t width, uint8_t height, uint8_t mosiPin, uint8_t sckPin, uint8_t dcPin,
+ uint8_t rstPin, uint8_t csPin) {
+ oled = new Adafruit_SSD1306(width, height, mosiPin, sckPin, dcPin, rstPin, csPin);
- if(!oled->begin(SSD1306_SWITCHCAPVCC)) {
- ESP_LOGE(tag, "SSD1306 allocation failed");
- for(;;); // Don't proceed, loop forever
- }
+ if (!oled->begin(SSD1306_SWITCHCAPVCC)) {
+ ESP_LOGE(tag, "SSD1306 allocation failed");
+ for (;;)
+ ; // Don't proceed, loop forever
+ }
- oled_ShowInfo();
+ oled_ShowInfo();
}
-void oled_ShowInfo(void){
- //if(sysProps.oledEnabled) {
+void oled_ShowInfo(void) {
+ // if(sysProps.oledEnabled) {
oled->display();
vTaskDelay(2000); // Pause for 2 seconds
@@ -37,89 +38,89 @@ void oled_ShowInfo(void){
// drawing operations and then update the screen all at once by calling
// display.display(). These examples demonstrate both approaches...
- //testdrawline(*oled); // Draw many lines
+ // testdrawline(*oled); // Draw many lines
- //testdrawrect(*oled); // Draw rectangles (outlines)
+ // testdrawrect(*oled); // Draw rectangles (outlines)
- testdrawline(oled); // Draw many lines
+ testdrawline(oled); // Draw many lines
- testdrawrect(oled); // Draw rectangles (outlines)
- //}
+ testdrawrect(oled); // Draw rectangles (outlines)
+ //}
}
-void testdrawline(Adafruit_SSD1306* display) {
- //if(sysProps.oledEnabled) {
+void testdrawline(Adafruit_SSD1306 *display) {
+ // if(sysProps.oledEnabled) {
int16_t i;
display->clearDisplay(); // Clear display buffer
- for(i=0; iwidth(); i+=4) {
- display->drawLine(0, 0, i, display->height()-1, SSD1306_WHITE);
- display->display(); // Update screen with each newly-drawn line
- vTaskDelay(1);
+ for (i = 0; i < display->width(); i += 4) {
+ display->drawLine(0, 0, i, display->height() - 1, SSD1306_WHITE);
+ display->display(); // Update screen with each newly-drawn line
+ vTaskDelay(1);
}
- for(i=0; iheight(); i+=4) {
- display->drawLine(0, 0, display->width()-1, i, SSD1306_WHITE);
- display->display();
- vTaskDelay(1);
+ for (i = 0; i < display->height(); i += 4) {
+ display->drawLine(0, 0, display->width() - 1, i, SSD1306_WHITE);
+ display->display();
+ vTaskDelay(1);
}
vTaskDelay(250);
display->clearDisplay();
- for(i=0; iwidth(); i+=4) {
- display->drawLine(0, display->height()-1, i, 0, SSD1306_WHITE);
- display->display();
- vTaskDelay(1);
+ for (i = 0; i < display->width(); i += 4) {
+ display->drawLine(0, display->height() - 1, i, 0, SSD1306_WHITE);
+ display->display();
+ vTaskDelay(1);
}
- for(i=display->height()-1; i>=0; i-=4) {
- display->drawLine(0, display->height()-1, display->width()-1, i, SSD1306_WHITE);
- display->display();
- vTaskDelay(1);
+ for (i = display->height() - 1; i >= 0; i -= 4) {
+ display->drawLine(0, display->height() - 1, display->width() - 1, i, SSD1306_WHITE);
+ display->display();
+ vTaskDelay(1);
}
vTaskDelay(250);
display->clearDisplay();
- for(i=display->width()-1; i>=0; i-=4) {
- display->drawLine(display->width()-1, display->height()-1, i, 0, SSD1306_WHITE);
- display->display();
- vTaskDelay(1);
+ for (i = display->width() - 1; i >= 0; i -= 4) {
+ display->drawLine(display->width() - 1, display->height() - 1, i, 0, SSD1306_WHITE);
+ display->display();
+ vTaskDelay(1);
}
- for(i=display->height()-1; i>=0; i-=4) {
- display->drawLine(display->width()-1, display->height()-1, 0, i, SSD1306_WHITE);
- display->display();
- vTaskDelay(1);
+ for (i = display->height() - 1; i >= 0; i -= 4) {
+ display->drawLine(display->width() - 1, display->height() - 1, 0, i, SSD1306_WHITE);
+ display->display();
+ vTaskDelay(1);
}
vTaskDelay(250);
display->clearDisplay();
- for(i=0; iheight(); i+=4) {
- display->drawLine(display->width()-1, 0, 0, i, SSD1306_WHITE);
- display->display();
- vTaskDelay(1);
+ for (i = 0; i < display->height(); i += 4) {
+ display->drawLine(display->width() - 1, 0, 0, i, SSD1306_WHITE);
+ display->display();
+ vTaskDelay(1);
}
- for(i=0; iwidth(); i+=4) {
- display->drawLine(display->width()-1, 0, i, display->height()-1, SSD1306_WHITE);
- display->display();
- vTaskDelay(1);
+ for (i = 0; i < display->width(); i += 4) {
+ display->drawLine(display->width() - 1, 0, i, display->height() - 1, SSD1306_WHITE);
+ display->display();
+ vTaskDelay(1);
}
vTaskDelay(2000); // Pause for 2 seconds
- //}
+ //}
}
-void testdrawrect(Adafruit_SSD1306* display) {
- //if(sysProps.oledEnabled) {
+void testdrawrect(Adafruit_SSD1306 *display) {
+ // if(sysProps.oledEnabled) {
display->clearDisplay();
- for(int16_t i=0; iheight()/2; i+=2) {
- display->drawRect(i, i, display->width()-2*i, display->height()-2*i, SSD1306_WHITE);
- display->display(); // Update screen with each newly-drawn rectangle
- vTaskDelay(1);
+ for (int16_t i = 0; i < display->height() / 2; i += 2) {
+ display->drawRect(i, i, display->width() - 2 * i, display->height() - 2 * i, SSD1306_WHITE);
+ display->display(); // Update screen with each newly-drawn rectangle
+ vTaskDelay(1);
}
vTaskDelay(2000);
- //}
+ //}
}
\ No newline at end of file
diff --git a/src/my_tsensor.cpp b/src/my_tsensor.cpp
index 7cbf218..6421e9b 100644
--- a/src/my_tsensor.cpp
+++ b/src/my_tsensor.cpp
@@ -21,7 +21,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
// Initialize the temperature sensor once with the provided I2C address
if (tSensor == nullptr) {
tSensor = new TI_TMP102_Compatible(addr);
- ESP_LOGI(tag, "TSensor initialized at I2C addr 0x%02X", addr);
+ ESP_LOGD(tag, "TSensor initialized at I2C addr 0x%02X", addr);
} else {
ESP_LOGW(tag, "TSensor already initialized; ignoring re-init request (addr 0x%02X)", addr);
}
@@ -77,7 +77,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
// Fan is off - check if we should turn it on
if (temperature >= sp1) {
fanIsOn = true;
- ESP_LOGI(tag, "Fan turning ON - temp %.2f >= setpoint1 %.2f", temperature, sp1);
+ ESP_LOGD(tag, "Fan turning ON - temp %.2f >= setpoint1 %.2f", temperature, sp1);
} else {
newDuty = 0.0f; // Stay off
}
@@ -86,7 +86,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
if (temperature < (sp1 - hyst)) {
fanIsOn = false;
newDuty = 0.0f;
- ESP_LOGI(tag, "Fan turning OFF - temp %.2f < (setpoint1 - hyst) %.2f", temperature, sp1 - hyst);
+ ESP_LOGD(tag, "Fan turning OFF - temp %.2f < (setpoint1 - hyst) %.2f", temperature, sp1 - hyst);
}
}
@@ -105,7 +105,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
float tempRatio = (temperature - sp1) / tempRange;
newDuty = fp1 + (tempRatio * powerRange);
- ESP_LOGV(tag, "Linear scaling: temp=%.2f, ratio=%.3f, duty=%.2f", temperature, tempRatio, newDuty);
+ ESP_LOGD(tag, "Linear scaling: temp=%.2f, ratio=%.3f, duty=%.2f", temperature, tempRatio, newDuty);
}
// Ensure duty is within bounds
@@ -115,7 +115,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
// Apply new duty cycle if changed (with small tolerance to avoid constant updates)
if (fabs(currentDuty - newDuty) > 0.1f) {
pwmOut->setOutput(newDuty);
- ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f%% (on=%s)", temperature, newDuty, fanIsOn ? "true" : "false");
+ ESP_LOGD(tag, "Board T: %.2f F, Fan -> %.2f%% (on=%s)", temperature, newDuty, fanIsOn ? "true" : "false");
}
}
diff --git a/src/my_wifi.cpp b/src/my_wifi.cpp
index ec00a04..5cbef5c 100644
--- a/src/my_wifi.cpp
+++ b/src/my_wifi.cpp
@@ -1,30 +1,28 @@
#include "my_wifi.h"
-#include
-#include
-#include
+#include "AppUpgrade.h"
+#include "common/fileSystem.h"
+#include "esp_log.h"
+#include "global.h"
+#include "jsonconstrain.h"
+#include "my_board.h"
+#include "my_buzzer.h"
+#include
+#include
#include
#include
-#include
-#include "esp_log.h"
#include
#include
#include
-#include "common/fileSystem.h"
-#include "global.h"
-#include "my_buzzer.h"
#include
-#include
-#include "jsonconstrain.h"
-#include "AppUpgrade.h"
-#include "my_board.h"
+#include
+#include
+#include
static const char *tag = "WIFI";
volatile bool InternetAvailable;
AsyncWebServer webServer(80);
AsyncEventSource eventUpgradeProgress("/upgrade-progress");
-// DNSServer *dnsServer;
-// #define DNS_PORT 53
String client_ssid;
String client_pass;
@@ -39,10 +37,10 @@ IPAddress gateway(192, 168, 10, 1);
IPAddress subnet(255, 255, 255, 0);
// for file manager page
-String filesDropdownOptions((char *)0);
-String dirDropdownOptions((char *)0);
-String savePath((char *)0); // needed for storing file when editing a file
-String savePathInput((char *)0);
+String filesDropdownOptions;
+String dirDropdownOptions;
+String savePath; // needed for storing file when editing a file
+String savePathInput;
const char *http_username = "admin";
const char *http_password = "12345678";
const char *param_delete_path = "delete-path";
@@ -67,11 +65,9 @@ static const uint8_t MAX_ATTEMPTS = 10;
static SemaphoreHandle_t wifiMutex = nullptr;
volatile bool wifi_task_running = false;
-void Wifi_Init()
-{
+void Wifi_Init() {
// Initialize LittleFS
- if (!LittleFS.begin(true))
- {
+ if (!LittleFS.begin(true)) {
ESP_LOGE(tag, "LittleFS mount failed");
return;
}
@@ -86,8 +82,7 @@ void Wifi_Init()
// Configure and start AP
WiFi.softAPConfig(local_IP, gateway, subnet);
- if (!WiFi.softAP(ap_ssid, ap_pass))
- {
+ if (!WiFi.softAP(ap_ssid, ap_pass)) {
ESP_LOGE(tag, "AP start failed");
return;
}
@@ -109,12 +104,10 @@ void Wifi_Init()
// Wifi_Scan_for_Networks();
}
-void Wifi_Load_Settings(String path)
-{
+void Wifi_Load_Settings(String path) {
// Load WiFi settings
File file = LittleFS.open(path, "r");
- if (!file)
- {
+ if (!file) {
ESP_LOGE(tag, "Error opening %s", path.c_str());
return;
}
@@ -123,49 +116,43 @@ void Wifi_Load_Settings(String path)
DeserializationError error = deserializeJson(doc, file);
file.close();
- if (error)
- {
+ if (error) {
ESP_LOGE(tag, "Failed to deserialize %s", path.c_str());
return;
}
JsonObject wifiJson = doc.as();
- if (wifiJson.isNull())
- {
+ if (wifiJson.isNull()) {
ESP_LOGE(tag, "%s is empty", path.c_str());
return;
}
// Load AP settings
JsonObject apJson = wifiJson["wifi-ap"];
- if (!apJson.isNull())
- {
+ if (!apJson.isNull()) {
ap_ssid = jsonConstrainString(tag, apJson, "ssid", "ATA-AP");
ap_pass = jsonConstrainString(tag, apJson, "pass", "12345678");
local_IP.fromString(jsonConstrainString(tag, apJson, "ip", "192.168.10.1"));
gateway.fromString(jsonConstrainString(tag, apJson, "gateway", "192.168.10.1"));
subnet.fromString(jsonConstrainString(tag, apJson, "subnet", "255.255.255.0"));
- char macSuffix[13] = {0}; // Just need 2 chars + null terminator
+ char macSuffix[13] = {0}; // Just need 2 chars + null terminator
get_chip_mac(macSuffix, sizeof(macSuffix)); // Only get first 2 chars of MAC
- ap_ssid += "-"; // Add a separator
- ap_ssid += macSuffix[0]; // Add first character
- ap_ssid += macSuffix[1]; // Add second character
+ ap_ssid += "-"; // Add a separator
+ ap_ssid += macSuffix[0]; // Add first character
+ ap_ssid += macSuffix[1]; // Add second character
}
// Load Client settings
JsonObject clientJson = wifiJson["wifi-client"];
- if (!apJson.isNull())
- {
+ if (!apJson.isNull()) {
client_ssid = jsonConstrainString(tag, clientJson, "ssid", "none");
client_pass = jsonConstrainString(tag, clientJson, "pass", "12345678");
}
}
-bool StartWifiConnectTask(String ssid = "", String pass = "")
-{
- if (ssid.isEmpty() || pass.length() < 8)
- {
+bool StartWifiConnectTask(String ssid = "", String pass = "") {
+ if (ssid.isEmpty() || pass.length() < 8) {
ESP_LOGE(tag, "Invalid SSID or password");
return false;
}
@@ -174,27 +161,22 @@ bool StartWifiConnectTask(String ssid = "", String pass = "")
if (wifiMutex == nullptr) {
wifiMutex = xSemaphoreCreateMutex();
}
-
+
// Take mutex with timeout
if (xSemaphoreTake(wifiMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
- if (!wifi_task_running)
- {
+ if (!wifi_task_running) {
client_ssid = ssid;
client_pass = pass;
- if (Wifi_Task_Handle == NULL)
- {
+ if (Wifi_Task_Handle == NULL) {
ESP_LOGI(tag, "Creating WiFi task");
- xTaskCreatePinnedToCore(Wifi_ConnectTask, "Wifi_Task", 1024 * 6, NULL, 1, &Wifi_Task_Handle, 0);
+ xTaskCreatePinnedToCore(Wifi_ConnectTask, "Wifi_Task", 1024 * 6, NULL, 1,
+ &Wifi_Task_Handle, 0);
xSemaphoreGive(wifiMutex);
return true;
- }
- else
- {
+ } else {
ESP_LOGI(tag, "WiFi task already running");
}
- }
- else
- {
+ } else {
ESP_LOGE(tag, "Task already running");
}
xSemaphoreGive(wifiMutex);
@@ -205,16 +187,14 @@ bool StartWifiConnectTask(String ssid = "", String pass = "")
return false;
}
-void Wifi_ConnectTask(void *parameter)
-{
+void Wifi_ConnectTask(void *parameter) {
static const char *tag = "Wifi_Task";
wifi_task_running = true;
-
+
// Register task with watchdog to prevent system hangs
esp_task_wdt_add(NULL);
-
- if (WiFi.status() != WL_CONNECTED || client_ssid != WiFi.SSID())
- {
+
+ if (WiFi.status() != WL_CONNECTED || client_ssid != WiFi.SSID()) {
ESP_LOGI(tag, "Connecting to: %s", client_ssid.c_str());
// Disconnect and connect to new network
@@ -224,13 +204,11 @@ void Wifi_ConnectTask(void *parameter)
// Wait for connection
uint8_t attempts = 0;
- while (WiFi.status() != WL_CONNECTED && attempts < MAX_ATTEMPTS)
- {
+ while (WiFi.status() != WL_CONNECTED && attempts < MAX_ATTEMPTS) {
// Reset watchdog timer to prevent timeouts during connection attempts
esp_task_wdt_reset();
-
- switch (WiFi.status())
- {
+
+ switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
ESP_LOGW(tag, "SSID not found: %s", client_ssid.c_str());
break;
@@ -245,20 +223,16 @@ void Wifi_ConnectTask(void *parameter)
}
// Check if connected
- if (WiFi.status() == WL_CONNECTED)
- {
+ if (WiFi.status() == WL_CONNECTED) {
ESP_LOGI(tag, "Connected to %s", client_ssid.c_str());
WiFi.setAutoReconnect(true);
- if (!Wifi_Save_Credentials("/system/wifi.json"))
- {
+ if (!Wifi_Save_Credentials("/system/wifi.json")) {
ESP_LOGW(tag, "Failed to save credentials");
}
Wifi_Check_Internet();
- }
- else
- {
+ } else {
ESP_LOGE(tag, "Failed to connect after %d attempts", MAX_ATTEMPTS);
}
}
@@ -267,20 +241,20 @@ void Wifi_ConnectTask(void *parameter)
// Unregister from watchdog before deletion
esp_task_wdt_delete(NULL);
-
+
Wifi_Task_Handle = NULL;
wifi_task_running = false;
vTaskDelete(NULL);
}
-void Wifi_Check_Internet()
-{
+void Wifi_Check_Internet() {
// Check for internet connection with multiple fallback servers
- const char *hosts[] = {"8.8.8.8", "1.1.1.1", "208.67.222.222"}; // Google DNS, Cloudflare DNS, OpenDNS
+ const char *hosts[] = {"8.8.8.8", "1.1.1.1",
+ "208.67.222.222"}; // Google DNS, Cloudflare DNS, OpenDNS
const int num_hosts = sizeof(hosts) / sizeof(hosts[0]);
-
+
InternetAvailable = false;
-
+
// Try pinging each host
for (int i = 0; i < num_hosts; i++) {
if (Ping.ping(hosts[i], 1)) {
@@ -291,23 +265,20 @@ void Wifi_Check_Internet()
// Small delay between ping attempts
vTaskDelay(pdMS_TO_TICKS(100));
}
-
+
if (!InternetAvailable) {
ESP_LOGW(tag, "No internet connection after trying multiple DNS servers");
}
}
-bool Wifi_Save_Credentials(String path)
-{
+bool Wifi_Save_Credentials(String path) {
// Load existing JSON
JsonDocument doc;
File readFile = LittleFS.open(path, "r");
- if (readFile)
- {
+ if (readFile) {
DeserializationError error = deserializeJson(doc, readFile);
readFile.close();
- if (error)
- {
+ if (error) {
ESP_LOGE(tag, "Failed to parse existing JSON");
return false;
}
@@ -320,15 +291,13 @@ bool Wifi_Save_Credentials(String path)
// Save updated JSON
File writeFile = LittleFS.open(path, "w");
- if (!writeFile)
- {
+ if (!writeFile) {
ESP_LOGE(tag, "Error opening %s for writing", path.c_str());
return false;
}
// Serialize JSON with pretty formatting
- if (serializeJsonPretty(doc, writeFile) == 0)
- {
+ if (serializeJsonPretty(doc, writeFile) == 0) {
ESP_LOGE(tag, "Failed to write JSON to file");
writeFile.close();
return false;
@@ -340,14 +309,13 @@ bool Wifi_Save_Credentials(String path)
/**
* Scans for available WiFi networks and stores the results in JSON format
- *
+ *
* Updates scanStatus global: 0=none, 1=scanning, 2=complete, -1=error
* Sets scanInProgress flag during operation
* Populates networkList with JSON formatted scan results
*/
-void Wifi_Scan_for_Networks()
-{
- static const char* tag = "WiFiScan";
+void Wifi_Scan_for_Networks() {
+ static const char *tag = "WiFiScan";
const uint32_t SCAN_TIMEOUT_MS = 15000; // 15 second timeout for scan
// Protect against concurrent scans
@@ -355,83 +323,101 @@ void Wifi_Scan_for_Networks()
ESP_LOGW(tag, "WiFi scan already in progress");
return;
}
-
+
// Use mutex for thread safety if available
bool useMutex = (wifiMutex != nullptr);
if (useMutex && xSemaphoreTake(wifiMutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
ESP_LOGE(tag, "Failed to acquire mutex - WiFi operation in progress");
return;
}
-
+
scanInProgress = true;
scanStatus = 1; // Scanning
ESP_LOGI(tag, "Starting WiFi network scan");
-
+
// Start scan (async=false, show_hidden=false)
WiFi.scanNetworks(false, false);
-
+
// Wait for scan with timeout
uint32_t startTime = millis();
- while (WiFi.scanComplete() == WIFI_SCAN_RUNNING)
- {
+ while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) {
// Check for timeout
if (millis() - startTime > SCAN_TIMEOUT_MS) {
ESP_LOGE(tag, "WiFi scan timeout after %u ms", SCAN_TIMEOUT_MS);
scanInProgress = false;
scanStatus = -1; // Error
- if (useMutex) xSemaphoreGive(wifiMutex);
+ if (useMutex)
+ xSemaphoreGive(wifiMutex);
return;
}
-
- // Reset watchdog if needed
- #ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
+
+// Reset watchdog if needed
+#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
esp_task_wdt_reset();
- #endif
-
+#endif
+
vTaskDelay(pdMS_TO_TICKS(100)); // Wait for scan to complete
}
// Get scan results
networkCount = WiFi.scanComplete();
- if (networkCount >= 0)
- {
+ if (networkCount >= 0) {
ESP_LOGI(tag, "WiFi scan complete, found %d networks", networkCount);
scanStatus = 2; // Complete
-
+
// Create JSON document with appropriate capacity
JsonDocument doc;
doc.clear();
JsonArray networks = doc["networks"].to();
- for (int i = 0; i < networkCount; i++)
- {
+ for (int i = 0; i < networkCount; i++) {
auto network = networks.add();
-
+
// Basic network info
network["ssid"] = WiFi.SSID(i);
network["rssi"] = WiFi.RSSI(i);
network["channel"] = WiFi.channel(i);
-
+
// Security details
wifi_auth_mode_t encType = WiFi.encryptionType(i);
network["encryption"] = encType != WIFI_AUTH_OPEN;
-
+
// Add detailed encryption type
- const char* encTypeStr = "unknown";
+ const char *encTypeStr = "unknown";
switch (encType) {
- case WIFI_AUTH_OPEN: encTypeStr = "open"; break;
- case WIFI_AUTH_WEP: encTypeStr = "WEP"; break;
- case WIFI_AUTH_WPA_PSK: encTypeStr = "WPA_PSK"; break;
- case WIFI_AUTH_WPA2_PSK: encTypeStr = "WPA2_PSK"; break;
- case WIFI_AUTH_WPA_WPA2_PSK: encTypeStr = "WPA_WPA2_PSK"; break;
- case WIFI_AUTH_WPA2_ENTERPRISE: encTypeStr = "WPA2_ENTERPRISE"; break;
- case WIFI_AUTH_WPA3_PSK: encTypeStr = "WPA3_PSK"; break;
- case WIFI_AUTH_WPA2_WPA3_PSK: encTypeStr = "WPA2_WPA3_PSK"; break;
- case WIFI_AUTH_WAPI_PSK: encTypeStr = "WAPI_PSK"; break;
- default: encTypeStr = "unknown"; break;
+ case WIFI_AUTH_OPEN:
+ encTypeStr = "open";
+ break;
+ case WIFI_AUTH_WEP:
+ encTypeStr = "WEP";
+ break;
+ case WIFI_AUTH_WPA_PSK:
+ encTypeStr = "WPA_PSK";
+ break;
+ case WIFI_AUTH_WPA2_PSK:
+ encTypeStr = "WPA2_PSK";
+ break;
+ case WIFI_AUTH_WPA_WPA2_PSK:
+ encTypeStr = "WPA_WPA2_PSK";
+ break;
+ case WIFI_AUTH_WPA2_ENTERPRISE:
+ encTypeStr = "WPA2_ENTERPRISE";
+ break;
+ case WIFI_AUTH_WPA3_PSK:
+ encTypeStr = "WPA3_PSK";
+ break;
+ case WIFI_AUTH_WPA2_WPA3_PSK:
+ encTypeStr = "WPA2_WPA3_PSK";
+ break;
+ case WIFI_AUTH_WAPI_PSK:
+ encTypeStr = "WAPI_PSK";
+ break;
+ default:
+ encTypeStr = "unknown";
+ break;
}
network["security"] = encTypeStr;
-
+
// Add signal quality 0-100%
int rssi = WiFi.RSSI(i);
int rssiLimited = rssi < -100 ? -100 : (rssi > -50 ? -50 : rssi);
@@ -442,267 +428,295 @@ void Wifi_Scan_for_Networks()
// Serialize to the global variable
networkList.clear();
serializeJson(doc, networkList);
-
+
// Clean up scan results from memory
WiFi.scanDelete();
- }
- else
- {
+ } else {
ESP_LOGE(tag, "WiFi scan failed with error code: %d", networkCount);
scanStatus = -1; // Error
}
-
+
scanInProgress = false;
- if (useMutex) xSemaphoreGive(wifiMutex);
+ if (useMutex)
+ xSemaphoreGive(wifiMutex);
}
-void Setup_WebServer_Handlers(AsyncWebServer &server)
-{
+void Setup_WebServer_Handlers(AsyncWebServer &server) {
- server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
- { request->send(LittleFS, "/www/index.html", "text/html"); });
- server.on("/home", HTTP_GET, [](AsyncWebServerRequest *request)
- { sendHtmlFile("/www/home.html", request, HomeHtmlProcessor); });
- server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- if (!request->authenticate(http_username, http_password))
- {
- return request->requestAuthentication();
- }
- // sendHtmlFile("/www/setup.html", request, htmlProcessor);
- });
- server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- sendHtmlFile("/www/about.html", request, fileManagerHtmlProcessor);
- // request->send(LittleFS, "/www/about.html", "text/html");
- });
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
+ request->send(LittleFS, "/www/index.html", "text/html");
+ });
+ server.on("/home", HTTP_GET, [](AsyncWebServerRequest *request) {
+ sendHtmlFile("/www/home.html", request, HomeHtmlProcessor);
+ });
+ server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (!request->authenticate(http_username, http_password)) {
+ return request->requestAuthentication();
+ }
+ // sendHtmlFile("/www/setup.html", request, htmlProcessor);
+ });
+ server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request) {
+ sendHtmlFile("/www/about.html", request, fileManagerHtmlProcessor);
+ // request->send(LittleFS, "/www/about.html", "text/html");
+ });
// Wifi related handlers
- server.on("/wifi/connect", HTTP_POST, [](AsyncWebServerRequest *request)
- {
- // Input validation
- if (!request->hasParam("ssid", false, false) || !request->hasParam("pass", false, false)) {
- ESP_LOGE(tag, "Missing required parameters");
- request->send(400, "application/json", "{\"error\":\"Missing ssid or password\"}");
- return;
- }
+ server.on("/wifi/connect", HTTP_POST, [](AsyncWebServerRequest *request) {
+ // Input validation
+ if (!request->hasParam("ssid", false, false) || !request->hasParam("pass", false, false)) {
+ ESP_LOGE(tag, "Missing required parameters");
+ request->send(400, "application/json", "{\"error\":\"Missing ssid or password\"}");
+ return;
+ }
- // Get and store credentials
- String ssid = request->getParam("ssid", false, false)->value();
- String pass = request->getParam("pass", false, false)->value();
+ // Get and store credentials
+ String ssid = request->getParam("ssid", false, false)->value();
+ String pass = request->getParam("pass", false, false)->value();
- // Validate credentials
- if (ssid.length() < 1 || pass.length() < 8) {
- ESP_LOGE(tag, "Invalid credentials");
- request->send(400, "application/json", "{\"error\":\"Invalid credentials\"}");
- return;
- }
+ // Validate credentials
+ if (ssid.length() < 1 || pass.length() < 8) {
+ ESP_LOGE(tag, "Invalid credentials");
+ request->send(400, "application/json", "{\"error\":\"Invalid credentials\"}");
+ return;
+ }
- // Start connection
- StartWifiConnectTask(ssid, pass);
-
- ESP_LOGI(tag, "Starting connection to %s", client_ssid.c_str());
- request->send(200, "application/json", "{\"status\":\"connecting\"}"); });
- server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- String jsonStr = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
- "\",\"ip\":\"" + WiFi.localIP().toString() + "\"}";
- request->send(200, "application/json", jsonStr); });
- server.on("/wifi/scans", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- if(networkCount <= 0) {
- request->send(400, "application/json", "{\"error\":\"No scan results\"}");
- return;
- }
-
- request->send(200, "application/json", networkList); });
- server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- if(WiFi.getMode() == WIFI_MODE_APSTA){
- // TODO Disable navigation bar
- }
- //sendHtmlFile("/www/wifi.html", request, htmlProcessor);
- request->send(LittleFS, "/www/wifi.html", "text/html"); });
+ // Start connection
+ StartWifiConnectTask(ssid, pass);
+
+ ESP_LOGI(tag, "Starting connection to %s", client_ssid.c_str());
+ request->send(200, "application/json", "{\"status\":\"connecting\"}");
+ });
+ server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request) {
+ String jsonStr = "{\"status\":\"" +
+ String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
+ "\",\"ip\":\"" + WiFi.localIP().toString() + "\"}";
+ request->send(200, "application/json", jsonStr);
+ });
+ server.on("/wifi/scans", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (networkCount <= 0) {
+ request->send(400, "application/json", "{\"error\":\"No scan results\"}");
+ return;
+ }
+
+ request->send(200, "application/json", networkList);
+ });
+ server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (WiFi.getMode() == WIFI_MODE_APSTA) {
+ // TODO Disable navigation bar
+ }
+ // sendHtmlFile("/www/wifi.html", request, htmlProcessor);
+ request->send(LittleFS, "/www/wifi.html", "text/html");
+ });
// File Manager related handlers
- server.on("/files/upload", HTTP_POST, [](AsyncWebServerRequest *request)
- { request->send(200); }, handleFilesUpload_OnBody);
+ server.on(
+ "/files/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); },
+ handleFilesUpload_OnBody);
- server.on("/files/download", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- if (!request->hasParam("file")) {
- ESP_LOGE(tag, "Missing file parameter");
- request->send(400, "text/plain", "Missing file parameter");
- return;
- }
+ server.on("/files/download", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (!request->hasParam("file")) {
+ ESP_LOGE(tag, "Missing file parameter");
+ request->send(400, "text/plain", "Missing file parameter");
+ return;
+ }
- try {
String filename = uriDecode(request->getParam("file")->value());
+ // Prevent directory traversal
+ if (filename.indexOf("..") >= 0) {
+ ESP_LOGW(tag, "Rejected download with unsafe path: %s", filename.c_str());
+ request->send(400, "text/plain", "Invalid file path");
+ return;
+ }
+ if (!filename.startsWith("/")) filename = "/" + filename;
+
+ if (!LittleFS.exists(filename)) {
+ ESP_LOGE(tag, "File not found: %s", filename.c_str());
+ request->send(404, "text/plain", "File not found!");
+ return;
+ }
+
ESP_LOGI(tag, "Download request for: %s", filename.c_str());
request->send(LittleFS, filename, "application/octet-stream");
- }
- catch (const std::exception& e) {
- ESP_LOGE(tag, "Download failed: %s", e.what());
- request->send(404, "text/plain", "File not found!");
- } });
- server.on("/files/delete", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- static const char* tag = "DeleteHandler";
+ });
+ server.on("/files/delete", HTTP_GET, [](AsyncWebServerRequest *request) {
+ static const char *tag = "DeleteHandler";
- // Authentication check
- if (!request->authenticate(http_username, http_password)) {
- ESP_LOGW(tag, "Authentication failed for delete request");
- return request->requestAuthentication();
- }
+ // Authentication check
+ if (!request->authenticate(http_username, http_password)) {
+ ESP_LOGW(tag, "Authentication failed for delete request");
+ return request->requestAuthentication();
+ }
- // Parameter validation
- if (!request->hasParam(param_delete_path)) {
- ESP_LOGE(tag, "Missing delete path parameter");
- request->send(400, "text/plain", "Missing file path");
- return;
- }
+ // Parameter validation
+ if (!request->hasParam(param_delete_path)) {
+ ESP_LOGE(tag, "Missing delete path parameter");
+ request->send(400, "text/plain", "Missing file path");
+ return;
+ }
- // Get and validate filename
- String filename = uriDecode(request->getParam(param_delete_path)->value());
- if (filename == "choose") {
- request->redirect("/files");
- return;
- }
+ // Get and validate filename
+ String filename = uriDecode(request->getParam(param_delete_path)->value());
+ if (filename == "choose") {
+ request->redirect("/files");
+ return;
+ }
- // Ensure path starts with /
- if (!filename.startsWith("/")) {
- filename = "/" + filename;
- }
+ // Prevent directory traversal
+ if (filename.indexOf("..") >= 0) {
+ ESP_LOGW(tag, "Rejected delete with unsafe path: %s", filename.c_str());
+ request->send(400, "text/plain", "Invalid file path");
+ return;
+ }
- try {
- if (LittleFS.remove(filename.c_str())) {
- ESP_LOGI(tag, "Successfully deleted file: %s", filename.c_str());
- } else {
- ESP_LOGE(tag, "Failed to delete file: %s", filename.c_str());
- request->send(500, "text/plain", "Delete failed");
- return;
- }
- } catch (const std::exception& e) {
- ESP_LOGE(tag, "Exception during delete: %s", e.what());
- request->send(500, "text/plain", "Delete failed");
- return;
- }
+ // Ensure path starts with /
+ if (!filename.startsWith("/")) filename = "/" + filename;
+ while (filename.indexOf("//") >= 0) filename.replace("//", "/");
- request->redirect("/files"); });
- server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- static const char* tag = "EditHandler";
+ if (LittleFS.remove(filename.c_str())) {
+ ESP_LOGI(tag, "Successfully deleted file: %s", filename.c_str());
+ } else {
+ ESP_LOGE(tag, "Failed to delete file: %s", filename.c_str());
+ request->send(500, "text/plain", "Delete failed");
+ return;
+ }
- // Authentication check
- if (!request->authenticate(http_username, http_password)) {
- ESP_LOGW(tag, "Authentication failed");
- return request->requestAuthentication();
- }
+ request->redirect("/files");
+ });
+ server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request) {
+ static const char *tag = "EditHandler";
- // Parameter validation
- if (!request->hasParam(param_edit_path)) {
- ESP_LOGE(tag, "Missing edit path parameter");
- request->send(400, "text/plain", "Missing file path");
- return;
- }
+ // Authentication check
+ if (!request->authenticate(http_username, http_password)) {
+ ESP_LOGW(tag, "Authentication failed");
+ return request->requestAuthentication();
+ }
- // Get and decode filename
- String fileName = uriDecode(request->getParam(param_edit_path)->value());
- ESP_LOGI(tag, "Edit request for file: %s", fileName.c_str());
+ // Parameter validation
+ if (!request->hasParam(param_edit_path)) {
+ ESP_LOGE(tag, "Missing edit path parameter");
+ request->send(400, "text/plain", "Missing file path");
+ return;
+ }
- // Set save path
- savePath = (fileName == "new") ? "/new.txt" : fileName;
+ // Get and decode filename
+ String fileName = uriDecode(request->getParam(param_edit_path)->value());
+ ESP_LOGI(tag, "Edit request for file: %s", fileName.c_str());
- // Validate path
- if (!savePath.startsWith("/")) {
- savePath = "/" + savePath;
- }
+ // Set save path
+ savePath = (fileName == "new") ? "/new.txt" : fileName;
- ESP_LOGI(tag, "Save path set to: %s", savePath.c_str());
-
- try {
- sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor);
- } catch (const std::exception& e) {
- ESP_LOGE(tag, "Failed to send edit page: %s", e.what());
- request->send(500, "text/plain", "Internal server error");
- } });
- server.on("/files/save", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- if(!request->authenticate(http_username, http_password)){
- return request->requestAuthentication();
- }
- String inputMessage((char*)0);
- if (request->hasParam(param_edit_textarea)) {
- inputMessage = request->getParam(param_edit_textarea)->value();
- }
- if (request->hasParam(param_save_path)) {
- savePath = uriDecode(request->getParam(param_save_path)->value());
- }
- writeFile(LittleFS, savePath.c_str(), inputMessage.c_str());
+ // Validate path
+ if (!savePath.startsWith("/")) {
+ savePath = "/" + savePath;
+ }
- request->redirect("/files"); });
- server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- if(!request->authenticate(http_username, http_password)){
- return request->requestAuthentication();
- }
- sendHtmlFile("/www/files.html", request, fileManagerHtmlProcessor); });
+ ESP_LOGI(tag, "Save path set to: %s", savePath.c_str());
+
+ if (!sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor)) {
+ ESP_LOGE(tag, "Failed to send edit page: %s", "/www/edit.html");
+ request->send(500, "text/plain", "Internal server error");
+ }
+ });
+ server.on("/files/save", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (!request->authenticate(http_username, http_password)) {
+ return request->requestAuthentication();
+ }
+ String inputMessage;
+ if (request->hasParam(param_edit_textarea)) {
+ inputMessage = request->getParam(param_edit_textarea)->value();
+ }
+ if (request->hasParam(param_save_path)) {
+ savePath = uriDecode(request->getParam(param_save_path)->value());
+ if (savePath.indexOf("..") >= 0) {
+ ESP_LOGW(tag, "Rejected save with unsafe path: %s", savePath.c_str());
+ request->send(400, "text/plain", "Invalid file path");
+ return;
+ }
+ if (!savePath.startsWith("/")) savePath = "/" + savePath;
+ while (savePath.indexOf("//") >= 0) savePath.replace("//", "/");
+ }
+ writeFile(LittleFS, savePath.c_str(), inputMessage.c_str());
+
+ request->redirect("/files");
+ });
+ server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (!request->authenticate(http_username, http_password)) {
+ return request->requestAuthentication();
+ }
+ sendHtmlFile("/www/files.html", request, fileManagerHtmlProcessor);
+ });
// Lights related handlers
- server.on("/lights/settings", HTTP_GET, [](AsyncWebServerRequest *request)
- { request->send(200); });
- server.on("/lights/settings", HTTP_POST, [](AsyncWebServerRequest *request)
- { request->send(200); });
- server.on("/lights/animation", HTTP_POST, [](AsyncWebServerRequest *request)
- { request->send(200); });
- server.on("/lights/setpixel", HTTP_POST, [](AsyncWebServerRequest *request)
- { request->send(200); });
+ server.on("/lights/settings", HTTP_GET,
+ [](AsyncWebServerRequest *request) { request->send(200); });
+ server.on("/lights/settings", HTTP_POST,
+ [](AsyncWebServerRequest *request) { request->send(200); });
+ server.on("/lights/animation", HTTP_POST,
+ [](AsyncWebServerRequest *request) { request->send(200); });
+ server.on("/lights/setpixel", HTTP_POST,
+ [](AsyncWebServerRequest *request) { request->send(200); });
// System and LED related handlers
- server.on("/system/summary", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}";
- request->send(200, "application/json", response); });
- server.on("/leds/settings", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- /*
- //CreateSysSummmaryPacket(doc);
- String summary;
- serializeJson(jsDoc, summary);
- request->send(200, "application/json", summary);
- */
- String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}";
- request->send(200, "application/json", response); });
- server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request)
- {
- String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}";
- request->send(200, "application/json", response); });
+ server.on("/system/summary", HTTP_GET, [](AsyncWebServerRequest *request) {
+ String response = "{\"status\":\"" +
+ String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
+ "\"}";
+ request->send(200, "application/json", response);
+ });
+ server.on("/leds/settings", HTTP_GET, [](AsyncWebServerRequest *request) {
+ /*
+ //CreateSysSummmaryPacket(doc);
+ String summary;
+ serializeJson(jsDoc, summary);
+ request->send(200, "application/json", summary);
+ */
+ String response = "{\"status\":\"" +
+ String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
+ "\"}";
+ request->send(200, "application/json", response);
+ });
+ server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request) {
+ String response = "{\"status\":\"" +
+ String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
+ "\"}";
+ request->send(200, "application/json", response);
+ });
// LightStik related handlers
- server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}";
- request->send(200, "application/json", response); });
- server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request)
- {
- String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}";
- request->send(200, "application/json", response); });
- server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request)
- {
- String response = "{\"status\":\"" + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + "\"}";
- request->send(200, "application/json", response); });
+ server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request) {
+ String response = "{\"status\":\"" +
+ String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
+ "\"}";
+ request->send(200, "application/json", response);
+ });
+ server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request) {
+ String response = "{\"status\":\"" +
+ String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
+ "\"}";
+ request->send(200, "application/json", response);
+ });
+ server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request) {
+ String response = "{\"status\":\"" +
+ String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
+ "\"}";
+ request->send(200, "application/json", response);
+ });
// Firmware Update Handlers
- server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request)
- {
+ server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request) {
// Ensure updateUrl is loaded (function resides in AppUpgrade.cpp)
- loadUpdateJson();
- // Pass nullptr bucket to use internally loaded default + subsequently set base via setBaseUrl if needed
+ if (!loadUpdateJson()) {
+ ESP_LOGE(tag, "Failed to load update.json for /upgrade/check");
+ }
+ // Pass nullptr bucket to use internally loaded default + subsequently set base via
+ // setBaseUrl if needed
AppUpdater updater(LittleFS, localVersion, nullptr, "update.json", "firmware.bin");
// If a dynamic URL was loaded, override base
extern String updateUrl; // declared in AppUpgrade.cpp
- if(updateUrl.length()) updater.setBaseUrl(updateUrl);
- // checkManifest() does not return a bool; capture its result (type-dependent) instead of using it in a boolean expression
+ if (updateUrl.length())
+ updater.setBaseUrl(updateUrl);
+ // checkManifest() does not return a bool; capture its result (type-dependent) instead of
+ // using it in a boolean expression
auto manifestResult = updater.checkManifest();
// TODO: inspect manifestResult for success/failure once its API is known
otaVersion = updater.otaVersion;
@@ -710,120 +724,115 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
JsonDocument doc;
doc["currentVersion"] = localVersion.toString();
- doc["latestVersion"] = otaVersion.toString();
+ doc["latestVersion"] = otaVersion.toString();
doc["updateAvailable"] = avail;
-
+
String response;
serializeJson(doc, response);
- request->send(200, "application/json", response); });
+ request->send(200, "application/json", response);
+ });
// Start update process
- server.on("/upgrade/start", HTTP_POST, [](AsyncWebServerRequest *request)
- {
- startFirmwareUpdateTask(&eventUpgradeProgress);
- request->send(200); });
- eventUpgradeProgress.onConnect([](AsyncEventSourceClient *client)
- {
- if (client->lastId())
- {
- Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
- }
- // send event with message "hello!", id current millis
- // and set reconnect delay to 1 second
- // client->send("hello!", NULL, millis(), 10000);
- });
- server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request)
- {
- if(!request->authenticate(http_username, http_password)){
- return request->requestAuthentication();
- }
- request->send(LittleFS, "/www/upgrade.html", "text/html"); });
+ server.on("/upgrade/start", HTTP_POST, [](AsyncWebServerRequest *request) {
+ startFirmwareUpdateTask(&eventUpgradeProgress);
+ request->send(200);
+ });
+ eventUpgradeProgress.onConnect([](AsyncEventSourceClient *client) {
+ if (client->lastId()) {
+ Serial.printf("Client reconnected! Last message ID that it got is: %u\n",
+ client->lastId());
+ }
+ // send event with message "hello!", id current millis
+ // and set reconnect delay to 1 second
+ // client->send("hello!", NULL, millis(), 10000);
+ });
+ server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) {
+ if (!request->authenticate(http_username, http_password)) {
+ return request->requestAuthentication();
+ }
+ request->send(LittleFS, "/www/upgrade.html", "text/html");
+ });
server.addHandler(&eventUpgradeProgress);
// Basic Connection status check
- server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request)
- { request->send(200, "application/json", "{\"status\":\"connected\"}"); });
+ server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) {
+ request->send(200, "application/json", "{\"status\":\"connected\"}");
+ });
// Server requested files that aren't template processed
server.on("/*", HTTP_GET, [](AsyncWebServerRequest *request) { // handle file uploads
// Validate request
- if (!request)
- {
+ if (!request) {
ESP_LOGE(tag, "Invalid request");
return;
}
// Get and validate file path
String filePath = request->url();
- if (filePath.isEmpty())
- {
+ if (filePath.isEmpty()) {
ESP_LOGE(tag, "Empty file path");
request->send(400, "text/plain", "Invalid file path");
return;
}
// Ensure path starts with '/'
- if (!filePath.startsWith("/"))
- {
+ if (!filePath.startsWith("/")) {
filePath = "/" + filePath;
}
- try
- {
- // Get content type once
- const char *contentType = getFileType(getFileExtension(filePath.c_str()));
- if (!contentType)
- {
- ESP_LOGW(tag, "Unknown file type: %s", filePath.c_str());
- contentType = "application/octet-stream";
- }
+ // Basic sanitization to prevent directory traversal
+ if (filePath.indexOf("..") >= 0) {
+ ESP_LOGW(tag, "Rejected unsafe path: %s", filePath.c_str());
+ request->send(400, "text/plain", "Invalid file path");
+ return;
+ }
- ESP_LOGI(tag, "Sending file: %s (%s)", filePath.c_str(), contentType);
- request->send(LittleFS, filePath, contentType);
- }
- catch (const std::runtime_error &e)
- {
- ESP_LOGE(tag, "FileSystem error: %s for path: %s", e.what(), filePath.c_str());
- request->send(404, "text/plain", "File not found");
- }
- catch (const std::exception &e)
- {
- ESP_LOGE(tag, "Error: %s for path: %s", e.what(), filePath.c_str());
- request->send(500, "text/plain", "Internal server error");
+ // Ensure leading slash and collapse duplicate slashes
+ if (!filePath.startsWith("/")) filePath = "/" + filePath;
+ while (filePath.indexOf("//") >= 0) filePath.replace("//", "/");
+
+ // Get content type once
+ const char *contentType = getFileType(getFileExtension(filePath.c_str()));
+ if (!contentType) {
+ ESP_LOGW(tag, "Unknown file type: %s", filePath.c_str());
+ contentType = "application/octet-stream";
}
+
+ ESP_LOGD(tag, "Sending file: %s (%s)", filePath.c_str(), contentType);
+ request->send(LittleFS, filePath, contentType);
});
// 404 handler
- server.onNotFound([](AsyncWebServerRequest *request)
- {
- ESP_LOGE(tag, "404: %s", request->url().c_str());
- request->send(404, "text/plain", "Not found"); });
+ server.onNotFound([](AsyncWebServerRequest *request) {
+ ESP_LOGE(tag, "404: %s", request->url().c_str());
+ request->send(404, "text/plain", "Not found");
+ });
}
-void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
-{
+void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index,
+ uint8_t *data, size_t len, bool final) {
static const size_t MAX_UPLOAD_SIZE = 1024 * 512; // 512KB limit
- if (!index)
- {
+ if (!index) {
// Initial upload chunk
- if (!request->hasParam("dir-path", true, false))
- {
+ if (!request->hasParam("dir-path", true, false)) {
ESP_LOGE(tag, "Missing dir-path parameter");
request->send(400, "text/plain", "Missing dir-path");
return;
}
AsyncWebParameter *p = request->getParam("dir-path", true, false);
- String path = p->value() + "/" + filename;
- ESP_LOGI(tag, "Starting upload: %s", path.c_str());
+ String path = p->value() + "/" + filename;
+ // Normalize and reject traversal
+ if (path.indexOf("..") >= 0) {
+ ESP_LOGW(tag, "Rejected upload with unsafe path: %s", path.c_str());
+ request->send(400, "text/plain", "Invalid upload path");
+ return;
+ }
+ if (!path.startsWith("/")) path = "/" + path;
+ while (path.indexOf("//") >= 0) path.replace("//", "/");
- // Validate path
- if (!path.startsWith("/"))
- {
- path = "/" + path;
- }
+ ESP_LOGI(tag, "Starting upload: %s", path.c_str());
- request->_tempFile = LittleFS.open(path, "w");
- if (!request->_tempFile)
- {
+ request->_tempFile = LittleFS.open(path, "w");
+ if (!request->_tempFile) {
ESP_LOGE(tag, "Failed to create file: %s", path.c_str());
request->send(500, "text/plain", "Failed to create file");
return;
@@ -831,10 +840,8 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s
}
// Write chunk
- if (len && request->_tempFile)
- {
- if (index + len > MAX_UPLOAD_SIZE)
- {
+ if (len && request->_tempFile) {
+ if (index + len > MAX_UPLOAD_SIZE) {
request->_tempFile.close();
LittleFS.remove(request->_tempFile.name());
ESP_LOGE(tag, "Upload too large");
@@ -842,8 +849,7 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s
return;
}
- if (!request->_tempFile.write(data, len))
- {
+ if (!request->_tempFile.write(data, len)) {
ESP_LOGE(tag, "Write failed");
request->_tempFile.close();
request->send(500, "text/plain", "Write failed");
@@ -851,8 +857,7 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s
}
}
- if (final)
- {
+ if (final) {
request->_tempFile.close();
ESP_LOGI(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len);
request->redirect("/files");
@@ -860,58 +865,46 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s
}
// Send html file with template processing {{VAR}}
-void sendHtmlFile(const char *filePath, AsyncWebServerRequest *request, String (*callback)(const String &))
-{
- try
- {
- const char *htmlFile = readFile(LittleFS, filePath);
- if (!htmlFile)
- {
- ESP_LOGE(tag, "Failed to read file: %s", filePath);
- request->send(404, "text/plain", "File not found");
- return;
- }
-
- String processedData = varReplace(htmlFile, callback);
- delete[] htmlFile; // Clean up allocated memory
-
- ESP_LOGI(tag, "Sent file: %s", filePath);
- request->send(200, "text/html", processedData);
- }
- catch (const std::exception &e)
- {
- ESP_LOGE(tag, "Error processing file %s: %s", filePath, e.what());
- request->send(500, "text/plain", "Server error");
+bool sendHtmlFile(const char *filePath, AsyncWebServerRequest *request,
+ String (*callback)(const String &)) {
+ const char *htmlFile = readFile(LittleFS, filePath);
+ if (!htmlFile) {
+ ESP_LOGE(tag, "Failed to read file: %s", filePath);
+ request->send(404, "text/plain", "File not found");
+ return false;
}
+
+ String processedData = varReplace(htmlFile, callback);
+ delete[] htmlFile; // Clean up allocated memory
+
+ ESP_LOGI(tag, "Sent file: %s", filePath);
+ request->send(200, "text/html", processedData);
+ return true;
}
-const char *getFileExtension(const char *filename)
-{
+const char *getFileExtension(const char *filename) {
// Input validation
- if (!filename)
- {
+ if (!filename) {
ESP_LOGW(tag, "Null filename provided");
return "";
}
// Find last dot
const char *lastDot = strrchr(filename, '.');
- if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0')
- {
- ESP_LOGI(tag, "No valid extension found in: %s", filename);
+ if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0') {
+ ESP_LOGD(tag, "No valid extension found in: %s", filename);
return "";
}
- ESP_LOGI(tag, "Found extension: %s", lastDot + 1);
+ ESP_LOGD(tag, "Found extension: %s", lastDot + 1);
return lastDot + 1;
}
-const char *getFileType(const char *ext)
-{
+const char *getFileType(const char *ext) {
if (!ext)
return "application/octet-stream";
- ESP_LOGI(tag, "Getting file type for extension: %s", ext);
+ ESP_LOGD(tag, "Getting file type for extension: %s", ext);
if (strcmp(ext, "png") == 0)
return "image/png";
@@ -932,18 +925,16 @@ const char *getFileType(const char *ext)
if (strcmp(ext, "json") == 0)
return "application/json";
- ESP_LOGW(tag, "Unknown file extension: %s", ext);
+ ESP_LOGD(tag, "Unknown file extension: %s", ext);
return "application/octet-stream";
}
// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content
-String varReplace(const String &input, String (*callback)(const String &))
-{
+String varReplace(const String &input, String (*callback)(const String &)) {
static const char *tag = "varReplace";
// Validate inputs
- if (input.isEmpty() || !callback)
- {
+ if (input.isEmpty() || !callback) {
ESP_LOGW(tag, "Empty input or null callback");
return input;
}
@@ -956,12 +947,10 @@ String varReplace(const String &input, String (*callback)(const String &))
int startPos = 0;
// Process all segments
- while (true)
- {
+ while (true) {
// Find next variable
int start = input.indexOf("{{", startPos);
- if (start == -1)
- {
+ if (start == -1) {
break;
}
@@ -970,8 +959,7 @@ String varReplace(const String &input, String (*callback)(const String &))
// Find end of variable
int end = input.indexOf("}}", start + 2);
- if (end == -1)
- {
+ if (end == -1) {
ESP_LOGW(tag, "Unmatched {{ at position %d", start);
result += input.substring(start);
break;
@@ -979,21 +967,15 @@ String varReplace(const String &input, String (*callback)(const String &))
// Extract and validate segment
String segment = input.substring(start + 2, end);
- if (segment.length() <= maxSegmentLength)
- {
- try
- {
- String replacement = callback(segment);
+ if (segment.length() <= maxSegmentLength) {
+ String replacement = callback(segment);
+ if (!replacement.isEmpty()) {
result += replacement;
- }
- catch (const std::exception &e)
- {
- ESP_LOGE(tag, "Callback error: %s", e.what());
+ } else {
+ ESP_LOGW(tag, "Callback returned empty for segment: %s", segment.c_str());
result += input.substring(start, end + 2);
}
- }
- else
- {
+ } else {
ESP_LOGW(tag, "Segment too long: %d chars", segment.length());
result += input.substring(start, end + 2);
}
@@ -1002,43 +984,33 @@ String varReplace(const String &input, String (*callback)(const String &))
}
// Add remaining text
- if (startPos < input.length())
- {
+ if (startPos < input.length()) {
result += input.substring(startPos);
}
return result;
}
-const char *convertFileSize(const size_t bytes)
-{
+const char *convertFileSize(const size_t bytes) {
static char fileSizeBuffer[16]; // Pre-allocated buffer for the file size
- if (bytes < 1024)
- {
+ if (bytes < 1024) {
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes);
- }
- else if (bytes < 1024 * 1024)
- {
+ } else if (bytes < 1024 * 1024) {
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", bytes / 1024.0);
- }
- else if (bytes < 1024 * 1024 * 1024)
- {
+ } else if (bytes < 1024 * 1024 * 1024) {
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", bytes / (1024.0 * 1024.0));
- }
- else
- {
- snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
+ } else {
+ snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f GB",
+ bytes / (1024.0 * 1024.0 * 1024.0));
}
return fileSizeBuffer;
}
-void onWiFiEvent(WiFiEvent_t event)
-{
+void onWiFiEvent(WiFiEvent_t event) {
// Serial.printf("[WiFi-event] event: %d\n", event);
- switch (event)
- {
+ switch (event) {
case ARDUINO_EVENT_WIFI_READY:
ESP_LOGI(tag, "WiFi interface ready");
break;
@@ -1132,51 +1104,41 @@ void onWiFiEvent(WiFiEvent_t event)
}
}
-void Wifi_Start_MDNS(void)
-{
+void Wifi_Start_MDNS(void) {
ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str());
- if (!MDNS.begin(mDnsName.c_str()))
- {
+ if (!MDNS.begin(mDnsName.c_str())) {
ESP_LOGE(tag, "Error setting up MDNS responder!");
- }
- else
- {
+ } else {
ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName);
}
}
-bool writeFile(fs::FS &fs, const char *path, const char *message)
-{
+bool writeFile(fs::FS &fs, const char *path, const char *message) {
// Validate inputs
- if (!path || !message)
- {
+ if (!path || !message) {
ESP_LOGE(tag, "Invalid parameters: path=%p message=%p", path, message);
return false;
}
// Normalize and validate path
String finalPath(path);
- if (!finalPath.startsWith("/"))
- {
+ if (!finalPath.startsWith("/")) {
finalPath = String("/") + finalPath;
}
// Prevent directory traversal
- if (finalPath.indexOf("..") >= 0)
- {
+ if (finalPath.indexOf("..") >= 0) {
ESP_LOGE(tag, "Rejected unsafe path: %s", finalPath.c_str());
return false;
}
// Collapse duplicate slashes (optional hardening)
- while (finalPath.indexOf("//") >= 0)
- {
+ while (finalPath.indexOf("//") >= 0) {
finalPath.replace("//", "/");
}
// Size checks
const size_t MAX_FILE_SIZE = 1024 * 1024; // 1MB cap (aligned with readFile)
const size_t totalSize = strlen(message);
- if (totalSize > MAX_FILE_SIZE)
- {
+ if (totalSize > MAX_FILE_SIZE) {
ESP_LOGE(tag, "Write too large: %u bytes for %s", (unsigned)totalSize, finalPath.c_str());
return false;
}
@@ -1184,8 +1146,7 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
// Write to a temporary file first for atomicity
String tmpPath = finalPath + ".tmp";
File tmp = fs.open(tmpPath.c_str(), "w");
- if (!tmp)
- {
+ if (!tmp) {
ESP_LOGE(tag, "Failed to open temp file: %s", tmpPath.c_str());
return false;
}
@@ -1193,12 +1154,11 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
// Write in a loop to ensure all bytes are written
size_t written = 0;
const uint8_t *buf = reinterpret_cast(message);
- while (written < totalSize)
- {
+ while (written < totalSize) {
size_t n = tmp.write(buf + written, totalSize - written);
- if (n == 0)
- {
- ESP_LOGE(tag, "Write failed to temp file: %s at %u/%u bytes", tmpPath.c_str(), (unsigned)written, (unsigned)totalSize);
+ if (n == 0) {
+ ESP_LOGE(tag, "Write failed to temp file: %s at %u/%u bytes", tmpPath.c_str(),
+ (unsigned)written, (unsigned)totalSize);
tmp.close();
fs.remove(tmpPath.c_str());
return false;
@@ -1211,17 +1171,14 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
tmp.close();
// Replace the target file atomically: remove existing then rename
- if (fs.exists(finalPath.c_str()))
- {
- if (!fs.remove(finalPath.c_str()))
- {
+ if (fs.exists(finalPath.c_str())) {
+ if (!fs.remove(finalPath.c_str())) {
ESP_LOGE(tag, "Failed to remove existing file: %s", finalPath.c_str());
fs.remove(tmpPath.c_str());
return false;
}
}
- if (!fs.rename(tmpPath.c_str(), finalPath.c_str()))
- {
+ if (!fs.rename(tmpPath.c_str(), finalPath.c_str())) {
ESP_LOGE(tag, "Failed to rename %s to %s", tmpPath.c_str(), finalPath.c_str());
fs.remove(tmpPath.c_str());
return false;
@@ -1231,30 +1188,26 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
return true;
}
-char *readFile(fs::FS &fs, const char *path)
-{
+char *readFile(fs::FS &fs, const char *path) {
static const char *tag = "readFile";
static const size_t MAX_FILE_SIZE = 1024 * 1024; // 1MB limit
// Validate input
- if (!path)
- {
+ if (!path) {
ESP_LOGE(tag, "Invalid path parameter");
return nullptr;
}
// Open file
File file = fs.open(path, "r");
- if (!file || file.isDirectory())
- {
+ if (!file || file.isDirectory()) {
ESP_LOGE(tag, "Failed to open file: %s", path);
return nullptr;
}
// Check file size
size_t fileSize = file.size();
- if (fileSize == 0 || fileSize > MAX_FILE_SIZE)
- {
+ if (fileSize == 0 || fileSize > MAX_FILE_SIZE) {
ESP_LOGE(tag, "Invalid file size: %u bytes", fileSize);
file.close();
return nullptr;
@@ -1262,8 +1215,7 @@ char *readFile(fs::FS &fs, const char *path)
// Allocate memory
char *fileContent = new (std::nothrow) char[fileSize + 1];
- if (!fileContent)
- {
+ if (!fileContent) {
ESP_LOGE(tag, "Memory allocation failed for size: %u", fileSize + 1);
file.close();
return nullptr;
@@ -1273,8 +1225,7 @@ char *readFile(fs::FS &fs, const char *path)
size_t bytesRead = file.readBytes(fileContent, fileSize);
file.close();
- if (bytesRead != fileSize)
- {
+ if (bytesRead != fileSize) {
ESP_LOGE(tag, "Read failed: expected %u bytes, got %u", fileSize, bytesRead);
delete[] fileContent;
return nullptr;
@@ -1286,14 +1237,12 @@ char *readFile(fs::FS &fs, const char *path)
return fileContent;
}
-String getSoftAPMacAddress()
-{
+String getSoftAPMacAddress() {
uint8_t mac[6];
WiFi.softAPmacAddress(mac);
String macString = "";
- for (int i = 0; i < 6; i++)
- {
+ for (int i = 0; i < 6; i++) {
macString += String(mac[i], HEX);
if (i < 5)
macString += ":";
@@ -1301,47 +1250,107 @@ String getSoftAPMacAddress()
return macString;
}
-String listDirAsHtml(String directoryList[], int count)
-{
- String listedFiles;
+String listDirAsHtml(String directoryList[], int count) {
+ // Validate input parameters
+ if (!directoryList || count <= 0 || count > MAX_DIRECTORIES) {
+ ESP_LOGW(tag, "Invalid parameters: directoryList=%p, count=%d", directoryList, count);
+ return String();
+ }
- for (int i = 0; i < count; i++)
- {
- // directory html
- listedFiles += "| Dir: ";
- listedFiles += directoryList[i];
- listedFiles += "/ | - |
\n";
+ // Pre-calculate estimated size to reduce reallocations
+ // Estimate: ~150 bytes per directory + ~120 bytes per file (conservative)
+ size_t estimatedSize = count * 300 + 512; // Extra buffer for safety
+ String result;
+ result.reserve(estimatedSize);
- filesDropdownOptions += "\n";
+ // Track statistics for debugging
+ int totalFiles = 0;
+ int totalDirs = 0;
+ int skippedDirs = 0;
- dirDropdownOptions += "\n";
+ for (int i = 0; i < count; i++) {
+ // Validate and normalize directory path
+ if (directoryList[i].isEmpty()) {
+ ESP_LOGD(tag, "Skipping empty directory path at index %d", i);
+ continue;
+ }
- File dir = LittleFS.open(directoryList[i]);
+ String dirPath = directoryList[i];
+
+ // Security check: prevent directory traversal
+ if (dirPath.indexOf("..") >= 0) {
+ ESP_LOGW(tag, "Skipping directory with traversal attempt: %s", dirPath.c_str());
+ skippedDirs++;
+ continue;
+ }
+
+ // Normalize path efficiently
+ if (!dirPath.startsWith("/")) {
+ dirPath = "/" + dirPath;
+ }
+
+ // Replace multiple slashes with single slash (more efficient than while loop)
+ dirPath.replace("//", "/");
+ if (dirPath.indexOf("//") >= 0) {
+ // If still contains //, do more thorough cleanup
+ while (dirPath.indexOf("//") >= 0) {
+ dirPath.replace("//", "/");
+ }
+ }
+
+ // Generate directory row HTML with better formatting
+ result += "| 📁 ";
+ result += dirPath;
+ result += "/ | - |
\n";
+ totalDirs++;
+
+ // Open directory with error handling
+ File dir = LittleFS.open(dirPath);
+ if (!dir) {
+ ESP_LOGW(tag, "Cannot open directory: %s", dirPath.c_str());
+ skippedDirs++;
+ continue;
+ }
+
+ if (!dir.isDirectory()) {
+ ESP_LOGW(tag, "Path is not a directory: %s", dirPath.c_str());
+ dir.close();
+ skippedDirs++;
+ continue;
+ }
+
+ // Process files in directory
File file = dir.openNextFile();
- while (file)
- {
- String fileName = file.name();
- if (!file.isDirectory())
- {
- // Serial.println(" File: " + String(file.name()));
- listedFiles += "| ";
- listedFiles += fileName;
- listedFiles += " | ";
- listedFiles += convertFileSize(file.size());
- listedFiles += " |
\n";
+ while (file) {
+ if (!file.isDirectory()) {
+ String fileName = file.name();
+ String filePath = file.path();
+ size_t fileSize = file.size();
+
+ // Validate file data
+ if (fileName.isEmpty()) {
+ ESP_LOGD(tag, "Skipping file with empty name in %s", dirPath.c_str());
+ file = dir.openNextFile();
+ continue;
+ }
+ // Build file row HTML efficiently
+ result += "| 📄 ";
+ result += fileName;
+ result += " | ";
+ result += convertFileSize(fileSize);
+ result += " |
\n";
+
+ totalFiles++;
+
+ // Update global dropdown options (maintaining compatibility)
filesDropdownOptions += "\n";
@@ -1349,196 +1358,173 @@ String listDirAsHtml(String directoryList[], int count)
file = dir.openNextFile();
}
dir.close();
+
+ // Update directory dropdown options (maintaining compatibility)
+ dirDropdownOptions += "\n";
}
- return listedFiles;
+ // Log summary for debugging
+ ESP_LOGD(tag, "Listed %d directories (%d skipped), %d files. HTML size: %d bytes",
+ totalDirs, skippedDirs, totalFiles, result.length());
+
+ return result;
}
/******************** Specific Html Processors ********************/
// file manager html processor
-String fileManagerHtmlProcessor(const String &var)
-{
- if (var == "ALLOWED_EXTENSIONS_EDIT")
- {
+String fileManagerHtmlProcessor(const String &var) {
+ if (var == "ALLOWED_EXTENSIONS_EDIT") {
return allowedExtensionsForEdit;
}
- if (var == "FS_FREE_BYTES")
- {
+ if (var == "FS_FREE_BYTES") {
return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes());
}
- if (var == "FS_USED_BYTES")
- {
+ if (var == "FS_USED_BYTES") {
return convertFileSize(LittleFS.usedBytes());
}
- if (var == "FS_TOTAL_BYTES")
- {
+ if (var == "FS_TOTAL_BYTES") {
return convertFileSize(LittleFS.totalBytes());
}
- if (var == "RAM_FREE_BYTES")
- {
+ if (var == "RAM_FREE_BYTES") {
return convertFileSize(LittleFS.totalBytes());
}
- if (var == "RAM_USED_BYTES")
- {
+ if (var == "RAM_USED_BYTES") {
return convertFileSize(LittleFS.totalBytes());
}
- if (var == "RAM_TOTAL_BYTES")
- {
+ if (var == "RAM_TOTAL_BYTES") {
return convertFileSize(LittleFS.totalBytes());
}
- if (var == "LISTED_FILES")
- {
+ if (var == "LISTED_FILES") {
filesDropdownOptions = ""; // clear out
dirDropdownOptions = ""; // clear out
- String directories[MAX_DIRECTORIES];
+ String *directories = new (std::nothrow) String[MAX_DIRECTORIES];
+ if (!directories) {
+ ESP_LOGE(tag, "Failed to allocate directories array");
+ return String();
+ }
int dirCount = 0;
getAllDirectories(directories, dirCount);
- return listDirAsHtml(directories, dirCount);
+ String out = listDirAsHtml(directories, dirCount);
+ delete[] directories;
+ return out;
}
- if (var == "EDIT-DEL_FILES")
- {
+ if (var == "EDIT-DEL_FILES") {
return filesDropdownOptions;
}
- if (var == "DIR_LIST")
- {
+ if (var == "DIR_LIST") {
return dirDropdownOptions;
}
- if (var == "SAVE_PATH_INPUT")
- {
+ if (var == "SAVE_PATH_INPUT") {
return savePath;
}
- if (var == "FIRM_VER")
- {
+ if (var == "FIRM_VER") {
return localVersion.toString();
}
return var;
}
-String HomeHtmlProcessor(const String &var)
-{
- if (var == "APP_NAME")
- {
+String HomeHtmlProcessor(const String &var) {
+ if (var == "APP_NAME") {
// return sysProps.appName;
return "N/A";
}
- if (var == "OLED")
- {
+ if (var == "OLED") {
return "N/A";
}
- if (var == "STRIP1")
- {
+ if (var == "STRIP1") {
// return (strip1) ? "Yes" : "No";
return "N/A";
}
- if (var == "STRIP2")
- {
+ if (var == "STRIP2") {
// return (strip2) ? "Yes" : "No";
return "N/A";
}
- if (var == "FRONT_LIGHT")
- {
+ if (var == "FRONT_LIGHT") {
// return (animProps.frontLight.enabled) ? "Yes" : "No";
return "N/A";
}
- if (var == "REAR_LIGHT")
- {
+ if (var == "REAR_LIGHT") {
// return (animProps.rearLight.enabled) ? "Yes" : "No";
return "N/A";
}
- if (var == "FIRMWARE")
- {
+ if (var == "FIRMWARE") {
return localVersion.toString();
}
- if (var == "BOOTH_T")
- {
+ if (var == "BOOTH_T") {
// return String(sysProps.t_sensor.temperature) + "F";
return "N/A";
}
- if (var == "SETPOINT")
- {
+ if (var == "SETPOINT") {
// return String(sysProps.t_sensor.Setpoint1) + "F";
return "N/A";
}
- if (var == "FLASH_SIZE")
- {
+ if (var == "FLASH_SIZE") {
return convertFileSize(ESP.getSketchSize());
}
- if (var == "FLASH_FREE")
- {
+ if (var == "FLASH_FREE") {
return convertFileSize(ESP.getFreeSketchSpace());
}
- if (var == "HEAP_SIZE")
- {
+ if (var == "HEAP_SIZE") {
return convertFileSize(ESP.getHeapSize());
}
- if (var == "HEAP_FREE")
- {
+ if (var == "HEAP_FREE") {
return convertFileSize(ESP.getFreeHeap());
}
- if (var == "CPU_FREQ")
- {
+ if (var == "CPU_FREQ") {
return String(ESP.getCpuFreqMHz()) + "Mhz";
}
- if (var == "IP")
- {
+ if (var == "IP") {
return WiFi.localIP().toString();
}
- if (var == "MAC")
- {
+ if (var == "MAC") {
return chipInfo.macStr;
}
- if (var == "SSID")
- {
+ if (var == "SSID") {
return WiFi.SSID();
}
- if (var == "RSSI")
- {
+ if (var == "RSSI") {
return String(WiFi.RSSI());
}
- if (var == "WIFI_CH")
- {
+ if (var == "WIFI_CH") {
return String(WiFi.channel());
}
- if (var == "ENCRYP")
- {
+ if (var == "ENCRYP") {
return String(WiFi.encryptionType(0));
}
- if (var == "AP_SSID")
- {
+ if (var == "AP_SSID") {
return WiFi.softAPSSID();
}
- if (var == "AP_CLIENTS")
- {
+ if (var == "AP_CLIENTS") {
return String(WiFi.softAPgetStationNum());
}
- if (var == "BLE")
- {
+ if (var == "BLE") {
return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No";
}
- if (var == "BLE_SSID")
- {
+ if (var == "BLE_SSID") {
// return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : "";
return "N/A";
}
- if (var == "BLE_CLIENTS")
- {
+ if (var == "BLE_CLIENTS") {
// return (BTDeviceConnected) ? "1" : "0";
return "N/A";
}
- if (var == "AP_MAC")
- {
+ if (var == "AP_MAC") {
return getSoftAPMacAddress();
}
@@ -1546,8 +1532,7 @@ String HomeHtmlProcessor(const String &var)
return var;
}
-void handleUpdateProgress(AsyncWebServerRequest *request)
-{
+void handleUpdateProgress(AsyncWebServerRequest *request) {
static const char *tag = "UpdateProgress";
// if (!request->authenticate(http_username, http_password)) {
diff --git a/temporary/AppUpgrade copy.cpp b/temporary/AppUpgrade copy.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/temporary/AppUpgrade orig.cpp b/temporary/AppUpgrade orig.cpp
deleted file mode 100644
index 91e6c90..0000000
--- a/temporary/AppUpgrade orig.cpp
+++ /dev/null
@@ -1,673 +0,0 @@
-#include "AppUpgrade.h"
-#include "esp_log.h"
-#include
-#include
-#include
-#include "global.h"
-#include "JsonConstrain.h"
-#include "BLE_UpdateService.h"
-#include
-#include
-
-static const char* TAG = "AppUpdater";
-TaskHandle_t Update_Task_Handle = NULL;
-TaskHandle_t versionCheckTask_Handle = NULL;
-
-// Queue handle for firmware update messages
-//QueueHandle_t updateMsgQueue = NULL;
-
-String updateUrl = "";
-
-Version otaVersion;
-
-
-AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName, const char* appBin)
- : localVersion(localVersion), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE])
-{
- baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
- // Ensure baseUrl ends with a single '/'
- if(!baseUrl.endsWith("/")) baseUrl += "/";
- ESP_LOGI(TAG, "AppUpdater initialized (local v%s) baseUrl=%s", localVersion.toString().c_str(), baseUrl.c_str());
-}
-
-void AppUpdater::setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)) {
- progressCb = callback;
-}
-
-void AppUpdater::updateProgress(UpdateStatus newStatus, int percentage, const char* message) {
- status = newStatus;
- if (progressCb) {
- progressCb(status, percentage, message);
- }
-}
-
-bool AppUpdater::checkManifest() {
- String url = buildUrl(manifestName);
- ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str());
-
- // Start the HTTP client and Send GET request for manifest
- HTTPClient http;
- http.begin(url);
- int httpCode = http.GET();
- if (httpCode != HTTP_CODE_OK) {
- ESP_LOGE(TAG, "HTTP GET failed, error: %d", httpCode);
- http.end();
- return false;
- }
-
- // Read the response
- String payload = http.getString();
- http.end();
-
- // Parse JSON
- DeserializationError error = deserializeJson(jsonManifest, payload);
- ESP_LOGD(TAG, "Manifest deserialized");
- if (error) {
- ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str());
- return false;
- }
-
- // Check for files section
- jsonFilesArray = jsonManifest["files"];
- if (jsonFilesArray.isNull()) {
- ESP_LOGE(TAG, "No files section in manifest");
- return false;
- }else{
- ESP_LOGD(TAG, "%d Files found", jsonFilesArray.size());
- }
-
- // Check for version section
- JsonObject jsonVersion = jsonManifest["version"];
- ESP_LOGD(TAG, "Version section found");
- if (jsonVersion.isNull()) {
- ESP_LOGE(TAG, "No version section in manifest");
- return false;
- }
-
- // Get the remote version
- byte major = jsonVersion["major"] | 0;
- byte minor = jsonVersion["minor"] | 0;
- byte patch = jsonVersion["patch"] | 0;
- otaVersion = {major, minor, patch};
-
- //Version localVersion;
- //::sscanf(localVersion, "%d.%d.%d", &localVersion.major, &localVersion.minor, &localVersion.patch);
-
- // Check if an update is available
- updateAvailable = false;
- // Only mark update available if remote is strictly newer than local
- if (otaVersion <= localVersion) {
- ESP_LOGI(TAG, "No updates available");
- return false;
- }else{
- updateAvailable = true;
- ESP_LOGD(TAG, "Update available");
- }
-
- //ESP_LOGD(TAG, "Manifest content: %s", payload.c_str());
-
- return true;
-}
-
-bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const char* expectedMd5) {
- //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
-
- // Construct full URL
- String url = buildUrl(remotePath);
- ESP_LOGD(TAG, "Downloading: %s -> %s", url.c_str(), localPath);
-
- String localMd5 = getLocalMD5(localPath);
-
- if (localMd5.equals(expectedMd5)) {
- ESP_LOGI(TAG, "File already up to date: %s", localPath);
- updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
- return true;
- }
-
- // Start the download
- HTTPClient http;
- http.begin(url);
- int httpCode = http.GET();
- if (httpCode != HTTP_CODE_OK) {
- ESP_LOGE(TAG, "Download failed: %d", httpCode);
- updateProgress(UpdateStatus::ERROR, 0, "Download failed");
- http.end();
- return false;
- }
-
- // Get the stream and content length
- WiFiClient* stream = http.getStreamPtr();
- size_t contentLength = http.getSize();
-
- // Verify and save the file
- bool success = verifyAndSaveFile(stream, contentLength, localPath, expectedMd5);
- http.end();
- if(!success){
- updateProgress( UpdateStatus::ERROR, 0, "MD5 verification failed");
- }else{
- updateProgress( UpdateStatus::FILE_SAVED, 100, localPath);
- }
-
- return success;
-}
-
-bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* expectedMd5)
-{
- MD5Builder md5;
- md5.begin();
- size_t totalRead = 0;
-
- // Create temporary filename
- String tempPath = String(localPath) + ".tmp";
-
- // Open temporary file for writing
- File file = fileSystem.open(tempPath.c_str(), FILE_WRITE);
- if (!file) {
- ESP_LOGE(TAG, "Failed to open temporary file for writing");
- return false;
- }
-
- //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
-
- if (contentLength > 0) {
- // Single pass with known content length
- while (totalRead < contentLength) {
- size_t available = stream->available();
- if (available) {
- size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE)));
-
- // Write to temp file and update MD5
- if (file.write(downloadBuffer.get(), readLen) != readLen) {
- ESP_LOGE(TAG, "Failed to write to temporary file");
-
- file.close();
- fileSystem.remove(tempPath.c_str());
- return false;
- }
-
- md5.add(downloadBuffer.get(), readLen);
- totalRead += readLen;
- updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 90) / contentLength , localPath);
- }
- yield();
- }
- } else {
- // Unknown content length: read until stream ends
- for (;;) {
- size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
- if (readLen == 0) {
- break;
- }
- if (file.write(downloadBuffer.get(), readLen) != readLen) {
- ESP_LOGE(TAG, "Failed to write to temporary file");
- file.close();
- fileSystem.remove(tempPath.c_str());
- return false;
- }
- md5.add(downloadBuffer.get(), readLen);
- totalRead += readLen;
- // Progress unknown; emit periodic heartbeats at 0%
- updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
- yield();
- }
- }
-
- file.close();
- md5.calculate();
- String calculatedMd5 = md5.toString();
-
- // Verify MD5 hash
- if (!calculatedMd5.equals(expectedMd5)) {
- //ESP_LOGE(TAG, "MD5 mismatch for %s", localPath);
- fileSystem.remove(tempPath.c_str());
- return false;
- }
-
- // Replace original file with verified temp file
- if (fileSystem.exists(localPath)) {
- fileSystem.remove(localPath);
- }
- if (!fileSystem.rename(tempPath.c_str(), localPath)) {
- ESP_LOGE(TAG, "Failed to rename temporary file");
- fileSystem.remove(tempPath.c_str());
- return false;
- }
-
- return true;
-}
-
-String AppUpdater::getLocalMD5(const char* filePath){
- File file = fileSystem.open(filePath, "r");
- if(!file){
- ESP_LOGE(TAG, "Error opening %s...", filePath);
- return String();
- }
-
- MD5Builder md5Builder;
- md5Builder.begin();
- size_t fileSize = file.size();
- size_t totalRead = 0;
- size_t readLen = 0;
- while (totalRead < fileSize) {
- readLen = file.readBytes(reinterpret_cast(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE)));
- md5Builder.add(downloadBuffer.get(), readLen);
- totalRead += readLen;
- }
-
- md5Builder.calculate();
- file.close();
- return md5Builder.toString();
-}
-
-bool AppUpdater::updateFilesArray() {
- int successCount = 0;
- int totalFiles = jsonFilesArray.size();
- ESP_LOGI(TAG, "Found %d files in manifest", totalFiles);
-
- // Iterate over each file entry in the manifest
- for (JsonObject file : jsonFilesArray) {
- const char* remotePath = file["remote"];
- const char* localPath = file["local"];
- const char* expectedMd5 = file["md5"];
-
- // Skip invalid entries
- if (!remotePath || !localPath || !expectedMd5) {
- ESP_LOGE(TAG, "Invalid file entry in manifest");
- continue;
- }
-
- // Attempt to update the file
- if (updateFile(remotePath, localPath, expectedMd5)) {
- successCount++;
- }
- }
-
- ESP_LOGI(TAG, "Manifest update complete: %d/%d files updated", successCount, totalFiles);
- return successCount == totalFiles;
-}
-
-bool AppUpdater::updateApp() {
- updateProgress(UpdateStatus::MESSAGE, 0, "Starting firmware update");
-
- // Check for firmware section in manifest
- if (!jsonManifest["firmware"].is() || !jsonManifest["firmware"]["md5"].is()) {
- ESP_LOGE(TAG, "Invalid firmware section in manifest");
- updateProgress(UpdateStatus::ERROR, 0, "Firmware: Invalid firmware section in manifest");
- return false;
- }
-
- // Get the firmware MD5 hash and URL
- const char* expectedMd5 = jsonManifest["firmware"]["md5"];
- String firmwareUrl = buildUrl(appName);
-
- // Download the firmware
- HTTPClient http;
- http.begin(firmwareUrl);
- int httpCode = http.GET();
- if (httpCode != HTTP_CODE_OK) {
- ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
- updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
- http.end();
- return false;
- }
-
- // Check available space
- size_t firmwareSize = http.getSize();
- if (!Update.begin(firmwareSize > 0 ? firmwareSize : UPDATE_SIZE_UNKNOWN)) {
- ESP_LOGE(TAG, "Firmware: Not enough space for update");
- updateProgress(UpdateStatus::ERROR, 0, "Firmware: Not enough space for update");
- http.end();
- return false;
- }
-
- // Set up MD5 checking
- MD5Builder md5;
- md5.begin();
-
- // Download and verify firmware
- WiFiClient* stream = http.getStreamPtr();
- if (firmwareSize > 0) {
- size_t remaining = firmwareSize;
- while (remaining > 0) {
- size_t chunk = std::min(remaining, size_t(BUFFER_SIZE));
- size_t read = stream->readBytes(downloadBuffer.get(), chunk);
-
- // Check for timeout
- if (read == 0) {
- ESP_LOGE(TAG, "Read timeout");
- Update.abort();
- http.end();
- return false;
- }
-
- // Update MD5 and write firmware
- md5.add(downloadBuffer.get(), read);
- if (Update.write(downloadBuffer.get(), read) != read) {
- ESP_LOGE(TAG, "Write failed");
- Update.abort();
- http.end();
- return false;
- }
-
- remaining -= read;
- updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware");
- }
- } else {
- // Unknown size: stream until end
- for (;;) {
- size_t read = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
- if (read == 0) break;
- md5.add(downloadBuffer.get(), read);
- if (Update.write(downloadBuffer.get(), read) != read) {
- ESP_LOGE(TAG, "Write failed");
- Update.abort();
- http.end();
- return false;
- }
- updateProgress(UpdateStatus::DOWNLOADING, 0, "firmware");
- }
- }
-
- // Verify MD5
- md5.calculate();
- String calculatedMd5 = md5.toString();
- if (!calculatedMd5.equals(expectedMd5)) {
- ESP_LOGE(TAG, "MD5 mismatch. Expected: %s, Got: %s", expectedMd5, calculatedMd5.c_str());
- updateProgress(UpdateStatus::MD5_FAILED, 0, "Firmware: MD5 mismatch");
- Update.abort();
- http.end();
- return false;
- }
-
- // Finish update
- if (!Update.end()) {
- ESP_LOGE(TAG, "Update end failed");
- updateProgress(UpdateStatus::ERROR, 0, "Firmware: Update failed");
- http.end();
- return false;
- }
-
- http.end();
- updateProgress(UpdateStatus::COMPLETE, 0, "Firmware: Complete");
- return true;
-}
-
-bool AppUpdater::IsUpdateAvailable(){
- return updateAvailable;
-}
-
-String AppUpdater::buildUrl(const char* path) const {
- if(!path || !*path) return baseUrl; // just base
- String p(path);
- // If already absolute URL, pass through
- if(p.startsWith("http://") || p.startsWith("https://")) return p;
- // Strip leading slashes to avoid double
- while(p.startsWith("/")) p.remove(0,1);
- // Ensure baseUrl has single trailing slash
- String b = baseUrl;
- if(!b.endsWith("/")) b += "/";
- return b + p;
-}
-
-
-AsyncEventSource* eventProgress = nullptr;
-void startFirmwareUpdateTask(AsyncEventSource* evProg) {
- eventProgress = evProg;
- if(Update_Task_Handle) {
- ESP_LOGW(TAG, "Firmware update task already running");
- return;
- }
- xTaskCreate(firmwareUpdateTask, "FirmwareUpdate", 1024*8, NULL, 1, &Update_Task_Handle);
-}
-
-void firmwareUpdateTask(void* parameter) {
- static const char* TAG = "UpdateTask";
- AppUpdater* updater = nullptr;
-
- try {
- loadUpdateJson();
-
- // Initialize updater
- updater = new AppUpdater(LittleFS, localVersion, updateUrl.c_str(), "update.json", "firmware.bin");
- updater->setProgressCallback(updateProgress);
-
- ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
-
- // Check and perform updates
- if (!updater->checkManifest()) { throw std::runtime_error("Failed to check manifest"); }
-
- if (updater->IsUpdateAvailable()) {
- ESP_LOGI(TAG, "Update available, updating files...");
-
- if (!updater->updateFilesArray()) {
- throw std::runtime_error("Failed to update files");
- }
-
- ESP_LOGI(TAG, "Updating firmware...");
- if (!updater->updateApp()) {
- throw std::runtime_error("Failed to update firmware");
- }
- ESP_LOGI(TAG, "Update successful, restarting...");
-
- sendUpdateMessage("Restarting ", true, 100);
- vTaskDelay(2000);
-
- ESP.restart();
-
- }
-
- } catch (const std::exception& e) {
- ESP_LOGE(TAG, "Update failed: %s", e.what());
- }
- end:
- delete updater;
- Update_Task_Handle = NULL;
- vTaskDelete(NULL);
-}
-
-void startVersionCheckTask() {
- if(versionCheckTask_Handle != NULL) {
- ESP_LOGW(TAG, "Version Check Tak already running");
- return;
- }
- xTaskCreate(versionCheckTask, "VersionCheckTask", 1024*8, NULL, 1, &versionCheckTask_Handle);
-}
-
-void versionCheckTask(void* parameter){
-
- if(updateUrl == ""){
- loadUpdateJson();
- }
-
- if(checkManifest(otaVersion) == false){
- ESP_LOGE(TAG, "Error checking manifest");
- }
-
- versionCheckTask_Handle = NULL;
- vTaskDelete(NULL);
-}
-
-void loadUpdateJson(void) {
- try {
- ESP_LOGD(TAG, "loadUpdateJaon function...");
- if(updateUrl == "") {
- String updateJsonPath = "/system/update.json";
-
- // Read and parse update.json
- File file = LittleFS.open(updateJsonPath);
- if (!file) {
- throw std::runtime_error("Failed to open update.json");
- }
-
- JsonDocument doc;
- DeserializationError error = deserializeJson(doc, file);
- file.close();
-
- if (error) { throw std::runtime_error("Failed to parse update.json"); }
-
- // Get update configuration
- JsonObject jObj = doc.as();
- String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
- String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
- updateUrl = baseUrl + folderName;
-
- ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
- }
- } catch (const std::exception& e) {
- ESP_LOGE(TAG, "Update failed: %s", e.what());
- }
-}
-
-void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {
-
- char buffer[128];
- const char* msg;
- bool isComplete = false;
-
- const char* safeMsg = message ? message : "";
- switch (newStatus) {
- case AppUpdater::UpdateStatus::IDLE:
- snprintf(buffer, sizeof(buffer), "Update idle");
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::MESSAGE:
- msg = message ? message : "";
- break;
- case AppUpdater::UpdateStatus::DOWNLOADING:
- snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", safeMsg, percentage);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::VERIFYING:
- snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", safeMsg, percentage);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::FILE_SKIPPED:
- snprintf(buffer, sizeof(buffer), "%s: Skipping file update, already up to date", safeMsg);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::FILE_SAVED:
- snprintf(buffer, sizeof(buffer), "%s: File Saved", safeMsg);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::MD5_FAILED:
- snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", safeMsg);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::COMPLETE:
- snprintf(buffer, sizeof(buffer), "Firmware Update Complete!!!");
- msg = buffer;
- isComplete = true;
- break;
- case AppUpdater::UpdateStatus::ERROR:
- snprintf(buffer, sizeof(buffer), "Error!: %s", safeMsg);
- msg = buffer;
- break;
- default:
- snprintf(buffer, sizeof(buffer), "Unknown update status: %d", (int)newStatus);
- msg = buffer;
- break;
- }
-
- ESP_LOGI(TAG, "%s", msg);
- sendUpdateMessage(msg, isComplete, percentage);
-}
-
-void sendUpdateMessage(const char* message, bool complete, int progress = -1) {
- if(eventProgress && eventProgress->count() > 0) {
- JsonDocument jsonDoc;
- jsonDoc["message"] = message;
- jsonDoc["complete"] = complete;
- jsonDoc["progress"] = progress;
- String strMessage;
- serializeJson(jsonDoc, strMessage);
- eventProgress->send(strMessage.c_str(), "update", millis());
- }
- else{
- ESP_LOGW(TAG, "No clients connected to event source");
- }
-
- bleUpgrade_send_message(message);
-}
-
-bool checkManifest(Version& remoteVersion) {
- const char* TAG = "manifestCheck";
- String url = updateUrl + "update.json";
- ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str());
-
- // Start the HTTP client and send GET request for manifest
- HTTPClient http;
- http.begin(url);
- int httpCode = http.GET();
- if (httpCode != HTTP_CODE_OK) {
- ESP_LOGE(TAG, "HTTP GET failed, error: %d", httpCode);
- http.end();
- return false;
- }
-
- // Read the response
- String payload = http.getString();
- http.end();
-
- //ESP_LOGD(TAG, "%s", payload.c_str());
-
- // Parse JSON
- JsonDocument jsonManifest;
- DeserializationError error = deserializeJson(jsonManifest, payload);
- if (error) {
- ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str());
- return false;
- }
-
- // Check for version section
- JsonObject jsonVersion = jsonManifest["version"];
-
- if (jsonVersion.isNull()) {
- ESP_LOGE(TAG, "No version section in manifest");
- return false;
- }
-
- // Get the remote version
- byte major = jsonVersion["major"] | 0;
- byte minor = jsonVersion["minor"] | 0;
- byte patch = jsonVersion["patch"] | 0;
- remoteVersion = {major, minor, patch};
- ESP_LOGI(TAG, "Remote version: %s", remoteVersion.toString().c_str());
- return true;
-}
-
-/*
-void setup() {
- Serial.begin(115200);
-
- // Initialize WiFi connection first
- // ... WiFi connection code ...
-
- // Initialize filesystem
- if(!LittleFS.begin()) {
- Serial.println("LittleFS Mount Failed");
- return;
- }
-
- // Create updater instance with:
- // - Current version: "1.0.0"
- // - Update server URL: "https://my-update-server.com/"
- // - Filesystem: LittleFS
- AppUpdater updater("1.0.0", "https://storage.googleapis.com/boothifier/latest/", LittleFS);
-
- // Set progress callback
- updater.setProgressCallback([](int progress) {
- Serial.printf("Update progress: %d%%\n", progress);
- });
-
- // Check and update firmware
- if (updater.checkAndUpdate()) {
- Serial.println("Update successful! Rebooting...");
- ESP.restart();
- }
-
- // Update specific files from manifest
- int updatedFiles = updater.updateFilesFromManifest("test_update.json");
- Serial.printf("Updated %d files\n", updatedFiles);
-}
-
-*/
\ No newline at end of file
diff --git a/temporary/AppUpgrade_orig.cpp b/temporary/AppUpgrade_orig.cpp
deleted file mode 100644
index 2d72a74..0000000
--- a/temporary/AppUpgrade_orig.cpp
+++ /dev/null
@@ -1,716 +0,0 @@
-#include "AppUpgrade.h"
-#include "esp_log.h"
-#include
-#include
-#include
-#include "global.h"
-#include "JsonConstrain.h"
-#include "BLE_UpdateService.h"
-#include
-#include
-#include
-
-static const char* TAG = "AppUpdater";
-TaskHandle_t Update_Task_Handle = NULL;
-TaskHandle_t versionCheckTask_Handle = NULL;
-volatile bool g_UpdateCancelFlag = false; // cancellation flag
-String updateUrl = "";
-Version otaVersion;
-
-
-AppUpdater::AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName, const char* appBin)
- : localVersion(localVersion), manifestName(manifestName), appName(appBin), fileSystem(fs), downloadBuffer(new uint8_t[BUFFER_SIZE])
-{
- baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
- // Ensure baseUrl ends with a single '/'
- if(!baseUrl.endsWith("/")) baseUrl += "/";
- ESP_LOGI(TAG, "AppUpdater initialized (local v%s) baseUrl=%s", localVersion.toString().c_str(), baseUrl.c_str());
-}
-
-void AppUpdater::setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)) {
- progressCb = callback;
-}
-
-void AppUpdater::updateProgress(UpdateStatus newStatus, int percentage, const char* message) {
- status = newStatus;
- if (progressCb) {
- progressCb(status, percentage, message);
- }
-}
-
-AppUpdater::ManifestCheckResult AppUpdater::checkManifest() {
- String url = buildUrl(manifestName);
- ESP_LOGD(TAG, "Fetching manifest from: %s", url.c_str());
-
- String payload;
- for(int attempt=0; attempt MAX_MANIFEST_SIZE){
- ESP_LOGE(TAG, "Manifest too large (%u bytes)", (unsigned)payload.length());
- return ManifestCheckResult::ERROR_TOO_LARGE;
- }
-
- // Parse JSON
- DeserializationError error = deserializeJson(jsonManifest, payload);
- ESP_LOGD(TAG, "Manifest deserialized");
- if (error) {
- ESP_LOGE(TAG, "Failed to parse manifest: %s", error.c_str());
- return ManifestCheckResult::ERROR_PARSE_FAILED;
- }
-
- // Check for files section
- jsonFilesArray = jsonManifest["files"];
- if (jsonFilesArray.isNull()) {
- ESP_LOGE(TAG, "No files section in manifest");
- return ManifestCheckResult::ERROR_NO_FILES_SECTION;
- }else{
- ESP_LOGD(TAG, "%d Files found", jsonFilesArray.size());
- }
-
- // Check for version section
- JsonObject jsonVersion = jsonManifest["version"];
- ESP_LOGD(TAG, "Version section found");
- if (jsonVersion.isNull()) {
- ESP_LOGE(TAG, "No version section in manifest");
- return ManifestCheckResult::ERROR_NO_VERSION;
- }
-
- // Get the remote version
- byte major = jsonVersion["major"] | 0;
- byte minor = jsonVersion["minor"] | 0;
- byte patch = jsonVersion["patch"] | 0;
- otaVersion = {major, minor, patch};
-
- //Version localVersion;
- //::sscanf(localVersion, "%d.%d.%d", &localVersion.major, &localVersion.minor, &localVersion.patch);
-
- // Check if an update is available
- updateAvailable = false;
- // Only mark update available if remote is strictly newer than local
- if (otaVersion <= localVersion) {
- ESP_LOGI(TAG, "No updates available: remote=%s, local=%s",
- otaVersion.toString().c_str(), localVersion.toString().c_str());
- return ManifestCheckResult::VERSION_CURRENT;
- }else{
- updateAvailable = true;
- ESP_LOGI(TAG, "Update available: remote=%s, local=%s",
- otaVersion.toString().c_str(), localVersion.toString().c_str());
- }
-
- //ESP_LOGD(TAG, "Manifest content: %s", payload.c_str());
-
- return ManifestCheckResult::UPDATE_AVAILABLE;
-}
-
-bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const char* expectedMd5) {
- //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
-
- // Construct full URL
- String url = buildUrl(remotePath);
- ESP_LOGD(TAG, "Downloading: %s -> %s", url.c_str(), localPath);
-
- // Quick skip: if exists and size & MD5 match
- bool skip = false;
- if(fileSystem.exists(localPath)){
- String localMd5 = getLocalMD5(localPath);
- if(localMd5.equals(expectedMd5)) skip = true;
- }
- if(skip){
- ESP_LOGI(TAG, "File already up to date: %s", localPath);
- updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
- return true;
- }
-
- // Start the download
- HTTPClient http;
- int httpCode = -1;
- for(int attempt=0; attempt 0) {
- // Single pass with known content length
- while (totalRead < contentLength) {
- if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
- size_t available = stream->available();
- if (available) {
- size_t readLen = stream->readBytes(downloadBuffer.get(), std::min(available, size_t(BUFFER_SIZE)));
-
- // Write to temp file and update MD5
- if (file.write(downloadBuffer.get(), readLen) != readLen) {
- ESP_LOGE(TAG, "Failed to write to temporary file");
-
- file.close();
- fileSystem.remove(tempPath.c_str());
- return false;
- }
-
- md5.add(downloadBuffer.get(), readLen);
- totalRead += readLen;
- updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / contentLength , localPath);
- }
- yield();
- }
- } else {
- // Unknown content length: read until stream ends
- for (;;) {
- if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; }
- size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
- if (readLen == 0) {
- break;
- }
- if (file.write(downloadBuffer.get(), readLen) != readLen) {
- ESP_LOGE(TAG, "Failed to write to temporary file");
- file.close();
- fileSystem.remove(tempPath.c_str());
- return false;
- }
- md5.add(downloadBuffer.get(), readLen);
- totalRead += readLen;
- // Progress unknown; emit periodic heartbeats at 0%
- // For unknown size, send heartbeats every ~16KB
- if((totalRead & 0x3FFF) == 0){
- updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
- }
- yield();
- }
- }
-
- file.close();
- md5.calculate();
- String calculatedMd5 = md5.toString();
-
- // Verify MD5 hash
- updateProgress(UpdateStatus::VERIFYING, 90, localPath);
- if (!calculatedMd5.equals(expectedMd5)) {
- //ESP_LOGE(TAG, "MD5 mismatch for %s", localPath);
- fileSystem.remove(tempPath.c_str());
- return false;
- }
-
- updateProgress(UpdateStatus::VERIFYING, 95, localPath);
-
- // Replace original file with verified temp file
- if (fileSystem.exists(localPath)) {
- fileSystem.remove(localPath);
- }
- if (!fileSystem.rename(tempPath.c_str(), localPath)) {
- ESP_LOGE(TAG, "Failed to rename temporary file");
- fileSystem.remove(tempPath.c_str());
- return false;
- }
-
- updateProgress(UpdateStatus::VERIFYING, 100, localPath);
- return true;
-}
-
-String AppUpdater::getLocalMD5(const char* filePath){
- File file = fileSystem.open(filePath, "r");
- if(!file){
- ESP_LOGE(TAG, "Error opening %s...", filePath);
- return String();
- }
-
- MD5Builder md5Builder;
- md5Builder.begin();
- size_t fileSize = file.size();
- size_t totalRead = 0;
- size_t readLen = 0;
- while (totalRead < fileSize) {
- readLen = file.readBytes(reinterpret_cast(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE)));
- md5Builder.add(downloadBuffer.get(), readLen);
- totalRead += readLen;
- }
-
- md5Builder.calculate();
- file.close();
- return md5Builder.toString();
-}
-
-bool AppUpdater::updateFilesArray() {
- int successCount = 0;
- int totalFiles = jsonFilesArray.size();
- ESP_LOGI(TAG, "Found %d files in manifest", totalFiles);
-
- // Iterate over each file entry in the manifest
- for (JsonObject file : jsonFilesArray) {
- const char* remotePath = file["path"];
- const char* localPath = remotePath;
- // If path begins with "data/" or "/data/" strip only the "data" portion, retaining the leading slash
- if (localPath) {
- if (strncmp(localPath, "data/", 5) == 0) {
- localPath += 4; // points to '/'
- } else if (strncmp(localPath, "/data/", 6) == 0) {
- localPath += 5; // points to '/'
- }
- }
- const char* expectedMd5 = file["md5"];
-
- // Skip invalid entries
- if (!remotePath || !localPath || !expectedMd5) {
- ESP_LOGE(TAG, "Invalid file entry in manifest");
- continue;
- }
-
- // Attempt to update the file
- if (updateFile(remotePath, localPath, expectedMd5)) {
- successCount++;
- }
- }
-
- ESP_LOGI(TAG, "Manifest update complete: %d/%d files updated", successCount, totalFiles);
- return successCount == totalFiles;
-}
-
-bool AppUpdater::updateApp() {
- updateProgress(UpdateStatus::MESSAGE, 0, "Starting firmware update");
-
- // Check for firmware section in manifest
- if (!jsonManifest["firmware"].is() || !jsonManifest["firmware"]["md5"].is()) {
- ESP_LOGE(TAG, "Invalid firmware section in manifest");
- updateProgress(UpdateStatus::ERROR, 0, "Firmware: Invalid firmware section in manifest");
- return false;
- }
-
- // Get the firmware MD5 hash and URL
- const char* expectedMd5 = jsonManifest["firmware"]["md5"];
- String firmwareUrl = buildUrl(appName);
-
- // Download the firmware
- HTTPClient http;
- int httpCode = -1;
- for(int attempt=0; attempt 0 ? firmwareSize : UPDATE_SIZE_UNKNOWN)) {
- ESP_LOGE(TAG, "Firmware: Not enough space for update");
- updateProgress(UpdateStatus::ERROR, 0, "Firmware: Not enough space for update");
- http.end();
- return false;
- }
-
- // Set up MD5 checking
- MD5Builder md5;
- md5.begin();
-
- // Download and verify firmware
- WiFiClient* stream = http.getStreamPtr();
- if (firmwareSize > 0) {
- size_t remaining = firmwareSize;
- while (remaining > 0) {
- if(g_UpdateCancelFlag){ Update.abort(); http.end(); return false; }
- size_t chunk = std::min(remaining, size_t(BUFFER_SIZE));
- size_t read = stream->readBytes(downloadBuffer.get(), chunk);
-
- // Check for timeout
- if (read == 0) {
- ESP_LOGE(TAG, "Read timeout");
- Update.abort();
- http.end();
- return false;
- }
-
- // Update MD5 and write firmware
- md5.add(downloadBuffer.get(), read);
- if (Update.write(downloadBuffer.get(), read) != read) {
- ESP_LOGE(TAG, "Write failed");
- Update.abort();
- http.end();
- return false;
- }
-
- remaining -= read;
- updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware");
- }
- } else {
- // Unknown size: stream until end
- for (;;) {
- if(g_UpdateCancelFlag){ Update.abort(); http.end(); return false; }
- size_t read = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
- if (read == 0) break;
- md5.add(downloadBuffer.get(), read);
- if (Update.write(downloadBuffer.get(), read) != read) {
- ESP_LOGE(TAG, "Write failed");
- Update.abort();
- http.end();
- return false;
- }
- updateProgress(UpdateStatus::DOWNLOADING, 0, "firmware");
- }
- }
-
- // Verify MD5
- md5.calculate();
- String calculatedMd5 = md5.toString();
- updateProgress(UpdateStatus::VERIFYING, 95, "firmware");
- if (!calculatedMd5.equals(expectedMd5)) {
- ESP_LOGE(TAG, "MD5 mismatch. Expected: %s, Got: %s", expectedMd5, calculatedMd5.c_str());
- updateProgress(UpdateStatus::MD5_FAILED, 0, "Firmware: MD5 mismatch");
- Update.abort();
- http.end();
- return false;
- }
-
- // Finish update
- if (!Update.end()) {
- ESP_LOGE(TAG, "Update end failed");
- updateProgress(UpdateStatus::ERROR, 0, "Firmware: Update failed");
- http.end();
- return false;
- }
-
- http.end();
- updateProgress(UpdateStatus::COMPLETE, 100, "Firmware: Complete");
- return true;
-}
-
-bool AppUpdater::IsUpdateAvailable(){
- return updateAvailable;
-}
-
-String AppUpdater::buildUrl(const char* path) const {
- if(!path || !*path) return baseUrl; // just base
- String p(path);
- // If already absolute URL, pass through
- if(p.startsWith("http://") || p.startsWith("https://")) return p;
- // Strip leading slashes to avoid double
- while(p.startsWith("/")) p.remove(0,1);
- // Ensure baseUrl has single trailing slash
- String b = baseUrl;
- if(!b.endsWith("/")) b += "/";
- return b + p;
-}
-
-
-AsyncEventSource* eventProgress = nullptr;
-void startFirmwareUpdateTask(AsyncEventSource* evProg) {
- eventProgress = evProg;
- if(Update_Task_Handle) {
- ESP_LOGW(TAG, "Firmware update task already running");
- return;
- }
- xTaskCreate(firmwareUpdateTask, "FirmwareUpdate", 1024*8, NULL, 1, &Update_Task_Handle);
-}
-
-void firmwareUpdateTask(void* parameter) {
- static const char* TAG = "UpdateTask";
- AppUpdater* updater = nullptr;
-
- try {
- loadUpdateJson();
-
- // Initialize updater
- updater = new AppUpdater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
- updater->setProgressCallback(updateProgress);
-
- ESP_LOGI(TAG, "Starting update check from: %s", updateUrl.c_str());
-
- // Check and perform updates
- auto manifestResult = updater->checkManifest();
-
- if (manifestResult != AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE) {
- // Handle different error cases
- std::string errorMsg;
- switch (manifestResult) {
- case AppUpdater::ManifestCheckResult::ERROR_FETCH_FAILED:
- errorMsg = "Failed to fetch manifest";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_TOO_LARGE:
- errorMsg = "Manifest file too large";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_PARSE_FAILED:
- errorMsg = "Failed to parse manifest";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_NO_FILES_SECTION:
- errorMsg = "Manifest missing files section";
- break;
- case AppUpdater::ManifestCheckResult::ERROR_NO_VERSION:
- errorMsg = "Manifest missing version section";
- break;
- case AppUpdater::ManifestCheckResult::VERSION_CURRENT:
- errorMsg = "Current version is up to date";
- // This is not actually an error
- ESP_LOGI(TAG, "No update needed: %s", errorMsg.c_str());
- throw std::runtime_error(errorMsg);
- break;
- default:
- errorMsg = "Unknown manifest check error";
- }
- throw std::runtime_error(errorMsg);
- }
-
- if (updater->IsUpdateAvailable()) {
- ESP_LOGI(TAG, "Update available, updating files...");
-
- if (!updater->updateFilesArray()) {
- throw std::runtime_error("Failed to update files");
- }
-
- ESP_LOGI(TAG, "Updating firmware...");
- if (!updater->updateApp()) {
- throw std::runtime_error("Failed to update firmware");
- }
- ESP_LOGI(TAG, "Update successful, restarting...");
-
- sendUpdateMessage("Restarting ", true, 100);
- vTaskDelay(2000);
-
- ESP.restart();
-
- }
-
- } catch (const std::exception& e) {
- ESP_LOGE(TAG, "Update failed: %s", e.what());
- }
- delete updater;
- Update_Task_Handle = NULL;
- vTaskDelete(NULL);
-}
-
-void startVersionCheckTask() {
- if(versionCheckTask_Handle != NULL) {
- ESP_LOGW(TAG, "Version Check Tak already running");
- return;
- }
- xTaskCreate(versionCheckTask, "VersionCheckTask", 1024*8, NULL, 1, &versionCheckTask_Handle);
-}
-
-void versionCheckTask(void* parameter){
- if(updateUrl == ""){
- loadUpdateJson();
- }
- AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
-
- auto manifestResult = updater.checkManifest();
-
- if (manifestResult == AppUpdater::ManifestCheckResult::UPDATE_AVAILABLE ||
- manifestResult == AppUpdater::ManifestCheckResult::VERSION_CURRENT) {
- otaVersion = updater.otaVersion; // capture remote
- ESP_LOGI(TAG, "Version check: remote=%s", otaVersion.toString().c_str());
- } else {
- ESP_LOGE(TAG, "Version check: manifest check failed with code %d", static_cast(manifestResult));
- }
-
- versionCheckTask_Handle = NULL;
- vTaskDelete(NULL);
-}
-
-void loadUpdateJson(void) {
- try {
- ESP_LOGD(TAG, "loadUpdateJaon function...");
- if(updateUrl == "") {
- String updateJsonPath = "/system/update.json";
-
- // Read and parse update.json
- File file = LittleFS.open(updateJsonPath);
- if (!file) {
- throw std::runtime_error("Failed to open update.json");
- }
-
- JsonDocument doc;
- DeserializationError error = deserializeJson(doc, file);
- file.close();
-
- if (error) { throw std::runtime_error("Failed to parse update.json"); }
-
- // Get update configuration
- JsonObject jObj = doc.as();
- String folderName = jsonConstrainString(TAG, jObj, "folder", "latest/");
- String baseUrl = jsonConstrainString(TAG, jObj, "baseurl", "https://s3-minio.boothwizard.com/boothifier/");
- updateUrl = baseUrl + folderName;
-
- ESP_LOGD(TAG, "updateUrl: %s", updateUrl.c_str());
- }
- } catch (const std::exception& e) {
- ESP_LOGE(TAG, "Update failed: %s", e.what());
- }
-}
-
-void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {
-
- char buffer[128];
- const char* msg;
- bool isComplete = false;
-
- const char* safeMsg = message ? message : "";
- switch (newStatus) {
- case AppUpdater::UpdateStatus::IDLE:
- snprintf(buffer, sizeof(buffer), "Update idle");
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::MESSAGE:
- msg = message ? message : "";
- break;
- case AppUpdater::UpdateStatus::DOWNLOADING:
- snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", safeMsg, percentage);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::VERIFYING:
- snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", safeMsg, percentage);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::FILE_SKIPPED:
- snprintf(buffer, sizeof(buffer), "%s: File Skipped, up to date", safeMsg);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::FILE_SAVED:
- snprintf(buffer, sizeof(buffer), "%s: File Saved", safeMsg);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::MD5_FAILED:
- snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", safeMsg);
- msg = buffer;
- break;
- case AppUpdater::UpdateStatus::COMPLETE:
- snprintf(buffer, sizeof(buffer), "Firmware Update Complete!!!");
- msg = buffer;
- isComplete = true;
- break;
- case AppUpdater::UpdateStatus::ERROR:
- snprintf(buffer, sizeof(buffer), "Error!: %s", safeMsg);
- msg = buffer;
- break;
- default:
- snprintf(buffer, sizeof(buffer), "Unknown update status: %d", (int)newStatus);
- msg = buffer;
- break;
- }
-
- ESP_LOGI(TAG, "%s", msg);
- sendUpdateMessage(msg, isComplete, percentage);
-}
-
-void sendUpdateMessage(const char* message, bool complete, int progress = -1) {
-
- if(eventProgress && eventProgress->count() > 0) {
- // This is for the web client and not the BLE client
- JsonDocument jsonDoc;
- jsonDoc["message"] = message;
- jsonDoc["complete"] = complete;
- jsonDoc["progress"] = progress;
- String strMessage;
- serializeJson(jsonDoc, strMessage);
- eventProgress->send(strMessage.c_str(), "update", millis());
- }
- else{
- ESP_LOGW(TAG, "No clients connected to event source");
- }
-
- bleUpgrade_send_message(message);
-}
-
-// (Removed duplicate global checkManifest; AppUpdater::checkManifest used instead)
-
-/*
-void setup() {
- Serial.begin(115200);
-
- // Initialize WiFi connection first
- // ... WiFi connection code ...
-
- // Initialize filesystem
- if(!LittleFS.begin()) {
- Serial.println("LittleFS Mount Failed");
- return;
- }
-
- // Create updater instance with:
- // - Current version: "1.0.0"
- // - Update server URL: "https://my-update-server.com/"
- // - Filesystem: LittleFS
- AppUpdater updater("1.0.0", "https://storage.googleapis.com/boothifier/latest/", LittleFS);
-
- // Set progress callback
- updater.setProgressCallback([](int progress) {
- Serial.printf("Update progress: %d%%\n", progress);
- });
-
- // Check and update firmware
- if (updater.checkAndUpdate()) {
- Serial.println("Update successful! Rebooting...");
- ESP.restart();
- }
-
- // Update specific files from manifest
- int updatedFiles = updater.updateFilesFromManifest("test_update.json");
- Serial.printf("Updated %d files\n", updatedFiles);
-}
-
-*/
\ No newline at end of file
diff --git a/temporary/Temp/ATALights2.cpp b/temporary/Temp/ATALights2.cpp
deleted file mode 100644
index d64a258..0000000
--- a/temporary/Temp/ATALights2.cpp
+++ /dev/null
@@ -1,178 +0,0 @@
-#include "ATALights.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "freertos/semphr.h"
-#include
-#include "Animations.h"
-#include
-#include
-
-
-static const char* tag = "strips";
-
-// Define constants for maximum LEDs
-#define MAX_LEDS 300 // Adjust based on your LED requirements
-
-// Create pointers for NeoPixelBus objects
-void* strip1 = nullptr;
-void* strip2 = nullptr;
-
-TaskHandle_t Animation_Task_Handle;
-
-// Runtime configuration variables
-int numLeds1 = 0, numLeds2 = 0; // Number of LEDs for each strip
-int dataPin1 = -1, dataPin2 = -1; // Data pins for each strip
-String chipType1, chipType2; // Chip types (e.g., WS2812, SK6812, TM1814)
-String colorOrder1, colorOrder2; // Color orders (e.g., GRB, RGB, BGR)
-
-
-void Init_Lights_Task(void){
-
- xTaskCreatePinnedToCore(Lights_Control_Task, "LumaMaster_Task", 1024*6, NULL, 1, &Animation_Task_Handle, CONFIG_ARDUINO_RUNNING_CORE);
- ESP_LOGI(tag, "Lights Task Created...");
-
-
- // Example runtime configuration for two strips
- dataPin1 = 5; // Pin for Strip 1
- numLeds1 = 150; // Number of LEDs on Strip 1
- chipType1 = "WS2812"; // Chip type for Strip 1
- colorOrder1 = "GRB"; // Color order for Strip 1
-
- dataPin2 = 18; // Pin for Strip 2
- numLeds2 = 100; // Number of LEDs on Strip 2
- chipType2 = "WS2812"; // Chip type for Strip 2
- colorOrder2 = "RGB"; // Color order for Strip 2
-
- // Dynamically initialize the strips
- strip1 = initializeStrip(dataPin1, numLeds1, chipType1, colorOrder1);
- strip2 = initializeStrip(dataPin2, numLeds2, chipType2, colorOrder2);
-
-
- // Start the strips if initialized
- if (strip1) static_cast*>(strip1)->Begin();
- if (strip2) static_cast*>(strip2)->Begin();
-}
-
-
-void Animation_Loop_Exit(void){
- if( Animation_Task_Handle ){
- xTaskNotifyGive( Animation_Task_Handle );
- }
-}
-
-
-void LightsON(void){
- FastLED.show();
-}
-
-
-void LightsOff(void){
- FastLED.clear();
- FastLED.show();
-}
-
-
-
-inline void setPixel1(int pixelIndex, const CRGB col) {
- register uint16_t x = pixelIndex + ledSettings[0].shift;
- // If strip.effSize is power of 2, use faster bit masking
- if ((ledSettings[0].effSize & (ledSettings[0].effSize - 1)) == 0) {
- x = (x < 0) ? ((x + ledSettings[0].effSize) & (ledSettings[0].effSize - 1)) : (x & (ledSettings[0].effSize - 1));
- leds1[(x + ledSettings[0].offset) & (ledSettings[0].effSize - 1)] = col;
- } else {
- // For non-power-of-2 sizes, still need modulo
- x = (x < 0) ? ((x + ledSettings[0].effSize) % ledSettings[0].effSize) : (x % ledSettings[0].effSize);
- leds1[(x + ledSettings[0].offset) % ledSettings[0].effSize] = col;
- }
-}
-
-
-inline void setPixel2(int pixelIndex, const CRGB col) {
- register uint16_t x = pixelIndex + ledSettings[1].shift;
- // If strip.effSize is power of 2, use faster bit masking
- if ((ledSettings[1].effSize & (ledSettings[1].effSize - 1)) == 0) {
- x = (x < 0) ? ((x + ledSettings[1].effSize) & (ledSettings[1].effSize - 1)) : (x & (ledSettings[1].effSize - 1));
- leds2[(x + ledSettings[1].offset) & (ledSettings[1].effSize - 1)] = col;
- } else {
- // For non-power-of-2 sizes, still need modulo
- x = (x < 0) ? ((x + ledSettings[1].effSize) % ledSettings[1].effSize) : (x % ledSettings[1].effSize);
- leds2[(x + ledSettings[1].offset) % ledSettings[1].effSize] = col;
- }
-}
-
-
-void Lights_Control_Task_Resume(void){
- vTaskResume(Animation_Task_Handle);
-}
-
-void Lights_Control_Task(void *parameters){
-
- ESP_LOGD(tag, "Lights Control Task Entered...");
- vTaskSuspend(NULL);
- ESP_LOGD(tag, "Lights Control Task Resumed...");
- vTaskDelay(2000);
- fill_solid(leds1, ledSettings[0].size-1, CRGB::Blue);
- FastLED.show();
- vTaskDelay(5000);
-
- while(true){
- Animation_Loop(2000, [&]() {
- ESP_LOGD(tag, "Looping....");
-
- // Example animation: Alternate colors between strips
- setStripColor>(strip1, numLeds1, RgbColor(255, 0, 0)); // Red for Strip 1
- setStripColor>(strip2, numLeds2, RgbColor(0, 255, 0)); // Green for Strip 2
- delay(1000);
-
- setStripColor>(strip1, numLeds1, RgbColor(0, 0, 255)); // Blue for Strip 1
- setStripColor>(strip2, numLeds2, RgbColor(255, 255, 0)); // Yellow for Strip 2
- delay(1000);
- });
-
- /*
- uint8_t animMode = 1;
- switch(animMode){
- case 0:
- break;
- case 1:
- break;
- }
- */
- }
-}
-
-
-void Init_FastLED_Strip(CRGB* leds, uint8_t pin, int size, EOrder rgbOrder, const String& chipType) {
-
-
-
-}
-
-// Function to initialize a strip dynamically (Non-SPI chipsets only)
-void* initializeStrip(int dataPin, int numLeds, const String& chipType, const String& colorOrder) {
- if (chipType == "WS2812" || chipType == "SK6812") {
- if (colorOrder == "GRB") {
- return new NeoPixelBus(numLeds, dataPin);
- } else if (colorOrder == "RGB") {
- return new NeoPixelBus(numLeds, dataPin);
- } else if (colorOrder == "BGR") {
- return new NeoPixelBus(numLeds, dataPin);
- }
- }
- Serial.println("Unsupported chipset or color order!");
- return nullptr;
-}
-
-// Function to set all LEDs of a strip to a specific color
-template
-void setStripColor(void* strip, int numLeds, RgbColor color) {
- if (strip) {
- T* actualStrip = static_cast(strip);
- for (int i = 0; i < numLeds; i++) {
- actualStrip->SetPixelColor(i, color);
- }
- actualStrip->Show();
- }
-}
-
-
diff --git a/temporary/Temp/BLE-FlashStick-Service.h b/temporary/Temp/BLE-FlashStick-Service.h
deleted file mode 100644
index 29260e8..0000000
--- a/temporary/Temp/BLE-FlashStick-Service.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-#include
-#include
diff --git a/temporary/Temp/BLE-FlaskStick-Service.cpp b/temporary/Temp/BLE-FlaskStick-Service.cpp
deleted file mode 100644
index 2172ffc..0000000
--- a/temporary/Temp/BLE-FlaskStick-Service.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-#include "BLE-FlashStick-Service.h"
-#include "WiFi.h"
-#include "my_wifi.h"
-#include "global.h"
-#include "AppUpgrade.h"
-#include "AppVersion.h"
-
-static const char *tag = "BLE_FlashStickService";
-
-#define UPGRADE_SERVICE_UUID "abcdef03-2345-6789-1234-56789abcdef0"
-#define UPGRADE_CHARACTERISTIC1_UUID "abcdef03-2345-6789-1234-56789abcdef1"
-
-NimBLEService *pUpgradeService = nullptr;
-NimBLECharacteristic *pUpgradeCharacteristic1 = nullptr;
-
-enum WIFI_STAT : byte { WIFI_DISCONNECTED=0, WIFI_BAD_CREDS=1, WIFI_NO_AP=2, WIFI_CONNECTED=3 };
-
-struct FLASHSTICK_PACKET {
- bool reistered = false;
- char msg[16] = "Hello...";
-}flashstickPacket;
-
-
-// Class for handling server events
-class ServerCallbacks : public NimBLEServerCallbacks {
- void onConnect(NimBLEServer* pServer) override {
- ESP_LOGI(tag, "Flash-Stick connected");
- }
-
- void onDisconnect(NimBLEServer* pServer) override {
- ESP_LOGI(tag, "Flash-Stick disconnected");
- }
-};
-
-
-// Class for handling characteristic events
-class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
-
- void onWrite(NimBLECharacteristic *pCharacteristic) override {
- std::string value = pCharacteristic->getValue();
- ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str());
-
- if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials
- JsonDocument doc;
- deserializeJson(doc, value.substr(13));
- JsonObject wifiJson = doc.as();
- String ssid = wifiJson["ssid"].as();
- String pass = wifiJson["pass"].as();
- ESP_LOGI(tag, "Wifi Credentials: %s, %s", ssid.c_str(), pass.c_str());
-
- bool status = StartWifiConnectTask(ssid, pass);
- if(status == true){
- updatePacket.wifiStatus = WIFI_DISCONNECTED;
- updatePacket.wifiOnline = false;
- updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
- }else{
- ESP_LOGI(tag, "Failed to start WiFi connection task");
- }
- }
- else if (value.compare("version-check") == 0) { // Check if new version is available
- ESP_LOGI(tag, "Version check command received: newVersion=%d.%d.%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch());
- if(updatePacket.newVersion[0] == 0){
- startVersionCheckTask(); // start the task and done
- }else{
- ESP_LOGI(tag, "Version already checked");
- }
- }
- else if (value.compare("upgrade-start") == 0) { // Start OTA update
- ESP_LOGI(tag, "Start OTA update command received");
- startFirmwareUpdateTask(nullptr); // start the task
- }
- else if (value.compare("rename-device") == 0) { // Start renaming device
- ESP_LOGI(tag, "Start renane device command received");
- }
- else {
- ESP_LOGW(tag, "Unknown command received: %s", value.c_str());
- }
- }
-
- void onRead(NimBLECharacteristic *pCharacteristic) override {
- updatePacket.wifiOnline = InternetAvailable;
- if(WiFi.status() == WL_CONNECTED){
- updatePacket.wifiStatus = WIFI_CONNECTED;
- if(updatePacket.wifiIP[0] == 0){
- updatePacket.wifiIP[0] = WiFi.localIP()[0];
- updatePacket.wifiIP[1] = WiFi.localIP()[1];
- updatePacket.wifiIP[2] = WiFi.localIP()[2];
- updatePacket.wifiIP[3] = WiFi.localIP()[3];
- }
- }else{
- updatePacket.wifiStatus = WIFI_DISCONNECTED;
- if(updatePacket.wifiIP[0] > 0){
- updatePacket.wifiIP[0] = 0;
- updatePacket.wifiIP[1] = 0;
- updatePacket.wifiIP[2] = 0;
- updatePacket.wifiIP[3] = 0;
- }
- }
-
- //update version
- if(otaVersion.major() != 0){
- ESP_LOGI(tag, "Updated new version: major=%d, minor=%d, patch=%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch());
- updatePacket.newVersion[0] = otaVersion.major();
- updatePacket.newVersion[1] = otaVersion.minor();
- updatePacket.newVersion[2] = otaVersion.patch();
- }
-
- pCharacteristic->setValue(reinterpret_cast(&updatePacket), sizeof(updatePacket));
- ESP_LOGI(tag, "Upgrade Char read");
- }
-};
-
-
-void bleUpgrade_send_message(String s){
- if(pUpgradeCharacteristic2){
- if (s != nullptr) {
- pUpgradeCharacteristic2->setValue(s);
- pUpgradeCharacteristic2->notify();
- } else {
- ESP_LOGW(tag, "Null string passed to bleUpgrade_send_message");
- }
- }
-}
-
-
-void Init_UpgradeBLEService(NimBLEServer *pServer){
-
- // Create Upgrade BLE Service
- pUpgradeService= pServer->createService( UPGRADE_SERVICE_UUID );
-
- pUpgradeCharacteristic1 = pUpgradeService->createCharacteristic(
- UPGRADE_CHARACTERISTIC1_UUID,
- NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
- );
-
- // Register the callback with the characteristic
- pUpgradeCharacteristic1->setCallbacks(new UpgradeChar_Callbacks());
- ESP_LOGI(tag, "Upgrade callback registered!");
-
-
- pUpgradeCharacteristic2 = pUpgradeService->createCharacteristic(
- UPGRADE_CHARACTERISTIC2_UUID,
- NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
- );
-
- // Register the callback with the characteristic
- pUpgradeCharacteristic2->setCallbacks(new UpgradeChar_Callbacks());
- ESP_LOGI(tag, "Upgrade callback registered!");
-
- pUpgradeService->start();
-
- NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
- pAdvertising->addServiceUUID( UPGRADE_SERVICE_UUID ); // Advertise service UUID
-
-
-}
-
diff --git a/temporary/Temp/BTSerial.cpp b/temporary/Temp/BTSerial.cpp
deleted file mode 100644
index 4295c4b..0000000
--- a/temporary/Temp/BTSerial.cpp
+++ /dev/null
@@ -1,240 +0,0 @@
-#include "BTSerial.h"
-#include
-#include
-#include
-#include
-#include
-#include