9-29 commit

This commit is contained in:
admin 2025-09-28 23:18:18 -07:00
parent 0040c64da8
commit 660fa5f1c7
171 changed files with 4689 additions and 25496 deletions

14
.clang-format Normal file
View File

@ -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

View File

@ -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)

View File

@ -1,5 +1,7 @@
{ {
"profile": "big-roam",
"mode": "roamer", "mode": "roamer",
"limited-mode": false,
"button": 0, "button": 0,
"buttons": "buttons":
[ [
@ -58,7 +60,7 @@
"en": true, "en": true,
"relay-index": 0, "relay-index": 0,
"button-index": 0, "button-index": 0,
"min": 0.0, "min": 1.0,
"max": 100.0, "max": 100.0,
"step": 1.5, "step": 1.5,
"rate": 30.0, "rate": 30.0,
@ -66,10 +68,10 @@
"vision": true "vision": true
}, },
{ {
"en": false, "en": true,
"relay-index": 1, "relay-index": 1,
"button-index": 1, "button-index": 1,
"min": 0.0, "min": 1.0,
"max": 100.0, "max": 100.0,
"step": 1.5, "step": 1.5,
"rate": 30.0, "rate": 30.0,
@ -106,9 +108,9 @@
"size": 168, "size": 168,
"chip": "SK6812", "chip": "SK6812",
"rgb-order": "rgb", "rgb-order": "rgb",
"shift":-5, "shift":-42,
"offset": 0, "offset": 0,
"bright": 200, "bright": 240,
"power-div": 0, "power-div": 0,
"i2s-ch": 0, "i2s-ch": 0,
"core": 1 "core": 1

View File

@ -1,5 +1,7 @@
{ {
"id": "custom",
"mode": 0, "mode": 0,
"limited-mode": false,
"buttons": "buttons":
[ [
{ {
@ -54,14 +56,26 @@
"ramp-lights": "ramp-lights":
[ [
{ {
"en": true, "en": true,
"relay-index": 0, "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": true,
"relay-index": 1, "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": { "oled": {

View File

@ -1,6 +1,8 @@
{ {
"mode": 0, "id": "flare-posh",
"buttons": "mode": 0,
"limited-mode": false,
"buttons":
[ [
{ {
"en": true "en": true
@ -56,12 +58,24 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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, "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": { "oled": {

View File

@ -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
}
}

View File

@ -1,4 +1,6 @@
{ {
"id": "m-lumia",
"limited-mode": false,
"mode": 0, "mode": 0,
"buttons": "buttons":
[ [
@ -56,12 +58,24 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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": true,
"relay-index": 1, "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": { "oled": {

View File

@ -1,4 +1,5 @@
{ {
"id": "m1",
"mode": 0, "mode": 0,
"buttons": "buttons":
[ [
@ -56,12 +57,24 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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": true,
"relay-index": 1, "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": { "oled": {

View File

@ -1,5 +1,7 @@
{ {
"id": "marquee",
"mode": 0, "mode": 0,
"limited-mode": false,
"buttons": "buttons":
[ [
{ {
@ -56,12 +58,24 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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": true,
"relay-index": 1, "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": { "oled": {

View File

@ -1,5 +1,7 @@
{ {
"id": "orig-roam",
"mode": "roamer", "mode": "roamer",
"limited-mode": false,
"button": 0, "button": 0,
"buttons": "buttons":
[ [
@ -58,19 +60,21 @@
"en": true, "en": true,
"relay-index": 0, "relay-index": 0,
"button-index": 0, "button-index": 0,
"min": 0.0, "min": 1.0,
"max": 100.0, "max": 100.0,
"step": 1.5, "step": 1.5,
"rate": 30.0,
"skip-count": 5, "skip-count": 5,
"vision": true "vision": true
}, },
{ {
"en": false, "en": true,
"relay-index": 1, "relay-index": 1,
"button-index": 1, "button-index": 1,
"min": 0.0, "min": 1.0,
"max": 100.0, "max": 100.0,
"step": 1.5, "step": 1.5,
"rate": 30.0,
"skip-count": 5, "skip-count": 5,
"vision": true "vision": true
} }

View File

@ -1,5 +1,7 @@
{ {
"id": "spectra",
"mode": 0, "mode": 0,
"limited-mode": false,
"buttons": "buttons":
[ [
{ {
@ -56,12 +58,24 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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, "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": { "oled": {

View File

@ -1,5 +1,7 @@
{ {
"id": "sport",
"mode": 0, "mode": 0,
"limited-mode": false,
"buttons": "buttons":
[ [
{ {
@ -56,12 +58,24 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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": true,
"relay-index": 1, "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": { "oled": {

View File

@ -1,4 +1,5 @@
{ {
"profile": "stik",
"mode": "stik", "mode": "stik",
"buttons": "buttons":
[ [
@ -56,13 +57,25 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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": true,
"relay-index": 1, "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": { "oled": {
"en": false, "en": false,

View File

@ -1,4 +1,5 @@
{ {
"profile": "testbooth",
"mode": 0, "mode": 0,
"buttons": "buttons":
[ [
@ -54,24 +55,26 @@
"ramp-lights": "ramp-lights":
[ [
{ {
"en": false, "en": true,
"relay-index": 0, "relay-index": 0,
"button-index": 0, "button-index": 0,
"min": 5.0, "min": 1.0,
"max": 100.0, "max": 100.0,
"step": 1.5, "step": 1.5,
"skip-count": 5, "rate": 30.0,
"vision": true "skip-count": 5,
"vision": true
}, },
{ {
"en": false, "en": true,
"relay-index": 1, "relay-index": 1,
"button-index": 1, "button-index": 1,
"min": 5.0, "min": 1.0,
"max": 100.0, "max": 100.0,
"step": 1.5, "step": 1.5,
"skip-count": 5, "rate": 30.0,
"vision": true "skip-count": 5,
"vision": true
} }
], ],
"oled": { "oled": {

View File

@ -1,5 +1,7 @@
{ {
"id": "xl-lumia",
"mode": 0, "mode": 0,
"limited-mode": false,
"buttons": "buttons":
[ [
{ {
@ -56,12 +58,24 @@
{ {
"en": true, "en": true,
"relay-index": 0, "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, "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": { "oled": {

View File

@ -1,5 +1,5 @@
{ {
"name": "ATALIGHTS", "name": "SP110E-ATA",
"unique": true, "unique": true,
"lights-service": "FFE0", "lights-service": "FFE0",
"lights-char": "FFE1", "lights-char": "FFE1",

View File

@ -1,4 +1,4 @@
{ {
"boardfile": "/boards/board15.json", "boardfile": "/boards/board15.json",
"configfile": "/booths/roamer-big.json" "configfile": "/booths/big-roam.json"
} }

View File

@ -589,67 +589,67 @@ IMPORTANT NOTES:
try{ try{
bleDevice = await navigator.bluetooth.requestDevice({ bleDevice = await navigator.bluetooth.requestDevice({
filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }], filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }],
optionalServices:[BLE_SERVICE_UUID] optionalServices:[BLE_SERVICE_UUID]
}); });
const server = await bleDevice.gatt.connect(); const server = await bleDevice.gatt.connect();
const service = await server.getPrimaryService(BLE_SERVICE_UUID); const service = await server.getPrimaryService(BLE_SERVICE_UUID);
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID); bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID); bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
await bleCharacteristic2.startNotifications(); await bleCharacteristic2.startNotifications();
let prevProgressFound = false; let prevProgressFound = false;
bleCharacteristic2.addEventListener('characteristicvaluechanged', e => { bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
try{ try{
//const view = e.target.value; // DataView //const view = e.target.value; // DataView
//const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength); //const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
// Debug info // Debug info
//let debugInfo = `Received ${bytes.length} bytes: `; //let debugInfo = `Received ${bytes.length} bytes: `;
//for (let i = 0; i < bytes.length; i++) { //for (let i = 0; i < bytes.length; i++) {
// debugInfo += bytes[i].toString(16).padStart(2, '0') + ' '; // debugInfo += bytes[i].toString(16).padStart(2, '0') + ' ';
// } // }
//console.log(debugInfo); //console.log(debugInfo);
// Try to decode as text // Try to decode as text
let txt = ''; let txt = '';
try { try {
//txt = new TextDecoder().decode(bytes); //txt = new TextDecoder().decode(bytes);
txt = new TextDecoder().decode(e.target.value); txt = new TextDecoder().decode(e.target.value);
// Remove null terminators and trim // Remove null terminators and trim
const nullIdx = txt.indexOf('\0'); const nullIdx = txt.indexOf('\0');
if (nullIdx !== -1) txt = txt.slice(0, nullIdx); if (nullIdx !== -1) txt = txt.slice(0, nullIdx);
txt = txt.trim(); txt = txt.trim();
} catch (decodeErr) { } catch (decodeErr) {
console.error('Text decode error:', decodeErr); console.error('Text decode error:', decodeErr);
// Fallback to showing hex if text decode fails // Fallback to showing hex if text decode fails
txt = '[Binary data]'; txt = '[Binary data]';
} }
// Show both raw and processed data in console // Show both raw and processed data in console
//console.log('--> Raw bytes:', bytes); //console.log('--> Raw bytes:', bytes);
//console.log('--> As text:', txt); //console.log('--> As text:', txt);
//logMessage(`--> (${bytes.length} bytes) ${txt}`); //logMessage(`--> (${bytes.length} bytes) ${txt}`);
// progress messages handling variable captured from outer scope // progress messages handling variable captured from outer scope
if(prevProgressFound && txt.includes('progress')){ if(prevProgressFound && txt.includes('progress')){
logMessage(`${txt}`, true); // overwrite last line for progress updates logMessage(`${txt}`, true); // overwrite last line for progress updates
} }
else{ else{
logMessage(`${txt}`); logMessage(`${txt}`);
prevProgressFound = false; // reset if current message isn't progress prevProgressFound = false; // reset if current message isn't progress
} }
if(txt.includes('progress')){ if(txt.includes('progress')){
prevProgressFound = true; prevProgressFound = true;
} }
} catch(err) { } catch(err) {
console.error('Processing error', err); console.error('Processing error', err);
logMessage('--> Error processing message: ' + err.message); logMessage('--> Error processing message: ' + err.message);
} }
}); });
bleConnected=true; bleConnected=true;

1324
data/www/usercfg.html Normal file

File diff suppressed because it is too large Load Diff

BIN
docs/SP110E Info.xlsx Normal file

Binary file not shown.

24
docs/ToDo.txt Normal file
View File

@ -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 <ArduinoJson.h>
permits: // or /* lsslslslslsl */
7) Use same struct of setting for to and from in ble
8) learn about Blob in BLE
######### Configuration Page ############
done

BIN
docs/~$SP110E Info.xlsx Normal file

Binary file not shown.

View File

@ -2,9 +2,6 @@
"folders": [ "folders": [
{ {
"path": "." "path": "."
},
{
"path": "../esp32s3_module_8mb"
} }
], ],
"settings": { "settings": {

View File

@ -5,13 +5,21 @@
#include "ColorPalettes.h" #include "ColorPalettes.h"
#include "PWM_Output.h" #include "PWM_Output.h"
#define PIXEL_INDEX -3 #define SHIFT_INDEX -3
#define SOLID_COLOR_INDEX -2 #define SOLID_COLOR_INDEX -2
#define OFF_INDEX -1 #define OFF_INDEX -1
#define WITH_GAPS true #define WITH_GAPS true
#define NO_GAPS false #define NO_GAPS false
enum CHIP_TYPE {
CHIP_WS2812B = 0,
CHIP_SK6812,
CHIP_WS2811,
CHIP_WS2815,
CHIP_UNKNOWN
};
extern uint32_t whiteTimeout; extern uint32_t whiteTimeout;
typedef struct { typedef struct {
@ -39,19 +47,53 @@ typedef struct {
int shift; int shift;
int offset; int offset;
int powerDiv; int powerDiv;
int effSize; //int effSize;
uint8_t bright; uint8_t bright;
uint8_t i2sCh; uint8_t i2sCh;
uint8_t core; uint8_t core;
uint8_t pin; uint8_t pin;
}LEDSTRIP_SETTINGS; }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]; extern LEDSTRIP_SETTINGS ledSettings[2];
void RGB_Lights_Control_Task(void *parameters); 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); 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 loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src);
void SetAndSaveUserSettings(USER_SETTINGS &userSettings);
//void Init_Ramp_Front_Light_Task(void); //void Init_Ramp_Front_Light_Task(void);

View File

@ -8,6 +8,7 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "AppVersion.h" #include "AppVersion.h"
//#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/" //#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/"
#define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/" #define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/"
#define BUFFER_SIZE 2048 // Reduced from 4096 to use less memory #define BUFFER_SIZE 2048 // Reduced from 4096 to use less memory
@ -171,6 +172,7 @@ class AppUpdater {
fs::FS& fileSystem; fs::FS& fileSystem;
UpdateStatus status; UpdateStatus status;
std::unique_ptr<uint8_t[]> downloadBuffer; std::unique_ptr<uint8_t[]> downloadBuffer;
size_t downloadBufferSize = 0; /**< Actual size allocated for downloadBuffer */
bool updateAvailable = false; bool updateAvailable = false;
UpdateMode updateMode = UpdateMode::UPDATE_BOTH; // Default to updating both files and firmware 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 startFirmwareUpdateTask(AsyncEventSource* eventSrc);
void loadUpdateJson(void); bool loadUpdateJson(void);
void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message); void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message);
@ -243,3 +245,5 @@ void setUpdateModeFilesOnly();
void setUpdateModeFirmwareOnly(); void setUpdateModeFirmwareOnly();
void setUpdateModeBoth(); void setUpdateModeBoth();

View File

@ -19,6 +19,11 @@
#define SET_SPEED 0x03 #define SET_SPEED 0x03
#define SET_AUTO_MODE 0x06 #define SET_AUTO_MODE 0x06
// My custom commands
#define SET_USER_SETTINGS 0xB0
#define SET_SHIFT 0xB1
// Initializes the BLE server, services, and advertising // Initializes the BLE server, services, and advertising
void Init_BLE_SP110E(NimBLEServer* pServer); void Init_BLE_SP110E(NimBLEServer* pServer);

View File

@ -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_ORANGE_WHITE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::White } };
const COLOR_PACK colorPack_GREEN_WHITE PROGMEM = { 2, { CRGB::Green, 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_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_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 } }; 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_ORANGE_BLUE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Blue } };
const COLOR_PACK colorPack_RED_GREEN PROGMEM = { 2, { CRGB::Red, CRGB::Green } }; 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_CYAN_RED PROGMEM = { 2, { CRGB::Cyan, CRGB::Red } };
const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } };
// Warm/cool combinations // Warm/cool combinations
const COLOR_PACK colorPack_ORANGE_CYAN PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Cyan } }; 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 } }; const COLOR_PACK colorPack_VIOLET_LIME PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Lime } };
// Analogous combinations // Analogous combinations
@ -67,28 +66,28 @@ const COLOR_PACK double_colorPacks[] PROGMEM = {
colorPack_RED_WHITE, colorPack_RED_WHITE,
colorPack_RED_YELLOW, colorPack_RED_YELLOW,
colorPack_BLUE_WHITE, colorPack_BLUE_WHITE,
colorPack_PINK_WHITE,
colorPack_GREEN_WHITE,
colorPack_PURPLE_WHITE, colorPack_PURPLE_WHITE,
colorPack_GREEN_YELLOW, colorPack_MAGENTA_WHITE,
colorPack_PURPLE_PINK, colorPack_GREEN_WHITE,
colorPack_CYAN_RED,
colorPack_ORANGE_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_PURPLE_YELLOW,
colorPack_BLUE_YELLOW, colorPack_BLUE_YELLOW,
colorPack_BLUE_GREEN, colorPack_BLUE_GREEN,
colorPack_BLUE_RED, colorPack_BLUE_RED,
colorPack_ORANGE_BLUE, colorPack_ORANGE_BLUE,
// 16 pack
colorPack_RED_GREEN, colorPack_RED_GREEN,
colorPack_MAGENTA_GREEN, colorPack_MAGENTA_GREEN,
colorPack_ORANGE_CYAN, colorPack_ORANGE_CYAN,
colorPack_PINK_GREEN,
colorPack_VIOLET_LIME, colorPack_VIOLET_LIME,
colorPack_RED_ORANGE, colorPack_RED_ORANGE,
colorPack_BLUE_PURPLE, colorPack_BLUE_PURPLE,
@ -101,12 +100,12 @@ const COLOR_PACK double_colorPacks[] PROGMEM = {
// Sectors // 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 // 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_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_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_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_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 } }; 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_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_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_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Green } };
const COLOR_PACK colorPack_BLUE_YELLOW_RED PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Red } }; const COLOR_PACK colorPack_BLUE_YELLOW_RED PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Red } };
// Additional triple color combinations // 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_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_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 } }; 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_GREEN_WHITE_ORANGE,
colorPack_BLUE_YELLOW_GREEN, colorPack_BLUE_YELLOW_GREEN,
colorPack_RED_GREEN_BLUE, colorPack_RED_GREEN_BLUE,
colorPack_PURPLE_PINK_CYAN, colorPack_MAGENTA_WHITE_CYAN,
colorPack_YELLOW_ORANGE_RED, colorPack_YELLOW_ORANGE_RED,
colorPack_BLUE_CYAN_LIME, colorPack_BLUE_CYAN_LIME,
colorPack_PINK_PURPLE_MAGENTA,
colorPack_RED_YELLOW_BLUE, colorPack_RED_YELLOW_BLUE,
// 9 pack
colorPack_RED_ORANGE_YELLOW, colorPack_RED_ORANGE_YELLOW,
colorPack_RED_YELLOW_GREEN, colorPack_RED_YELLOW_GREEN,
colorPack_RED_PURPLE_BLUE, colorPack_RED_PURPLE_BLUE,
colorPack_GREEN_WHITE_RED, colorPack_GREEN_WHITE_RED,
colorPack_GREEN_WHITE_BLUE, colorPack_GREEN_WHITE_BLUE,
colorPack_BLUE_WHITE_GREEN,
colorPack_BLUE_YELLOW_RED, colorPack_BLUE_YELLOW_RED,
colorPack_GREEN_CYAN_BLUE,
// 16 pack
colorPack_ORANGE_YELLOW_LIME, colorPack_ORANGE_YELLOW_LIME,
colorPack_MAGENTA_YELLOW_CYAN, colorPack_MAGENTA_YELLOW_CYAN,
colorPack_LIME_CYAN_MAGENTA, colorPack_LIME_CYAN_MAGENTA,
colorPack_RED_ORANGE_PINK, colorPack_RED_ORANGE_PINK,
colorPack_PURPLE_BLUE_CYAN, colorPack_PURPLE_BLUE_CYAN,
colorPack_GREEN_CYAN_BLUE,
colorPack_YELLOW_PURPLE_ORANGE, colorPack_YELLOW_PURPLE_ORANGE,
colorPack_PINK_LIME_PURPLE
}; };
const COLOR_PACK colorPack_grad_blueish PROGMEM = { 3, { CRGB::Blue, CRGB::Cyan, CRGB::Green } }; 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_Magenta PROGMEM = { 1, { CRGB::Magenta } };
const COLOR_PACK colorPack_Single_White PROGMEM = { 1, { CRGB::White } }; const COLOR_PACK colorPack_Single_White PROGMEM = { 1, { CRGB::White } };
// 9 Single Color Packs // 8 Single Color Packs
const COLOR_PACK single_colorPacks[] PROGMEM = { const COLOR_PACK single_colorPacks[] PROGMEM = {
colorPack_Single_White, colorPack_Single_White,
colorPack_Single_Red, colorPack_Single_Red,

View File

@ -38,6 +38,8 @@ class PWM_Output {
float standardFactor; float standardFactor;
float visionFactor; float visionFactor;
int8_t pin;
bool initialized = false;
}; };

View File

@ -18,7 +18,7 @@ private:
float currentValue; float currentValue;
bool IsOn = false; bool IsOn = false;
RAMP_STATE rampState; RAMP_STATE rampState;
int tickCount; int tickCount = 0;
void tick(); void tick();
void singleClick(); void singleClick();

View File

@ -60,3 +60,5 @@ void Log_CPU_Load(void);
void print_task_watermarks(void); void print_task_watermarks(void);
float updateLowpass(float currentValue, float newValue, float alpha);

View File

@ -9,6 +9,10 @@ extern SYS_SETTINGS sys_settings;
extern PWM_Output *pwmOutputs[4]; extern PWM_Output *pwmOutputs[4];
extern RAMP_LIGHT *rampLight1; extern RAMP_LIGHT *rampLight1;
extern RAMP_LIGHT *rampLight2; extern RAMP_LIGHT *rampLight2;
extern String booth_file_path;
extern float PowerVin;
extern float PowerVinAlpha;
extern float prevPowerVin;
void Init_ADC(void); void Init_ADC(void);
float readBoardInputVoltage(void); float readBoardInputVoltage(void);

View File

@ -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 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 fileManagerHtmlProcessor(const String& var);
String HomeHtmlProcessor(const String& var); String HomeHtmlProcessor(const String& var);
String listDirAsHtml(String directoryList[], int count); String listDirAsHtml(String directoryList[], int count);

View File

@ -5,6 +5,9 @@
#include <FastLED.h> #include <FastLED.h>
#include "ATALights.h" #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 { typedef struct {
bool enabled; bool enabled;
}BTN_SETTINGS; }BTN_SETTINGS;
@ -79,6 +82,8 @@ typedef struct {
enum BOOTH_MODE { BOOTH_MODE_NONE=0, BOOTH_MODE_ROAMER=1, BOOTH_MODE_STIK=2 }; enum BOOTH_MODE { BOOTH_MODE_NONE=0, BOOTH_MODE_ROAMER=1, BOOTH_MODE_STIK=2 };
typedef struct { typedef struct {
String profile;
bool limitedMode;
BOOTH_MODE mode; BOOTH_MODE mode;
BOARD_PINS boardPins; BOARD_PINS boardPins;
BTN_SETTINGS btnSettings[3]; BTN_SETTINGS btnSettings[3];

View File

@ -24,6 +24,7 @@ lib_deps =
jeremycole/I2C Temperature Sensors derived from the LM75 @ ^1.0.3 jeremycole/I2C Temperature Sensors derived from the LM75 @ ^1.0.3
mathertel/OneButton @ ^2.6.1 mathertel/OneButton @ ^2.6.1
h2zero/NimBLE-Arduino @ ^1.4.1 h2zero/NimBLE-Arduino @ ^1.4.1
#h2zero/NimBLE-Arduino @ ^2.3.6
adafruit/Adafruit SSD1306 @ ^2.5.7 adafruit/Adafruit SSD1306 @ ^2.5.7
fastled/FastLED @ ^3.9.4 fastled/FastLED @ ^3.9.4
marian-craciunescu/ESP32Ping@^1.7 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_VERBOSE
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO
-D CONFIG_ARDUHAL_LOG_COLORS=1 -D CONFIG_ARDUHAL_LOG_COLORS=1
#-Os
#-ffunction-sections
#-fdata-sections
#-Wl,--gc-sections
upload_port = COM5 upload_port = COM5
debug_init_break = tbreak setup debug_init_break = tbreak setup
monitor_port = COM5 monitor_port = COM5

View File

@ -9,16 +9,16 @@
#include "system.h" #include "system.h"
#include "ColorPalettes.h" #include "ColorPalettes.h"
#include "global.h" #include "global.h"
//#include <crgb.h>
#include "PWM_Output.h" #include "PWM_Output.h"
#include "my_device.h" #include "my_device.h"
#include "system.h" #include "system.h"
#include "my_device.h" #include "my_device.h"
#include "LittleFS.h"
#define FASTLED_CORE 0 #define FASTLED_CORE 0
static const char* tag = "strips"; static const char* tag = "strips";
uint32_t whiteTimeout = 0; //uint32_t whiteTimeout = 0;
TaskHandle_t Animation_Task_Handle; TaskHandle_t Animation_Task_Handle;
TaskHandle_t Ramp_Front_Light_Task_Handle; TaskHandle_t Ramp_Front_Light_Task_Handle;
@ -27,7 +27,13 @@ volatile bool AnimationLooping = false;
ANIM_EVENT prevAnimEvent = {0}; ANIM_EVENT prevAnimEvent = {0};
QueueHandle_t animationQueue = xQueueCreate( 4, sizeof( ANIM_EVENT ) ); 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){ 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(){ 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(){ void RGB_Animations_OFF(){
@ -63,7 +70,9 @@ void Lights_Set_White(uint8_t val){
//pwmOut[0]->setOutput(val / 2.5f); //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]; 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); 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); 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; 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<int>( (static_cast<uint32_t>(x)) & (static_cast<uint32_t>(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) { inline void setPixel1(LEDSTRIP_SETTINGS& leds, int pixelIndex, const CRGB col) {
register uint16_t x = pixelIndex + ledSettings[0].shift; register uint16_t x = pixelIndex + ledSettings[0].shift;
// If strip.effSize is power of 2, use faster bit masking // If strip.size is power of 2, use faster bit masking
if ((leds.effSize & (leds.effSize - 1)) == 0) { if ((leds.size & (leds.size - 1)) == 0) {
x = (x < 0) ? ((x + leds.effSize) & (leds.effSize - 1)) : (x & (leds.effSize - 1)); x = (x < 0) ? ((x + leds.size) & (leds.size - 1)) : (x & (leds.size - 1));
leds.leds[(x + leds.offset) & (leds.effSize - 1)] = col; leds.leds[(x + leds.offset) & (leds.size - 1)] = col;
} else { } else {
// For non-power-of-2 sizes, still need modulo // For non-power-of-2 sizes, still need modulo
x = (x < 0) ? ((x + ledSettings[0].effSize) % ledSettings[0].effSize) : (x % ledSettings[0].effSize); x = (x < 0) ? ((x + ledSettings[0].size) % ledSettings[0].size) : (x % ledSettings[0].size);
ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].effSize] = col; ledSettings[0].leds[(x + ledSettings[0].offset) % ledSettings[0].size] = col;
} }
} }
*/
inline void setPixel2(int pixelIndex, const CRGB col) { inline void setPixel2(int pixelIndex, const CRGB col) {
register uint16_t x = pixelIndex + ledSettings[1].shift; register uint16_t x = pixelIndex + ledSettings[1].shift;
// If strip.effSize is power of 2, use faster bit masking // If strip.size is power of 2, use faster bit masking
if ((ledSettings[1].effSize & (ledSettings[1].effSize - 1)) == 0) { if ((ledSettings[1].size & (ledSettings[1].size - 1)) == 0) {
x = (x < 0) ? ((x + ledSettings[1].effSize) & (ledSettings[1].effSize - 1)) : (x & (ledSettings[1].effSize - 1)); 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].effSize - 1)] = col; ledSettings[1].leds[(x + ledSettings[1].offset) & (ledSettings[1].size - 1)] = col;
} else { } else {
// For non-power-of-2 sizes, still need modulo // For non-power-of-2 sizes, still need modulo
x = (x < 0) ? ((x + ledSettings[1].effSize) % ledSettings[1].effSize) : (x % ledSettings[1].effSize); x = (x < 0) ? ((x + ledSettings[1].size) % ledSettings[1].size) : (x % ledSettings[1].size);
ledSettings[1].leds[(x + ledSettings[1].offset) % ledSettings[1].effSize] = col; 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){ if(pin == 3){
// First level: Chip type selection // First level: Chip type selection
if (chipUpper == "WS2812B" || chipUpper == "SK6812") { if (chipUpper == "WS2812B") {
switch(rgbOrder) { switch(rgbOrder) {
case RGB: FastLED.addLeds<WS2812B, 3, RGB>(leds, size); break; case RGB: FastLED.addLeds<WS2812B, 3, RGB>(leds, size); break;
case RBG: FastLED.addLeds<WS2812B, 3, RBG>(leds, size); break; case RBG: FastLED.addLeds<WS2812B, 3, RBG>(leds, size); break;
@ -308,7 +347,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
default: FastLED.addLeds<SK6812, 3, GRB>(leds, size); break; default: FastLED.addLeds<SK6812, 3, GRB>(leds, size); break;
} }
} }
else if (chipUpper == "WS2811_400") { else if (chipUpper == "WS2811") {
switch(rgbOrder) { switch(rgbOrder) {
case RGB: FastLED.addLeds<WS2811_400, 3, RGB>(leds, size); break; case RGB: FastLED.addLeds<WS2811_400, 3, RGB>(leds, size); break;
case RBG: FastLED.addLeds<WS2811_400, 3, RBG>(leds, size); break; case RBG: FastLED.addLeds<WS2811_400, 3, RBG>(leds, size); break;
@ -361,7 +400,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
default: FastLED.addLeds<SK6812, 9, GRB>(leds, size); break; default: FastLED.addLeds<SK6812, 9, GRB>(leds, size); break;
} }
} }
else if (chipUpper == "WS2811_400") { else if (chipUpper == "WS2811") {
switch(rgbOrder) { switch(rgbOrder) {
case RGB: FastLED.addLeds<WS2811_400, 9, RGB>(leds, size); break; case RGB: FastLED.addLeds<WS2811_400, 9, RGB>(leds, size); break;
case RBG: FastLED.addLeds<WS2811_400, 9, RBG>(leds, size); break; case RBG: FastLED.addLeds<WS2811_400, 9, RBG>(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){ else if(pin == 46){
// First level: Chip type selection // First level: Chip type selection
if (chipUpper == "WS2812B" || chipUpper == "SK6812") { if (chipUpper == "WS2812B") {
switch(rgbOrder) { switch(rgbOrder) {
case RGB: FastLED.addLeds<WS2812B, 46, RGB>(leds, size); break; case RGB: FastLED.addLeds<WS2812B, 46, RGB>(leds, size); break;
case RBG: FastLED.addLeds<WS2812B, 46, RBG>(leds, size); break; case RBG: FastLED.addLeds<WS2812B, 46, RBG>(leds, size); break;
@ -414,7 +453,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
default: FastLED.addLeds<SK6812, 46, GRB>(leds, size); break; default: FastLED.addLeds<SK6812, 46, GRB>(leds, size); break;
} }
} }
else if (chipUpper == "WS2811_400") { else if (chipUpper == "WS2811") {
switch(rgbOrder) { switch(rgbOrder) {
case RGB: FastLED.addLeds<WS2811_400, 46, RGB>(leds, size); break; case RGB: FastLED.addLeds<WS2811_400, 46, RGB>(leds, size); break;
case RBG: FastLED.addLeds<WS2811_400, 46, RBG>(leds, size); break; case RBG: FastLED.addLeds<WS2811_400, 46, RBG>(leds, size); break;
@ -439,7 +478,7 @@ void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder,
else { else {
// Default to WS2812B if unknown chip type // Default to WS2812B if unknown chip type
ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str()); ESP_LOGW(tag, "Unknown LED chip type: %s, defaulting to WS2812B", chipType.c_str());
FastLED.addLeds<WS2812B, 9, GRB>(leds, size); FastLED.addLeds<WS2812B, 46, GRB>(leds, size);
} }
ESP_LOGI(tag, "Initialized %s LED strip with %d LEDs on pin %d", chipType.c_str(), size, pin); 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){ 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; ANIM_EVENT AnimEvent;
CRGB col;
COLOR_PACK colorPack; if (LIMITED_ANIMATION){
CRGBPalette16 firePalette; 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) case -3: // Set Shift
vTaskDelay(pdMS_TO_TICKS(500)); // wait for everything to settle if (AnimEvent.data.data[0] >= 0 && AnimEvent.data.data[0] < ledSettings[0].size) {
fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
RGB_Lights_Set_Brightness(48); ledSettings[0].shift = AnimEvent.data.data[0];
RGB_Lights_Set_Animation(23, 0, 0, 0); // set comet rainbow animation vTaskDelay(25 / portTICK_PERIOD_MS);
setPixel1(ledSettings[0], 0, CRGB::White * FastLED.getBrightness() / 255);
FastLED.show();
while (true) { ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift);
if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) { } else {
ESP_LOGI(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex); ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[0]);
switch (AnimEvent.AnimationIndex) { }
case -3: // Set Pixel by index break;
if (AnimEvent.data.data[7] >= 0 && AnimEvent.data.data[7] < ledSettings[0].size) { case -2: // Fill Static Color
ledSettings[0].leds[AnimEvent.data.data[7]] = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu); fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu));
FastLED.show(); FastLED.show();
} else { ESP_LOGD(tag, "Static Color");
ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[7]); 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 6:
case -2: // Fill Static Color // RGB White and Dedicated White all on with timeout and brightness reduction
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) {
Anim_SolidWhite(AnimationLooping, ledSettings[0].leds, pwmOutputs[0], ledSettings[0].size, 240, 30000); 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 18 ... 20: // Sparkle/Twinkle
case 7: Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60); break;
break; case 21:
case 8: case 9: case 10: case 11: case 12: Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 7000, 90);
Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80); break;
break; case 22: // Rain Animation
case 13: case 14: case 15: case 16: case 17: { // Fire Animations Anim_Morph(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 20, 40);
COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable break;
createFirePalette(firePalette, fp);
Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift); // Comets
break; 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 AnimationLooping = false;
case 23: prevAnimEvent = AnimEvent;
Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1); ESP_LOGD(tag, "Going to Queue to Wait");
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;
} }
}
} 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; ESP_LOGD(tag, "Set Shift: %d", ledSettings[0].shift);
prevAnimEvent = AnimEvent; vTaskDelay(25 / portTICK_PERIOD_MS);
ESP_LOGD(tag, "Going to Queue to Wait"); 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) { void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack) {
for (uint8_t i = 0; i < 16; i++) { for (uint8_t i = 0; i < 16; i++) {
if (i < 3) palette[i] = CRGB::Black; if (i < 3) palette[i] = CRGB::Black;

View File

@ -27,6 +27,7 @@ void Animation_Init(void){
} }
// Animation Loop Template // Animation Loop Template
/*
void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<int()> callback) { void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<int()> callback) {
if (!callback) { if (!callback) {
ESP_LOGE("Animation_Loop", "Invalid callback function"); ESP_LOGE("Animation_Loop", "Invalid callback function");
@ -65,7 +66,62 @@ void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<in
loop_active_flag = false; loop_active_flag = false;
} }
*/
void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<int()> 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<int()> callback) { void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int()> callback) {
if (!callback) { if (!callback) {
ESP_LOGE("Animation_Loop", "Invalid callback function"); ESP_LOGE("Animation_Loop", "Invalid callback function");
@ -105,8 +161,74 @@ void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int(
loop_active_flag = false; loop_active_flag = false;
} }
*/
void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int()> 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 // Animation Loop Template
/*
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function<int()> callback) { void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function<int()> callback) {
loop_active_flag = true; loop_active_flag = true;
speed = constrain(speed, 0, MaxSpeed); 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; loop_active_flag = false;
} }
*/
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function<int()> 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 // Animation Loop Template
/*
void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function<int()> callback) { void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function<int()> callback) {
loop_active_flag = true; loop_active_flag = true;
uint32_t loop_cycle_count = 0; 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; loop_active_flag = false;
} }
*/
void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function<int()> 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 COMET_FADE_FACTOR COMET_FADE_FACTOR2
#define MAX_COMETS 8 // Maximum number of comets supported #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<int[]> 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) { void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier) {
// Validate inputs // Validate inputs
int numComets = colorPack.size; int numComets = colorPack.size;
@ -479,8 +655,8 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
return; return;
} }
// Calculate comet size // Calculate comet size (use float math and round to avoid integer-division truncation)
int cometSize = (size / totalComets) * COMET_SIZE_FACTOR; int cometSize = (int)(((float)size * (float)COMET_SIZE_FACTOR) / (float)totalComets + 0.5f);
if (cometSize < 1) cometSize = 1; if (cometSize < 1) cometSize = 1;
// Set fade factor // Set fade factor
@ -500,9 +676,14 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
int pos; int pos;
CRGB color; CRGB color;
int speed = minSpeed; 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 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 { try {
Animation_Loop_Variable(activeFlag, [&]() -> int { Animation_Loop_Variable(activeFlag, [&]() -> int {
// Fade all LEDs // Fade all LEDs
@ -558,7 +739,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
FastLED.show(); 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); 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"); 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<bool> activeFlag with std::atomic<bool> 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) 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) { if (flashElapsed >= flashTimeout) {
// Flash timeout expired, set PWM to minimum // Flash timeout expired, set PWM to minimum
pwmOut->setOutput(pwmMin); pwmOut->setOutput(pwmMin);
// exit the loop
activeFlag = false;
ESP_LOGI("Anim_TimedFill_Flash", "Flash timeout expired, PWM set to minimum"); ESP_LOGI("Anim_TimedFill_Flash", "Flash timeout expired, PWM set to minimum");
} }
// Continue in infinite loop regardless of timeout status // 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 // No animation, just maintain the solid white state
return 0; 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 FastLED.setBrightness(origBright); // Restore original brightness
} }

View File

@ -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 if (buffer_size < 1024) buffer_size = 1024; // Absolute minimum is 1KB
downloadBuffer.reset(new uint8_t[buffer_size]); downloadBuffer.reset(new uint8_t[buffer_size]);
downloadBufferSize = buffer_size;
baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL); baseUrl = bucket ? String(bucket) : String(DEFAULT_MANIFEST_URL);
// Ensure baseUrl ends with a single '/' // Ensure baseUrl ends with a single '/'
@ -81,6 +82,7 @@ AppUpdater::ManifestCheckResult AppUpdater::checkManifest() {
http.end(); http.end();
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS)); if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
} }
if(payload.isEmpty()){ if(payload.isEmpty()){
ESP_LOGE(TAG, "Failed to fetch manifest after retries"); ESP_LOGE(TAG, "Failed to fetch manifest after retries");
return ManifestCheckResult::ERROR_FETCH_FAILED; return ManifestCheckResult::ERROR_FETCH_FAILED;
@ -159,6 +161,7 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const
} else { } else {
ESP_LOGI(TAG, "Local file does not exist: %s", localPath); ESP_LOGI(TAG, "Local file does not exist: %s", localPath);
} }
if(skip){ if(skip){
ESP_LOGI(TAG, "File already up to date: %s", localPath); ESP_LOGI(TAG, "File already up to date: %s", localPath);
updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath); updateProgress(UpdateStatus::FILE_SKIPPED, 100, localPath);
@ -179,9 +182,12 @@ bool AppUpdater::updateFile(const char* remotePath, const char* localPath, const
http.end(); http.end();
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS)); if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
} }
if (httpCode != HTTP_CODE_OK) { if (httpCode != HTTP_CODE_OK) {
ESP_LOGE(TAG, "Download failed for %s: HTTP code %d", localPath, httpCode); 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(); http.end();
return false; 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) 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 const int MAX_RETRIES = 2; // Maximum number of retries for MD5 failure
for (int retry = 0; retry <= MAX_RETRIES; retry++) { 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) { if (retry > 0) {
ESP_LOGW(TAG, "Retrying download of %s (attempt %d/%d)", localPath, retry, MAX_RETRIES); 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 // Need to re-fetch the file for retry - use the REMOTE path, not local path
String url = buildUrl(remotePath); String url = buildUrl(remotePath);
HTTPClient http; retryHttp.begin(url);
http.begin(url); int httpCode = retryHttp.GET();
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) { if (httpCode != HTTP_CODE_OK) {
ESP_LOGE(TAG, "Retry download failed: %d", httpCode); ESP_LOGE(TAG, "Retry download failed: %d", httpCode);
http.end(); retryHttp.end();
continue; // Try next retry if available continue; // Try next retry if available
} }
stream = http.getStreamPtr(); activeStream = retryHttp.getStreamPtr();
contentLength = http.getSize(); activeContentLength = retryHttp.getSize();
} }
MD5Builder md5; MD5Builder md5;
md5.begin(); md5.begin();
size_t totalRead = 0; size_t totalRead = 0;
// Create temporary filename in the same directory as the target file // Create temporary filename in the same directory as the target file
String targetDir = String(localPath); String targetDir = String(localPath);
int lastSlash = targetDir.lastIndexOf('/'); int lastSlash = targetDir.lastIndexOf('/');
String tempPath; String tempPath;
if (lastSlash >= 0) { if (lastSlash >= 0) {
// Extract directory and filename // Extract directory and filename
String directory = targetDir.substring(0, lastSlash + 1); 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 // File is in root directory
tempPath = "/temp_" + String(localPath) + ".download"; tempPath = "/temp_" + String(localPath) + ".download";
} }
// Clean up any existing temp file first // Clean up any existing temp file first
if (fileSystem.exists(tempPath.c_str())) { if (fileSystem.exists(tempPath.c_str())) {
ESP_LOGW(TAG, "Removing existing temp file: %s", tempPath.c_str()); ESP_LOGW(TAG, "Removing existing temp file: %s", tempPath.c_str());
fileSystem.remove(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, "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) // 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 File file = fileSystem.open(tempPath.c_str(), FILE_WRITE, true); // true = create if not exists
if (!file) { if (!file) {
ESP_LOGE(TAG, "Failed to open temporary file for writing: %s", tempPath.c_str()); ESP_LOGE(TAG, "Failed to open temporary file for writing: %s", tempPath.c_str());
// Try to diagnose the issue // Try to diagnose the issue
ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes", ESP_LOGE(TAG, "LittleFS info - Used: %u bytes, Total: %u bytes",
LittleFS.usedBytes(), LittleFS.totalBytes()); (unsigned)LittleFS.usedBytes(), (unsigned)LittleFS.totalBytes());
// Check if we're out of space // Check if we're out of space
if (LittleFS.usedBytes() >= LittleFS.totalBytes() * 0.95) { if (LittleFS.usedBytes() >= LittleFS.totalBytes() * 0.95) {
ESP_LOGE(TAG, "LittleFS nearly full - may not have space for temp file"); ESP_LOGE(TAG, "LittleFS nearly full - may not have space for temp file");
} }
if (retry > 0) retryHttp.end();
return false; return false;
} }
//updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); // Defensive: ensure download buffer exists
if (!downloadBuffer || downloadBufferSize == 0) {
if (contentLength > 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 // Single pass with known content length
while (totalRead < contentLength) { while (totalRead < activeContentLength) {
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; } if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; }
size_t available = stream->available(); size_t available = activeStream->available();
if (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 // Write to temp file and update MD5
if (file.write(downloadBuffer.get(), readLen) != readLen) { if (file.write(downloadBuffer.get(), readLen) != readLen) {
ESP_LOGE(TAG, "Failed to write to temporary file"); ESP_LOGE(TAG, "Failed to write to temporary file");
file.close(); file.close();
fileSystem.remove(tempPath.c_str()); fileSystem.remove(tempPath.c_str());
if (retry > 0) retryHttp.end();
return false; return false;
} }
md5.add(downloadBuffer.get(), readLen); md5.add(downloadBuffer.get(), readLen);
totalRead += readLen; totalRead += readLen;
updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / contentLength , localPath); updateProgress(UpdateStatus::DOWNLOADING, (totalRead * 80) / activeContentLength , localPath);
} }
yield(); yield();
} }
} else { } else {
// Unknown content length: read until stream ends // Unknown content length: read until stream ends
for (;;) { for (;;) {
if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); return false; } if(g_UpdateCancelFlag){ file.close(); fileSystem.remove(tempPath.c_str()); if (retry > 0) retryHttp.end(); return false; }
size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE); size_t readLen = activeStream->readBytes(downloadBuffer.get(), downloadBufferSize);
if (readLen == 0) { if (readLen == 0) {
break; break;
} }
@ -314,6 +345,7 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
ESP_LOGE(TAG, "Failed to write to temporary file"); ESP_LOGE(TAG, "Failed to write to temporary file");
file.close(); file.close();
fileSystem.remove(tempPath.c_str()); fileSystem.remove(tempPath.c_str());
if (retry > 0) retryHttp.end();
return false; return false;
} }
md5.add(downloadBuffer.get(), readLen); md5.add(downloadBuffer.get(), readLen);
@ -326,44 +358,45 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
yield(); yield();
} }
} }
file.close(); file.close();
md5.calculate(); md5.calculate();
String calculatedMd5 = md5.toString(); String calculatedMd5 = md5.toString();
// Verify MD5 hash // Verify MD5 hash
updateProgress(UpdateStatus::VERIFYING, 90, localPath); 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()); localPath, expectedMd5, calculatedMd5.c_str());
// Compare MD5 case-insensitively (in case there are case differences) // Compare MD5 case-insensitively (in case there are case differences)
String expectedMd5Lower = String(expectedMd5); String expectedMd5Lower = String(expectedMd5);
expectedMd5Lower.toLowerCase(); expectedMd5Lower.toLowerCase();
String calculatedMd5Lower = calculatedMd5; String calculatedMd5Lower = calculatedMd5;
calculatedMd5Lower.toLowerCase(); calculatedMd5Lower.toLowerCase();
if (!calculatedMd5Lower.equals(expectedMd5Lower)) { 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()); localPath, retry+1, MAX_RETRIES+1, expectedMd5, calculatedMd5.c_str());
ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars", ESP_LOGE(TAG, "Length comparison - Expected: %d chars, Got: %d chars",
strlen(expectedMd5), calculatedMd5.length()); (int)strlen(expectedMd5), (int)calculatedMd5.length());
fileSystem.remove(tempPath.c_str()); fileSystem.remove(tempPath.c_str());
if (retry < MAX_RETRIES) { if (retry < MAX_RETRIES) {
// Will retry in next loop iteration // Will retry in next loop iteration
if (retry > 0) retryHttp.end();
continue; continue;
} }
// Special case for certain file types - allow them to be used even with MD5 mismatch // 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 // This is a fallback option for non-critical files like HTML pages
bool isNonCriticalFile = false; bool isNonCriticalFile = false;
if (String(localPath).endsWith(".html") || if (String(localPath).endsWith(".html") ||
String(localPath).endsWith(".css") || String(localPath).endsWith(".css") ||
String(localPath).endsWith(".js")) { String(localPath).endsWith(".js")) {
isNonCriticalFile = true; isNonCriticalFile = true;
} }
if (isNonCriticalFile) { if (isNonCriticalFile) {
ESP_LOGW(TAG, "Using file %s despite MD5 mismatch (non-critical file)", localPath); 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 // 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 // Rename the temp file to the final location
if (fileSystem.exists(localPath)) { if (fileSystem.exists(localPath)) {
fileSystem.remove(localPath); fileSystem.remove(localPath);
} }
if (!fileSystem.rename(tempPath.c_str(), 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); tempPath.c_str(), localPath);
fileSystem.remove(tempPath.c_str()); fileSystem.remove(tempPath.c_str());
if (retry > 0) retryHttp.end();
return false; return false;
} }
if (retry > 0) retryHttp.end();
// Return false to indicate verification failure, but the file will still be used // Return false to indicate verification failure, but the file will still be used
return false; return false;
} }
if (retry > 0) retryHttp.end();
return false; return false;
} }
updateProgress(UpdateStatus::VERIFYING, 95, localPath); updateProgress(UpdateStatus::VERIFYING, 95, localPath);
// Ensure target directory exists before rename // Ensure target directory exists before rename
String dirPath = String(localPath); String dirPath = String(localPath);
int targetLastSlash = dirPath.lastIndexOf('/'); int targetLastSlash = dirPath.lastIndexOf('/');
@ -419,7 +455,7 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
} }
} }
} }
// Replace original file with verified temp file // Replace original file with verified temp file
if (fileSystem.exists(localPath)) { if (fileSystem.exists(localPath)) {
fileSystem.remove(localPath); fileSystem.remove(localPath);
@ -427,13 +463,15 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
if (!fileSystem.rename(tempPath.c_str(), localPath)) { if (!fileSystem.rename(tempPath.c_str(), localPath)) {
ESP_LOGE(TAG, "Failed to rename temporary file: %s -> %s", tempPath.c_str(), localPath); ESP_LOGE(TAG, "Failed to rename temporary file: %s -> %s", tempPath.c_str(), localPath);
fileSystem.remove(tempPath.c_str()); fileSystem.remove(tempPath.c_str());
if (retry > 0) retryHttp.end();
return false; return false;
} }
updateProgress(UpdateStatus::VERIFYING, 100, localPath); updateProgress(UpdateStatus::VERIFYING, 100, localPath);
if (retry > 0) retryHttp.end();
return true; return true;
} }
return false; // All retries failed return false; // All retries failed
} }
@ -450,7 +488,7 @@ String AppUpdater::getLocalMD5(const char* filePath){
size_t totalRead = 0; size_t totalRead = 0;
size_t readLen = 0; size_t readLen = 0;
while (totalRead < fileSize) { while (totalRead < fileSize) {
readLen = file.readBytes(reinterpret_cast<char*>(downloadBuffer.get()), std::min(fileSize - totalRead, size_t(BUFFER_SIZE))); readLen = file.readBytes(reinterpret_cast<char*>(downloadBuffer.get()), std::min(fileSize - totalRead, downloadBufferSize));
md5Builder.add(downloadBuffer.get(), readLen); md5Builder.add(downloadBuffer.get(), readLen);
totalRead += readLen; totalRead += readLen;
} }
@ -569,6 +607,7 @@ bool AppUpdater::updateApp() {
if (httpCode != HTTP_CODE_OK) { if (httpCode != HTTP_CODE_OK) {
ESP_LOGE(TAG, "Firmware download failed: %d", httpCode); ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed"); updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
http.end();
return false; return false;
} }
@ -662,12 +701,12 @@ bool AppUpdater::updateApp() {
while (remaining > 0 && failedReads < MAX_FAILED_READS) { while (remaining > 0 && failedReads < MAX_FAILED_READS) {
// Check for cancellation // Check for cancellation
if(g_UpdateCancelFlag) { if(g_UpdateCancelFlag) {
ESP_LOGE(TAG, "Update cancelled by user"); ESP_LOGE(TAG, "Update cancelled by user");
Update.abort(); Update.abort();
http.end(); http.end();
return false; return false;
} }
// Check WiFi status every 5 seconds // Check WiFi status every 5 seconds
if (millis() - lastWatchdogKick > 5000) { if (millis() - lastWatchdogKick > 5000) {
@ -735,8 +774,9 @@ bool AppUpdater::updateApp() {
// Send periodic progress updates to keep client informed // Send periodic progress updates to keep client informed
if (millis() - lastWatchdogKick > 5000) { if (millis() - lastWatchdogKick > 5000) {
int percent = (totalReceived * 100) / firmwareSize; int percent = (totalReceived * 100) / firmwareSize;
updateProgress(UpdateStatus::DOWNLOADING, percent, char buf[64];
String("Firmware: " + String(percent) + "% - waiting for data...").c_str()); snprintf(buf, sizeof(buf), "Firmware: %d%% - waiting for data...", percent);
updateProgress(UpdateStatus::DOWNLOADING, percent, buf);
} }
delay(100); // Short delay to prevent CPU hogging delay(100); // Short delay to prevent CPU hogging
@ -782,8 +822,9 @@ bool AppUpdater::updateApp() {
lastProgressTime = millis(); lastProgressTime = millis();
// Just update with received byte count since we don't know total // Just update with received byte count since we don't know total
updateProgress(UpdateStatus::DOWNLOADING, 0, char buf2[64];
String("Firmware: " + String(totalReceived / 1024) + "KB received").c_str()); snprintf(buf2, sizeof(buf2), "Firmware: %uKB received", (unsigned)(totalReceived / 1024));
updateProgress(UpdateStatus::DOWNLOADING, 0, buf2);
} else { } else {
emptyReads++; emptyReads++;
delay(100); 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_init(60, true); // 60 second timeout, panic on timeout
esp_task_wdt_add(NULL); // Add current task to watchdog esp_task_wdt_add(NULL); // Add current task to watchdog
try { // Load update.json; proceed only if successful
loadUpdateJson(); if (!loadUpdateJson()) {
ESP_LOGE(TAG, "Failed to load update.json, aborting update task");
esp_task_wdt_reset(); // Reset watchdog timer after JSON loading // Clean up watchdog and exit task
esp_task_wdt_delete(NULL);
// Initialize updater with smart pointer Update_Task_Handle = NULL;
std::unique_ptr<AppUpdater> updater(new AppUpdater( vTaskDelete(NULL);
LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin")); return;
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");
} }
esp_task_wdt_reset(); // Reset watchdog timer after JSON loading
// Initialize updater with smart pointer
std::unique_ptr<AppUpdater> 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: cleanup:
// Clean up watchdog before exit // Clean up watchdog before exit
@ -1007,7 +1055,12 @@ void startVersionCheckTask() {
void versionCheckTask(void* parameter){ void versionCheckTask(void* parameter){
if(updateUrl == ""){ 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"); AppUpdater updater(LittleFS, localVersion, updateUrl.c_str(), "manifest.json", "firmware.bin");
@ -1025,35 +1078,36 @@ void versionCheckTask(void* parameter){
vTaskDelete(NULL); vTaskDelete(NULL);
} }
void loadUpdateJson(void) { bool loadUpdateJson(void) {
try { ESP_LOGD(TAG, "loadUpdateJson function...");
ESP_LOGD(TAG, "loadUpdateJaon function..."); if(updateUrl == "") {
if(updateUrl == "") { String updateJsonPath = "/system/update.json";
String updateJsonPath = "/system/update.json";
// Read and parse update.json // Read and parse update.json
File file = LittleFS.open(updateJsonPath); File file = LittleFS.open(updateJsonPath);
if (!file) { if (!file) {
throw std::runtime_error("Failed to open update.json"); ESP_LOGE(TAG, "Failed to open update.json");
} return false;
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<JsonObject>();
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()); 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<JsonObject>();
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) { void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const char* message = nullptr) {

View File

@ -5,6 +5,9 @@
#include "ATALights.h" #include "ATALights.h"
#include "BleSettings.h" #include "BleSettings.h"
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
#include "my_device.h"
#include "global.h" // for get_chip_mac
#include "esp_log.h"
static const char *tag = "BLE_SP110E"; 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_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0"
//#define UPGRADE_CHARACTERISTIC_UUID "abcdef01-2345-6789-1234-56789abcdef1" //#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 {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; //typedef enum {RGB,RBG,GRB,GBR,BRG,BGR,SEQUENCE_COUNT} SEQUENCES;
@ -133,6 +135,78 @@ public:
process_BLE_SP110E_Command(data, procLen, pCharacteristic); 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 caseinsensitive 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: private:
static void logBytes(const uint8_t* data, size_t len) { static void logBytes(const uint8_t* data, size_t len) {
if (!data) return; 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) { void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacteristic* bleChar) {
if (!val) { //uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer
ESP_LOGE(tag, "Null command data received");
return; 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; }
if (len < 4) { //ESP_LOGI(tag, "USER_SETTING size is: %d", sizeof(USER_SETTINGS));
ESP_LOGW(tag, "Command too short: %d bytes, expected at least 4", len);
return; 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, data0: %d, data1: %d, data2: %d, length: %d", command, val[0], val[1], val[2], len);
//ESP_LOGI(tag, "Command received: 0x%02X, length: %d", command, len);
uint8_t response[sizeof(INFO_PACK)]; // Use a single response buffer
// Handle different commands // Handle different commands
switch (command) { switch (command) {
@ -273,7 +351,7 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
break; break;
case SET_SPEED: case SET_SPEED:
led_status.speed = val[0]; 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; break;
case GET_CHECK_DEVICE: // This prepends a checksum case GET_CHECK_DEVICE: // This prepends a checksum
led_status.checksum = calculateChecksum(val); 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: case SET_DEVICE_NAME:
ESP_LOGI(tag, "Set Device Name"); ESP_LOGI(tag, "Set Device Name");
break; 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: default:
ESP_LOGW(tag, "Unknown command: 0x%02X", command); 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 // Create FFE1 Characteristic with WRITE and NOTIFY properties
pSP110ECharacteristic = pService->createCharacteristic( pSP110ECharacteristic = pService->createCharacteristic(
BTSP110ECharacteristicUUID.c_str(), BTSP110ECharacteristicUUID.c_str(),
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
); );
// Register the callback with the characteristic // Register the callback with the characteristic
@ -422,11 +533,8 @@ void BLE_LightStick_Client_Task(void *parameter) {
// Get the characteristic. // Get the characteristic.
pRemoteCharacteristic = pRemoteService->getCharacteristic(BTStickCharacteristicUUID.c_str()); pRemoteCharacteristic = pRemoteService->getCharacteristic(BTStickCharacteristicUUID.c_str());
if (pRemoteCharacteristic != nullptr && pRemoteCharacteristic->canNotify()) { if (pRemoteCharacteristic != nullptr && pRemoteCharacteristic->canNotify()) {
pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic, pRemoteCharacteristic->subscribe(true, [](NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
uint8_t* pData, size_t length, bool isNotify) { ESP_LOGD(tag, "Notification received: %zu bytes", length);
Serial.print("Notification received: ");
Serial.write(pData, length);
Serial.println();
process_BLE_SP110E_Command(pData, length, nullptr); process_BLE_SP110E_Command(pData, length, nullptr);
}); });
} else { } else {
@ -483,7 +591,7 @@ class MyAdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {
NimBLEDevice::getScan()->stop(); NimBLEDevice::getScan()->stop();
myDevice = advertisedDevice; myDevice = advertisedDevice;
masterFound = true; masterFound = true;
Serial.println("Device found!"); ESP_LOGE(tag, "Device found!");
} }
} }
}; };

View File

@ -38,35 +38,87 @@ class ServerCallbacks : public NimBLEServerCallbacks {
class ServerCallbacks : public NimBLEServerCallbacks { class ServerCallbacks : public NimBLEServerCallbacks {
public: public:
void onConnect(NimBLEServer* /*pServer*/) override { ServerCallbacks() : connectedClients(0) {}
ESP_LOGI(tag, "Client connected");
ensureAdvertising("onConnect"); 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); 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 { void onDisconnect(NimBLEServer* /*pServer*/) override {
ESP_LOGI(tag, "Client disconnected"); if (connectedClients > 0) connectedClients--;
ensureAdvertising("onDisconnect"); 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); Buzzer_Play_Tune(TUNE_DISCONNECTED, 1);
} }
private: private:
void ensureAdvertising(const char* reason) { int connectedClients;
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);
}
}
}; };

View File

@ -70,8 +70,6 @@ void Load_BLE_Settings(const String &configPath) {
BTDeviceName += macSuffix[0]; // Add first character BTDeviceName += macSuffix[0]; // Add first character
BTDeviceName += macSuffix[1]; // Add second 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", 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(), BTDeviceName.c_str(), BTServiceUUID.c_str(), BTSP110ECharacteristicUUID.c_str(),
BTStickCharacteristicUUID.c_str(), BTUpgradeServiceUUID.c_str(), BTStickCharacteristicUUID.c_str(), BTUpgradeServiceUUID.c_str(),

View File

@ -3,11 +3,7 @@
#include "global.h" #include "global.h"
const char* tag = "pwmout"; static 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
};
PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected){ PWM_Output::PWM_Output(int8_t pin, uint8_t ch, uint8_t res, uint32_t freq, float maxDuty, bool visionCorrected){
this->currDuty = 0; 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->maxDuty = maxDuty;
this->visionCorrected = visionCorrected; this->visionCorrected = visionCorrected;
this->freq = freq; this->freq = freq;
this->pin = pin;
setResolution(res); setResolution(res);
pinMode(pin, OUTPUT); pinMode(pin, OUTPUT);
uint32_t actualFreq = ledcSetup(ch, freq, res); uint32_t actualFreq = ledcSetup(ch, freq, res);
if (actualFreq != freq) { if (actualFreq != freq) {
ESP_LOGE(tag, "pwmOut-> ch:%d ledcSetup failed! Requested freq: %d, Actual freq: %d", ch, freq, actualFreq); 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; return;
} }
ledcAttachPin(pin, ch); ledcAttachPin(pin, ch);
setOutput(this->currDuty); setOutput(this->currDuty);
this->initialized = true;
} }
void PWM_Output::setMaxDuty(float duty) { void PWM_Output::setMaxDuty(float duty) {
@ -41,6 +41,10 @@ void PWM_Output::setMaxDuty(float duty) {
// Range is 0 to 100% // Range is 0 to 100%
void PWM_Output::setOutput(float duty){ 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] // Clamp the duty cycle to the range [0.0, maxDuty]
if (duty < 0.0) { if (duty < 0.0) {
duty = 0.0; duty = 0.0;
@ -50,15 +54,14 @@ void PWM_Output::setOutput(float duty){
// calculate correct duty value // calculate correct duty value
int outDutyVal; int outDutyVal;
if(this->visionCorrected){ if(this->visionCorrected){
outDutyVal = linearizeOutput(duty); outDutyVal = linearizeOutput(duty);
} } else {
else{
outDutyVal = static_cast<int>(duty * this->standardFactor); outDutyVal = static_cast<int>(duty * this->standardFactor);
} }
// Clamp to valid resolution range [0, 2^res - 1] // Clamp to valid resolution range [0, (1<<res)-1]
int maxVal = static_cast<int>(binaryPow[this->res]); int maxVal = (this->res >= 31) ? INT32_MAX : ((1 << this->res) - 1);
if (outDutyVal < 0) outDutyVal = 0; if (outDutyVal < 0) outDutyVal = 0;
if (outDutyVal > maxVal) outDutyVal = maxVal; if (outDutyVal > maxVal) outDutyVal = maxVal;
@ -72,8 +75,10 @@ void PWM_Output::setFreq(uint32_t fq){
uint32_t newFreq; uint32_t newFreq;
if(this->freq != fq){ if(this->freq != fq){
newFreq = ledcChangeFrequency(this->ch, fq, this->res); newFreq = ledcChangeFrequency(this->ch, fq, this->res);
if(newFreq){ if(newFreq > 0){
this->freq = fq; 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; if(this->res > 16) this->res = 16;
// Use the clamped resolution when computing factors // Use the clamped resolution when computing factors
this->standardFactor = binaryPow[this->res] * 0.01f; int maxVal = (this->res >= 31) ? INT32_MAX : ((1 << this->res) - 1);
this->visionFactor = binaryPow[this->res] * 0.0001f; this->standardFactor = static_cast<float>(maxVal) * 0.01f;
this->visionFactor = static_cast<float>(maxVal) * 0.0001f;
ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor); ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor);
} }

View File

@ -14,9 +14,12 @@ RAMP_LIGHT::RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, float min, floa
button->attachLongPressStop([](void* context) { static_cast<RAMP_LIGHT*>(context)->longPressStop(); }, this); button->attachLongPressStop([](void* context) { static_cast<RAMP_LIGHT*>(context)->longPressStop(); }, this);
button->attachDuringLongPress([](void* context) { static_cast<RAMP_LIGHT*>(context)->duringLongPress(); }, this); button->attachDuringLongPress([](void* context) { static_cast<RAMP_LIGHT*>(context)->duringLongPress(); }, this);
if(min < 0.0) min = 0.0;
if(max > 100.0) max = 100.0; if(min < 0.0) this->min = 0.0;
currentValue = min; 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; IsOn = false;
rampState = RampingUp; rampState = RampingUp;
} }
@ -52,11 +55,17 @@ void RAMP_LIGHT::longPressStop(){
void RAMP_LIGHT::duringLongPress(){ void RAMP_LIGHT::duringLongPress(){
if(IsOn){ if(IsOn){
if (tickCount > 0 && --tickCount == 0) { if (tickCount > 0 && --tickCount == 0) {
// When ramping down, currentValue may go below min if step is large; constrain ensures bounds. // Adjust currentValue based on ramp direction
// Ensure currentValue stays within [min, max] bounds for safe PWM operation if(rampState == RampingUp){
currentValue = constrain(currentValue, min, max); currentValue += step;
pwmOutput->setOutput(currentValue); } else {
//ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput->currOutVal, pwmOutput->getOutVal()); 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; tickCount = TickDelayCount;
} }
} }

View File

@ -23,23 +23,31 @@ void Init_File_System(void){
//printAllSystemFiles(); //printAllSystemFiles();
} }
void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){ void getAllDirectories(String directoryList[], int &count){
File root = LittleFS.open("/"); File root = LittleFS.open("/");
if (!root || !root.isDirectory()){ if (!root || !root.isDirectory()){
ESP_LOGE("FileSystem", "Failed to open root directory or root is not a directory"); ESP_LOGE("FileSystem", "Failed to open root directory or root is not a directory");
return; return;
} }
// Initialize with root directory
if (MAX_DIRECTORIES <= 0) {
ESP_LOGW("FileSystem", "MAX_DIRECTORIES is not set or invalid");
root.close();
return;
}
directoryList[0] = "/"; directoryList[0] = "/";
count = 1; count = 1;
File file = root.openNextFile(); File file = root.openNextFile();
while (file) { while (file) {
if (file.isDirectory()){ if (file.isDirectory()){
if (count < 10){ // Ensure we don't overflow the array if (count < MAX_DIRECTORIES){ // Ensure we don't overflow the caller's array
directoryList[count] = '/' + String(file.name()); String entry = String(file.name());
if (!entry.startsWith("/")) entry = "/" + entry;
directoryList[count] = entry;
count++; count++;
}else{ } else {
ESP_LOGW("FileSystem", "Directory list array is full"); ESP_LOGW("FileSystem", "Directory list array is full");
break; break;
} }

View File

@ -15,7 +15,7 @@ void writeFilesToSerial(void);
//void getAllDirectoriesRecursive(const String& path, String directoryList[], int& count, int maxSize); //void getAllDirectoriesRecursive(const String& path, String directoryList[], int& count, int maxSize);
//void getAllDirectories(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); void printFilesInDirectories(const String directoryList[], int count);

View File

@ -412,4 +412,9 @@ void print_task_watermarks(void) {
} else { } else {
ESP_LOGE(tag, "Failed to get current task handle"); ESP_LOGE(tag, "Failed to get current task handle");
} }
} }
float updateLowpass(float currentValue, float newValue, float alpha) {
return (alpha * newValue) + ((1 - alpha) * currentValue);
}

View File

@ -82,6 +82,9 @@ TimerHandle_t upgradeHeartbeatTimer = NULL;
TimerHandle_t diagnosticsTimer = NULL; TimerHandle_t diagnosticsTimer = NULL;
#define diagnosticsInterval 60000 // ms #define diagnosticsInterval 60000 // ms
TimerHandle_t analogInputTimer = NULL;
#define analogInputInterval 3000 // ms
void setupLogLevels(esp_log_level_t logLevel); void setupLogLevels(esp_log_level_t logLevel);
@ -120,6 +123,13 @@ void DiagnosticsCallback(TimerHandle_t xTimer) {
#endif #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() void checkLEDCChannels()
{ {
@ -167,7 +177,7 @@ void setup()
printAllSystemFiles(); 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); Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);
// Load Board Pins // Load Board Pins
@ -205,15 +215,10 @@ void setup()
// Initialize Temperature Sensor // Initialize Temperature Sensor
Init_TSensor(72, &sys_settings.tSensorSettings); Init_TSensor(72, &sys_settings.tSensorSettings);
float val = readBoardInputVoltage();
ESP_LOGI(tag, "Input Volage = %f", val);
// Initialize BLE & Wifi // Initialize BLE & Wifi
// If button 1 is held during boot, enable upgrade mode // If button 1 is held during boot, enable upgrade mode
Load_BLE_Settings("/system/ble.json"); Load_BLE_Settings("/system/ble.json");
if (digitalRead(sys_settings.boardPins.btn[0]) == LOW) if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){
{
setStatusPin1(true); setStatusPin1(true);
ESP_LOGW(tag, "Upgrade Mode Triggered"); ESP_LOGW(tag, "Upgrade Mode Triggered");
ESP_LOGW(tag, "Enabling BLE and Update Service"); ESP_LOGW(tag, "Enabling BLE and Update Service");
@ -223,8 +228,7 @@ void setup()
UpgradeMode = true; UpgradeMode = true;
upgradeHeartbeatTimer = xTimerCreate("UpgradeHeartbeat", pdMS_TO_TICKS(5000), pdTRUE, NULL, UpgradeHeartbeatCallback); upgradeHeartbeatTimer = xTimerCreate("UpgradeHeartbeat", pdMS_TO_TICKS(5000), pdTRUE, NULL, UpgradeHeartbeatCallback);
} }
else else {
{
ESP_LOGI(tag, "Enabling BLE, No Update Service"); ESP_LOGI(tag, "Enabling BLE, No Update Service");
Init_BleServer(true, false); // Dont start the Upgrade service Init_BleServer(true, false); // Dont start the Upgrade service
} }
@ -236,20 +240,18 @@ void setup()
} }
#if OLED_ENABLED #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); 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 #endif
#if STRIPS_ENABLED #if STRIPS_ENABLED
Init_RGB_Lights_Task(); Init_RGB_Lights_Task(UpgradeMode);
//vTaskDelay(100);
//Init_Ramp_Front_Light_Task();
#endif #endif
// Create and start software timers // Create and start software timers
buttonScanTimer = xTimerCreate("ButtonScan", pdMS_TO_TICKS(buttonScanInterval), pdTRUE, NULL, ButtonScanCallback); buttonScanTimer = xTimerCreate("ButtonScan", pdMS_TO_TICKS(buttonScanInterval), pdTRUE, NULL, ButtonScanCallback);
temperatureTimer = xTimerCreate("Temperature", pdMS_TO_TICKS(sys_settings.tSensorSettings.intervalMs), pdTRUE, NULL, TemperatureCallback); temperatureTimer = xTimerCreate("Temperature", pdMS_TO_TICKS(sys_settings.tSensorSettings.intervalMs), pdTRUE, NULL, TemperatureCallback);
statusLedTimer = xTimerCreate("StatusLED", pdMS_TO_TICKS(statusLedInterval), pdTRUE, NULL, StatusLedCallback); statusLedTimer = xTimerCreate("StatusLED", pdMS_TO_TICKS(statusLedInterval), pdTRUE, NULL, StatusLedCallback);
analogInputTimer = xTimerCreate("AnalogInput", pdMS_TO_TICKS(analogInputInterval), pdTRUE, NULL, AnalogInputCallback);
#if FREERTOs_DIAGNOSTICS #if FREERTOs_DIAGNOSTICS
@ -257,10 +259,11 @@ void setup()
#endif #endif
// Start the timers // Start the timers
if (buttonScanTimer) xTimerStart(buttonScanTimer, 25); //if (buttonScanTimer) xTimerStart(buttonScanTimer, 100);
if (temperatureTimer && sys_settings.tSensorSettings.enabled) xTimerStart(temperatureTimer, 100); if (temperatureTimer && sys_settings.tSensorSettings.enabled) xTimerStart(temperatureTimer, 100);
if (statusLedTimer) xTimerStart(statusLedTimer, 0); if (statusLedTimer) xTimerStart(statusLedTimer, 0);
if (upgradeHeartbeatTimer && UpgradeMode) xTimerStart(upgradeHeartbeatTimer, upgradeHeartbeatInterval); if (upgradeHeartbeatTimer && UpgradeMode) xTimerStart(upgradeHeartbeatTimer, upgradeHeartbeatInterval);
if (analogInputTimer) xTimerStart(analogInputTimer, 0);
#if FREERTOS_DIAGNOSTICS #if FREERTOS_DIAGNOSTICS
if (diagnosticsTimer) xTimerStart(diagnosticsTimer, diagnosticsInterval); if (diagnosticsTimer) xTimerStart(diagnosticsTimer, diagnosticsInterval);
@ -315,9 +318,20 @@ void loop()
} }
} }
#endif #endif
/*
for (int i = 0; i < 3; i++) {
if (boardButtons[i] != NULL) {
boardButtons[i]->tick();
}
}
vTaskDelay(buttonScanInterval);
*/
// Small delay to prevent busy-waiting // 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) void setupLogLevels(esp_log_level_t logLevel)

View File

@ -4,124 +4,143 @@
#include <FS.h> #include <FS.h>
#include <LittleFS.h> #include <LittleFS.h>
#include "global.h"
#include "JsonConstrain.h" #include "JsonConstrain.h"
#include "global.h"
#include "system.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 // Basic validator for ESP32-S3 GPIOs; rejects common reserved/USB/strap pins
static bool isValidGpio(int pin) { static bool isValidGpio(int pin) {
if (pin < 0 || pin > 48) return false; if (pin < 0 || pin > 48)
switch (pin) { return false;
switch (pin) {
case 19: // USB D- case 19: // USB D-
case 20: // USB D+ case 20: // USB D+
case 45: // strapping case 45: // strapping
case 46: // strapping case 46: // strapping
return false; return false;
default: default:
return true; return true;
} }
} }
bool Load_Board_Pins(BOARD_PINS& boardPins, const String& path){ bool Load_Board_Pins(BOARD_PINS &boardPins, const String &path) {
// Default initialize to -1 to avoid stale values on partial loads // Default initialize to -1 to avoid stale values on partial loads
memset(&boardPins, -1, sizeof(boardPins)); memset(&boardPins, -1, sizeof(boardPins));
thisBoardPins = &boardPins; thisBoardPins = &boardPins;
File file = LittleFS.open(path); File file = LittleFS.open(path);
if (!file) { if (!file) {
ESP_LOGE(tag, "Error opening %s...", path.c_str()); ESP_LOGE(tag, "Error opening %s...", path.c_str());
return false; return false;
} }
JsonDocument doc; JsonDocument doc;
DeserializationError error = deserializeJson(doc, file); DeserializationError error = deserializeJson(doc, file);
file.close(); file.close();
if (error) { if (error) {
ESP_LOGE(tag, "%s deserialize error!..", path.c_str()); ESP_LOGE(tag, "%s deserialize error: %s", path.c_str(), error.c_str());
return false; return false;
} }
JsonObject boardJson = doc.as<JsonObject>(); JsonObject boardJson = doc.as<JsonObject>();
boardPins.rgb1 = jsonConstrain<int>(tag, boardJson, "rgb1", -1, 48, -1); boardPins.rgb1 = jsonConstrain<int>(tag, boardJson, "rgb1", -1, 48, -1);
boardPins.rgb2 = jsonConstrain<int>(tag, boardJson, "rgb2", -1, 48, -1); boardPins.rgb2 = jsonConstrain<int>(tag, boardJson, "rgb2", -1, 48, -1);
boardPins.btn[0] = jsonConstrain<int>(tag, boardJson, "btn1", -1, 48, -1); boardPins.btn[0] = jsonConstrain<int>(tag, boardJson, "btn1", -1, 48, -1);
boardPins.btn[1] = jsonConstrain<int>(tag, boardJson, "btn2", -1, 48, -1); boardPins.btn[1] = jsonConstrain<int>(tag, boardJson, "btn2", -1, 48, -1);
boardPins.btn[2] = jsonConstrain<int>(tag, boardJson, "btn3", -1, 48, -1); boardPins.btn[2] = jsonConstrain<int>(tag, boardJson, "btn3", -1, 48, -1);
boardPins.buzzer = jsonConstrain<int>(tag, boardJson, "buzzer", -1, 48, -1); boardPins.buzzer = jsonConstrain<int>(tag, boardJson, "buzzer", -1, 48, -1);
boardPins.touch[0] = jsonConstrain<int>(tag, boardJson, "touch1", -1, 48, -1); boardPins.touch[0] = jsonConstrain<int>(tag, boardJson, "touch1", -1, 48, -1);
boardPins.touch[1] = jsonConstrain<int>(tag, boardJson, "touch2", -1, 48, -1); boardPins.touch[1] = jsonConstrain<int>(tag, boardJson, "touch2", -1, 48, -1);
boardPins.touch[2] = jsonConstrain<int>(tag, boardJson, "touch3", -1, 48, -1); boardPins.touch[2] = jsonConstrain<int>(tag, boardJson, "touch3", -1, 48, -1);
boardPins.touch[3] = jsonConstrain<int>(tag, boardJson, "touch4", -1, 48, -1); boardPins.touch[3] = jsonConstrain<int>(tag, boardJson, "touch4", -1, 48, -1);
boardPins.touch[4] = jsonConstrain<int>(tag, boardJson, "touch5", -1, 48, -1); boardPins.touch[4] = jsonConstrain<int>(tag, boardJson, "touch5", -1, 48, -1);
boardPins.shield = jsonConstrain<int>(tag, boardJson, "shield", -1, 48, -1); boardPins.shield = jsonConstrain<int>(tag, boardJson, "shield", -1, 48, -1);
boardPins.relay[0] = jsonConstrain<int>(tag, boardJson, "relay1", -1, 48, -1); boardPins.relay[0] = jsonConstrain<int>(tag, boardJson, "relay1", -1, 48, -1);
boardPins.relay[1] = jsonConstrain<int>(tag, boardJson, "relay2", -1, 48, -1); boardPins.relay[1] = jsonConstrain<int>(tag, boardJson, "relay2", -1, 48, -1);
boardPins.relay[2] = jsonConstrain<int>(tag, boardJson, "relay3", -1, 48, -1); boardPins.relay[2] = jsonConstrain<int>(tag, boardJson, "relay3", -1, 48, -1);
boardPins.relay[3] = jsonConstrain<int>(tag, boardJson, "relay4", -1, 48, -1); boardPins.relay[3] = jsonConstrain<int>(tag, boardJson, "relay4", -1, 48, -1);
boardPins.stat[0] = jsonConstrain<int>(tag, boardJson, "stat1", -1, 48, -1); boardPins.stat[0] = jsonConstrain<int>(tag, boardJson, "stat1", -1, 48, -1);
boardPins.stat[1] = jsonConstrain<int>(tag, boardJson, "stat2", -1, 48, -1); boardPins.stat[1] = jsonConstrain<int>(tag, boardJson, "stat2", -1, 48, -1);
boardPins.adc1 = jsonConstrain<int>(tag, boardJson, "adc1", -1, 48, -1); boardPins.adc1 = jsonConstrain<int>(tag, boardJson, "adc1", -1, 48, -1);
boardPins.oled_dc = jsonConstrain<int>(tag, boardJson, "oled_dc", -1, 48, -1); boardPins.oled_dc = jsonConstrain<int>(tag, boardJson, "oled_dc", -1, 48, -1);
boardPins.oled_rst = jsonConstrain<int>(tag, boardJson, "oled_rst", -1, 48, -1); boardPins.oled_rst = jsonConstrain<int>(tag, boardJson, "oled_rst", -1, 48, -1);
boardPins.oled_mosi = jsonConstrain<int>(tag, boardJson, "oled_mosi", -1, 48, -1); boardPins.oled_mosi = jsonConstrain<int>(tag, boardJson, "oled_mosi", -1, 48, -1);
boardPins.oled_sck = jsonConstrain<int>(tag, boardJson, "oled_sck", -1, 48, -1); boardPins.oled_sck = jsonConstrain<int>(tag, boardJson, "oled_sck", -1, 48, -1);
boardPins.oled_cs = jsonConstrain<int>(tag, boardJson, "oled_cs", -1, 48, -1); boardPins.oled_cs = jsonConstrain<int>(tag, boardJson, "oled_cs", -1, 48, -1);
boardPins.ext[0] = jsonConstrain<int>(tag, boardJson, "ext1", -1, 48, -1); boardPins.ext[0] = jsonConstrain<int>(tag, boardJson, "ext1", -1, 48, -1);
boardPins.ext[1] = jsonConstrain<int>(tag, boardJson, "ext2", -1, 48, -1); boardPins.ext[1] = jsonConstrain<int>(tag, boardJson, "ext2", -1, 48, -1);
boardPins.rf433tx = jsonConstrain<int>(tag, boardJson, "rf433tx", -1, 48, -1); boardPins.rf433tx = jsonConstrain<int>(tag, boardJson, "rf433tx", -1, 48, -1);
boardPins.rf433rx = jsonConstrain<int>(tag, boardJson, "rf433rx", -1, 48, -1); boardPins.rf433rx = jsonConstrain<int>(tag, boardJson, "rf433rx", -1, 48, -1);
// Validate pins against reserved GPIOs // Validate pins against reserved GPIOs
auto clampPin = [](int v){ return isValidGpio(v) ? v : -1; }; auto clampPin = [](int v) { return isValidGpio(v) ? v : -1; };
boardPins.rgb1 = clampPin(boardPins.rgb1); boardPins.rgb1 = clampPin(boardPins.rgb1);
boardPins.rgb2 = clampPin(boardPins.rgb2); boardPins.rgb2 = clampPin(boardPins.rgb2);
for (int i=0;i<3;i++) boardPins.btn[i] = clampPin(boardPins.btn[i]); for (int i = 0; i < 3; i++)
boardPins.buzzer = clampPin(boardPins.buzzer); boardPins.btn[i] = clampPin(boardPins.btn[i]);
for (int i=0;i<5;i++) boardPins.touch[i] = clampPin(boardPins.touch[i]); boardPins.buzzer = clampPin(boardPins.buzzer);
boardPins.shield = clampPin(boardPins.shield); for (int i = 0; i < 5; i++)
for (int i=0;i<4;i++) boardPins.relay[i] = clampPin(boardPins.relay[i]); boardPins.touch[i] = clampPin(boardPins.touch[i]);
for (int i=0;i<2;i++) boardPins.stat[i] = clampPin(boardPins.stat[i]); boardPins.shield = clampPin(boardPins.shield);
boardPins.adc1 = clampPin(boardPins.adc1); for (int i = 0; i < 4; i++)
boardPins.oled_dc = clampPin(boardPins.oled_dc); boardPins.relay[i] = clampPin(boardPins.relay[i]);
boardPins.oled_rst = clampPin(boardPins.oled_rst); for (int i = 0; i < 2; i++)
boardPins.oled_mosi = clampPin(boardPins.oled_mosi); boardPins.stat[i] = clampPin(boardPins.stat[i]);
boardPins.oled_sck = clampPin(boardPins.oled_sck); boardPins.adc1 = clampPin(boardPins.adc1);
boardPins.oled_cs = clampPin(boardPins.oled_cs); boardPins.oled_dc = clampPin(boardPins.oled_dc);
for (int i=0;i<2;i++) boardPins.ext[i] = clampPin(boardPins.ext[i]); boardPins.oled_rst = clampPin(boardPins.oled_rst);
boardPins.rf433tx = clampPin(boardPins.rf433tx); boardPins.oled_mosi = clampPin(boardPins.oled_mosi);
boardPins.rf433rx = clampPin(boardPins.rf433rx); 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()); ESP_LOGI(tag, "loaded Pins from %s", path.c_str());
return true; 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[0] >= 0){ pinMode(boardPins.stat[0], OUTPUT); } if (boardPins.stat[1] >= 0) {
if(boardPins.stat[1] >= 0){ pinMode(boardPins.stat[1], OUTPUT); } 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); } ESP_LOGI(tag, "Board pins initialized...");
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...");
} }

View File

@ -5,11 +5,10 @@
#include "AppUpgrade.h" #include "AppUpgrade.h"
static const char* tag = "button"; static const char* tag = "button";
OneButton *boardButtons[3]; OneButton *boardButtons[3] = { nullptr, nullptr, nullptr };
void Init_ButtonEvents(int8_t (&pin)[3]){ void Init_ButtonEvents(int8_t (&pin)[3]){
// Initialize buttons if pins are valid and not already initialized
if (pin[0] >= 0) { if (pin[0] >= 0) {
if (boardButtons[0] == nullptr) { if (boardButtons[0] == nullptr) {
boardButtons[0] = new OneButton(pin[0], true, true); boardButtons[0] = new OneButton(pin[0], true, true);

View File

@ -17,7 +17,7 @@ static const char* tag = "buzzer";
// Define static constexpr member from RtttlPlayer class // Define static constexpr member from RtttlPlayer class
constexpr uint16_t RtttlPlayer::LUT4[12]; constexpr uint16_t RtttlPlayer::LUT4[12];
RtttlPlayer *player; RtttlPlayer *player = nullptr;
BUZZ_TUNE buzzTune[TUNE_MAX_COUNT]; BUZZ_TUNE buzzTune[TUNE_MAX_COUNT];
int8_t buzzPin; int8_t buzzPin;
int8_t buzzerChannel = -1; // Store the LEDC channel used by the buzzer 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<int>(tag, obj, "cycles", 1, 100, 1); buzzTune[tuneIndex].cycles = jsonConstrain<int>(tag, obj, "cycles", 1, 100, 1);
buzzTune[tuneIndex].pause = jsonConstrain<int>(tag, obj, "pause", 0, 100, 0); buzzTune[tuneIndex].pause = jsonConstrain<int>(tag, obj, "pause", 0, 100, 0);
buzzTune[tuneIndex].melody = jsonConstrainString(tag, obj, "tune", DEFAULT_MELODY); 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, tuneIndex, buzzTune[tuneIndex].cycles, buzzTune[tuneIndex].pause,
buzzTune[tuneIndex].melody.c_str()); buzzTune[tuneIndex].melody.c_str());
tuneIndex++; tuneIndex++;

View File

@ -1,79 +1,94 @@
#include "my_device.h" #include "my_device.h"
#include "JsonConstrain.h" #include "JsonConstrain.h"
#include "global.h"
#include "system.h"
#include "my_buttons.h"
#include "PWM_Output.h" #include "PWM_Output.h"
#include "Ramp_Lights.h" #include "Ramp_Lights.h"
#include "esp_log.h" #include "esp_log.h"
#include <FS.h> #include "global.h"
#include <LittleFS.h> #include "my_buttons.h"
#include "system.h"
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <esp_system.h> #include <FS.h>
#include <esp_log.h> #include <LittleFS.h>
#include <esp_adc_cal.h> #include <OneButton.h>
#include <driver/adc.h> #include <driver/adc.h>
#include <driver/ledc.h> #include <driver/ledc.h>
#include <OneButton.h> #include <esp_adc_cal.h>
#include <esp_log.h>
#include <esp_system.h>
#include <functional> #include <functional>
#include <memory> #include <memory>
static const char *tag = "my_device"; static const char *tag = "my_device";
SYS_SETTINGS sys_settings; SYS_SETTINGS sys_settings;
PWM_Output *pwmOutputs[4]; PWM_Output *pwmOutputs[4] = { nullptr, nullptr, nullptr, nullptr };
RAMP_LIGHT *rampLight1; RAMP_LIGHT *rampLight1 = nullptr;
RAMP_LIGHT *rampLight2; 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.. // TODO Restore original setOutput code..
#define RELAY_RES 10 #define RELAY_RES 10
void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]) void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]) {
{ // Delete any existing objects to avoid leaks on re-init
// Initialize all pointers to nullptr first
for (int i = 0; i < 4; i++) { 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(); int chIndex = findUnusedLedcChannel();
if (chIndex < 0) if (chIndex < 0) {
{
ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i); ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i);
continue; continue;
} }
pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, pwmSettings[i].max, false); pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq,
pwmOutputs[i]->setOutput(pwmSettings[i].def); 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); // 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]) {
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; }
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);
{ if (rampLight1)
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, min=%f, max=%f, step=%f", 1, settings[0].btnIndex, 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); else
ESP_LOGE(tag, "Failed to allocate RampLight1");
} }
if (settings[1].enabled) 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); 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 (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 // 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); File file = LittleFS.open(sysPath);
if (!file) if (!file) {
{
ESP_LOGE(tag, "Error opening %s...", sysPath); ESP_LOGE(tag, "Error opening %s...", sysPath);
return; return;
} }
@ -82,23 +97,21 @@ void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, Stri
DeserializationError error = deserializeJson(doc, file); DeserializationError error = deserializeJson(doc, file);
file.close(); file.close();
if (error) if (error) {
{
ESP_LOGE(tag, "%s deserialize error!..", sysPath); ESP_LOGE(tag, "%s deserialize error!..", sysPath);
return; return;
} }
// get hardware version string // get hardware version string
boardPath = jsonConstrainString(tag, doc.as<JsonObject>(), "boardfile", "/cfg/boards/board15.json"); boardPath =
boothPath = jsonConstrainString(tag, doc.as<JsonObject>(), "configfile", "/cfg/booths/custom.json"); jsonConstrainString(tag, doc.as<JsonObject>(), "boardfile", "/cfg/boards/board15.json");
boothPath =
jsonConstrainString(tag, doc.as<JsonObject>(), "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); File file = LittleFS.open(boothPath);
if (!file) if (!file) {
{
ESP_LOGE(tag, "Error opening %s...", boothPath.c_str()); ESP_LOGE(tag, "Error opening %s...", boothPath.c_str());
return; return;
} }
@ -107,139 +120,150 @@ void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
DeserializationError error = deserializeJson(doc, file); DeserializationError error = deserializeJson(doc, file);
file.close(); file.close();
if (error) if (error) {
{
ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str()); ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str());
return; return;
} }
sys_settings.profile = jsonConstrainString(tag, doc.as<JsonObject>(), "profile", "custom");
// ********** Mode *********** // ********** Mode ***********
String modeStr = jsonConstrainString(tag, doc.as<JsonObject>(), "mode", "booth"); String modeStr = jsonConstrainString(tag, doc.as<JsonObject>(), "mode", "booth");
if (modeStr == "roamer") if (modeStr == "roamer") {
{
sys_settings.mode = BOOTH_MODE_ROAMER; sys_settings.mode = BOOTH_MODE_ROAMER;
} } else if (modeStr == "stik") {
else if (modeStr == "stik")
{
sys_settings.mode = BOOTH_MODE_STIK; sys_settings.mode = BOOTH_MODE_STIK;
} } else {
else
{
sys_settings.mode = BOOTH_MODE_NONE; sys_settings.mode = BOOTH_MODE_NONE;
} }
// ********** PWM Out *********** // ********** PWM Out ***********
JsonArray pwmJsonArray = doc["pwmout"]; JsonArray pwmJsonArray = doc["pwmout"];
if (!pwmJsonArray.isNull()) if (!pwmJsonArray.isNull()) {
{
int pwmIndex = 0; int pwmIndex = 0;
for (JsonObject obj : pwmJsonArray) for (JsonObject obj : pwmJsonArray) {
{ if (pwmIndex >=
if (pwmIndex >= sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0])) sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0]))
break; break;
sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true); sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
sys_settings.pwmOutSettings[pwmIndex].freq = jsonConstrain<int>(tag, obj, "freq", 100, 5000, 250); sys_settings.pwmOutSettings[pwmIndex].freq =
sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0); jsonConstrain<int>(tag, obj, "freq", 100, 5000, 250);
sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0); sys_settings.pwmOutSettings[pwmIndex].min =
sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain<float>(tag, obj, "default", 0.0, 100.0, 0.0); jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
sys_settings.pwmOutSettings[pwmIndex].max =
jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
sys_settings.pwmOutSettings[pwmIndex].def =
jsonConstrain<float>(tag, obj, "default", 0.0, 100.0, 0.0);
sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0; sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
// sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true); // sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true);
pwmIndex++; pwmIndex++;
} }
ESP_LOGI(tag, "Loaded PWmOutput settings..."); ESP_LOGI(tag, "Loaded PWmOutput settings...");
} } else {
else
{
ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath); ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath);
} }
// ********** Ramp Lights *********** // ********** Ramp Lights ***********
JsonArray rampJsonArray = doc["ramp-lights"]; JsonArray rampJsonArray = doc["ramp-lights"];
if (!rampJsonArray.isNull()) if (!rampJsonArray.isNull()) {
{
int rampIndex = 0; int rampIndex = 0;
for (JsonObject obj : rampJsonArray) for (JsonObject obj : rampJsonArray) {
{ if (rampIndex >=
if (rampIndex >= sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0])) sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0]))
break; break;
sys_settings.rampLightSettings[rampIndex].enabled = jsonConstrainBool(tag, obj, "en", true); sys_settings.rampLightSettings[rampIndex].enabled =
sys_settings.rampLightSettings[rampIndex].vision = jsonConstrainBool(tag, obj, "vision", true); jsonConstrainBool(tag, obj, "en", true);
sys_settings.rampLightSettings[rampIndex].pwmOutIndex = jsonConstrain<int>(tag, obj, "relay-index", 0, 1, 0); sys_settings.rampLightSettings[rampIndex].vision =
sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain<int>(tag, obj, "button-index", 0, 1, 0); jsonConstrainBool(tag, obj, "vision", true);
sys_settings.rampLightSettings[rampIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0); sys_settings.rampLightSettings[rampIndex].pwmOutIndex =
sys_settings.rampLightSettings[rampIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0); jsonConstrain<int>(tag, obj, "relay-index", 0, 1, 0);
sys_settings.rampLightSettings[rampIndex].step = jsonConstrain<float>(tag, obj, "step", 0.1, 10.0, 1.5); sys_settings.rampLightSettings[rampIndex].btnIndex =
jsonConstrain<int>(tag, obj, "button-index", 0, 1, 0);
sys_settings.rampLightSettings[rampIndex].min =
jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
sys_settings.rampLightSettings[rampIndex].max =
jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
sys_settings.rampLightSettings[rampIndex].step =
jsonConstrain<float>(tag, obj, "step", 0.1, 10.0, 1.5);
rampIndex++; rampIndex++;
} }
ESP_LOGI(tag, "Loaded Ramp Lights settings..."); ESP_LOGI(tag, "Loaded Ramp Lights settings...");
} } else {
else
{
ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath); ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath);
} }
// ********** Fan *********** // ********** Fan ***********
JsonObject sensorJson = doc["t-sensor"]; JsonObject sensorJson = doc["t-sensor"];
if (!sensorJson.isNull()) if (!sensorJson.isNull()) {
{
sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true); sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true);
sys_settings.tSensorSettings.pwmIndex = jsonConstrain<int>(tag, sensorJson, "relay", 0, 3, 3); sys_settings.tSensorSettings.pwmIndex =
sys_settings.tSensorSettings.setpoint1 = jsonConstrain<float>(tag, sensorJson, "sp1", 50.0, 100.0, 80.0); jsonConstrain<int>(tag, sensorJson, "relay", 0, 3, 3);
sys_settings.tSensorSettings.setpoint2 = jsonConstrain<float>(tag, sensorJson, "sp2", 60.0, 110.0, 90.0); sys_settings.tSensorSettings.setpoint1 =
sys_settings.tSensorSettings.fanPower1 = jsonConstrain<float>(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0); jsonConstrain<float>(tag, sensorJson, "sp1", 50.0, 100.0, 80.0);
sys_settings.tSensorSettings.fanPower2 = jsonConstrain<float>(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0); sys_settings.tSensorSettings.setpoint2 =
sys_settings.tSensorSettings.hyst = jsonConstrain<float>(tag, sensorJson, "hyst", 1.0, 10.0, 1.0); jsonConstrain<float>(tag, sensorJson, "sp2", 60.0, 110.0, 90.0);
sys_settings.tSensorSettings.intervalMs = jsonConstrain<int>(tag, sensorJson, "interval", 1000, 30000, 5000); sys_settings.tSensorSettings.fanPower1 =
jsonConstrain<float>(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
sys_settings.tSensorSettings.fanPower2 =
jsonConstrain<float>(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0);
sys_settings.tSensorSettings.hyst =
jsonConstrain<float>(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
sys_settings.tSensorSettings.intervalMs =
jsonConstrain<int>(tag, sensorJson, "interval", 1000, 30000, 5000);
ESP_LOGI(tag, "Loaded TSensor settings..."); 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); ESP_LOGI(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1,
} sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
else } else {
{
ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath); ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath);
} }
// ********** RGB Strips *********** // ********** RGB Strips ***********
JsonArray stripsJsonArray = doc["strips"]; JsonArray stripsJsonArray = doc["strips"];
if (!stripsJsonArray.isNull()) if (!stripsJsonArray.isNull()) {
{
int stripIndex = 0; int stripIndex = 0;
for (JsonObject obj : stripsJsonArray) for (JsonObject obj : stripsJsonArray) {
{
if (stripIndex >= 2) if (stripIndex >= 2)
break; break;
sys_settings.ledStripSettings[stripIndex]->enabled = jsonConstrainBool(tag, obj, "en", true); if (sys_settings.ledStripSettings[stripIndex]) {
sys_settings.ledStripSettings[stripIndex]->size = jsonConstrain<int>(tag, obj, "size", 1, 250, 25); sys_settings.ledStripSettings[stripIndex]->enabled =
sys_settings.ledStripSettings[stripIndex]->chip = jsonConstrainString(tag, obj, "chip", "WS2812B"); jsonConstrainBool(tag, obj, "en", true);
sys_settings.ledStripSettings[stripIndex]->rgbOrder = jsonConstrainString(tag, obj, "rgb-order", "WS2812B"); sys_settings.ledStripSettings[stripIndex]->size =
sys_settings.ledStripSettings[stripIndex]->shift = jsonConstrain<int>(tag, obj, "shift", -250, 250, 0); jsonConstrain<int>(tag, obj, "size", 1, 250, 25);
sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain<int>(tag, obj, "offset", -250, 250, 0); sys_settings.ledStripSettings[stripIndex]->chip =
sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain<int>(tag, obj, "bright", 5, 255, 200); jsonConstrainString(tag, obj, "chip", "WS2812B");
sys_settings.ledStripSettings[stripIndex]->powerDiv = 0; sys_settings.ledStripSettings[stripIndex]->rgbOrder =
sys_settings.ledStripSettings[stripIndex]->i2sCh = 0; jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain<int>(tag, obj, "core", 0, 1, 0); sys_settings.ledStripSettings[stripIndex]->shift =
jsonConstrain<int>(tag, obj, "shift", -250, 250, 0);
sys_settings.ledStripSettings[stripIndex]->offset =
jsonConstrain<int>(tag, obj, "offset", -250, 250, 0);
sys_settings.ledStripSettings[stripIndex]->bright =
jsonConstrain<int>(tag, obj, "bright", 5, 255, 200);
sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
sys_settings.ledStripSettings[stripIndex]->core =
jsonConstrain<int>(tag, obj, "core", 0, 1, 0);
} else {
ESP_LOGW(tag, "ledStripSettings[%d] is null, skipping config", stripIndex);
}
stripIndex++; stripIndex++;
} }
sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1; sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1;
sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2; sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2;
ESP_LOGI(tag, "Loaded LED Strip settings..."); ESP_LOGI(tag, "Loaded LED Strip settings...");
} } else {
else
{
ESP_LOGE(tag, "Error!, %s key: strips not found.."); ESP_LOGE(tag, "Error!, %s key: strips not found..");
} }
// ********** BLE *********** // ********** BLE ***********
JsonObject bleJson = doc["ble"]; JsonObject bleJson = doc["ble"];
if (!bleJson.isNull()) if (!bleJson.isNull()) {
{
sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true); sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true);
sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS"); sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS");
ESP_LOGI(tag, "Loaded BLE settings..."); ESP_LOGI(tag, "Loaded BLE settings...");
} } else {
else
{
ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath); 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 // Configure ADC
analogReadResolution(12); // 12-bit ADC analogReadResolution(12); // 12-bit ADC
analogSetAttenuation(ADC_11db); analogSetAttenuation(ADC_11db);
if (sys_settings.boardPins.adc1 >= 0) if (sys_settings.boardPins.adc1 >= 0) {
{
analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db); 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); esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars);
// Check calibration success // 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"); 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"); ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse");
} } else {
else
{
ESP_LOGW(tag, "ADC calibration: Using default reference voltage"); ESP_LOGW(tag, "ADC calibration: Using default reference voltage");
} }
} }
float readBoardInputVoltage(void) {
float readBoardInputVoltage(void)
{
const int SAMPLES = 64; const int SAMPLES = 64;
uint32_t reading = 0; uint32_t reading = 0;
if (sys_settings.boardPins.adc1 < 0) if (sys_settings.boardPins.adc1 < 0) {
{
ESP_LOGE(tag, "ADC Pin not valid"); ESP_LOGE(tag, "ADC Pin not valid");
return 0.0; return 0.0;
} }
// Multiple readings for averaging // Multiple readings for averaging
for (int i = 0; i < SAMPLES; i++) for (int i = 0; i < SAMPLES; i++) {
{
reading += analogRead(sys_settings.boardPins.adc1); reading += analogRead(sys_settings.boardPins.adc1);
delayMicroseconds(50); // Small delay between samples 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); 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); voltage_mv = esp_adc_cal_raw_to_voltage(reading, &adc_chars);
// Scale to 12V range // Voltage divider: R1 = 10k (series to input), R2 = 470 (to ground). Measurement is across R2.
float voltage = (voltage_mv / 1000.0f) * (10470.0f / 470.0f); // 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;
} }

View File

@ -1,24 +1,25 @@
#include "my_oled.h" #include "my_oled.h"
#include <SPI.h>
#include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
#include <SPI.h>
static const char* tag = "oled"; static const char *tag = "oled";
Adafruit_SSD1306 *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) 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); oled = new Adafruit_SSD1306(width, height, mosiPin, sckPin, dcPin, rstPin, csPin);
if(!oled->begin(SSD1306_SWITCHCAPVCC)) { if (!oled->begin(SSD1306_SWITCHCAPVCC)) {
ESP_LOGE(tag, "SSD1306 allocation failed"); ESP_LOGE(tag, "SSD1306 allocation failed");
for(;;); // Don't proceed, loop forever for (;;)
} ; // Don't proceed, loop forever
}
oled_ShowInfo(); oled_ShowInfo();
} }
void oled_ShowInfo(void){ void oled_ShowInfo(void) {
//if(sysProps.oledEnabled) { // if(sysProps.oledEnabled) {
oled->display(); oled->display();
vTaskDelay(2000); // Pause for 2 seconds 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 // drawing operations and then update the screen all at once by calling
// display.display(). These examples demonstrate both approaches... // 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) { void testdrawline(Adafruit_SSD1306 *display) {
//if(sysProps.oledEnabled) { // if(sysProps.oledEnabled) {
int16_t i; int16_t i;
display->clearDisplay(); // Clear display buffer display->clearDisplay(); // Clear display buffer
for(i=0; i<display->width(); i+=4) { for (i = 0; i < display->width(); i += 4) {
display->drawLine(0, 0, i, display->height()-1, SSD1306_WHITE); display->drawLine(0, 0, i, display->height() - 1, SSD1306_WHITE);
display->display(); // Update screen with each newly-drawn line display->display(); // Update screen with each newly-drawn line
vTaskDelay(1); vTaskDelay(1);
} }
for(i=0; i<display->height(); i+=4) { for (i = 0; i < display->height(); i += 4) {
display->drawLine(0, 0, display->width()-1, i, SSD1306_WHITE); display->drawLine(0, 0, display->width() - 1, i, SSD1306_WHITE);
display->display(); display->display();
vTaskDelay(1); vTaskDelay(1);
} }
vTaskDelay(250); vTaskDelay(250);
display->clearDisplay(); display->clearDisplay();
for(i=0; i<display->width(); i+=4) { for (i = 0; i < display->width(); i += 4) {
display->drawLine(0, display->height()-1, i, 0, SSD1306_WHITE); display->drawLine(0, display->height() - 1, i, 0, SSD1306_WHITE);
display->display(); display->display();
vTaskDelay(1); vTaskDelay(1);
} }
for(i=display->height()-1; i>=0; i-=4) { for (i = display->height() - 1; i >= 0; i -= 4) {
display->drawLine(0, display->height()-1, display->width()-1, i, SSD1306_WHITE); display->drawLine(0, display->height() - 1, display->width() - 1, i, SSD1306_WHITE);
display->display(); display->display();
vTaskDelay(1); vTaskDelay(1);
} }
vTaskDelay(250); vTaskDelay(250);
display->clearDisplay(); display->clearDisplay();
for(i=display->width()-1; i>=0; i-=4) { for (i = display->width() - 1; i >= 0; i -= 4) {
display->drawLine(display->width()-1, display->height()-1, i, 0, SSD1306_WHITE); display->drawLine(display->width() - 1, display->height() - 1, i, 0, SSD1306_WHITE);
display->display(); display->display();
vTaskDelay(1); vTaskDelay(1);
} }
for(i=display->height()-1; i>=0; i-=4) { for (i = display->height() - 1; i >= 0; i -= 4) {
display->drawLine(display->width()-1, display->height()-1, 0, i, SSD1306_WHITE); display->drawLine(display->width() - 1, display->height() - 1, 0, i, SSD1306_WHITE);
display->display(); display->display();
vTaskDelay(1); vTaskDelay(1);
} }
vTaskDelay(250); vTaskDelay(250);
display->clearDisplay(); display->clearDisplay();
for(i=0; i<display->height(); i+=4) { for (i = 0; i < display->height(); i += 4) {
display->drawLine(display->width()-1, 0, 0, i, SSD1306_WHITE); display->drawLine(display->width() - 1, 0, 0, i, SSD1306_WHITE);
display->display(); display->display();
vTaskDelay(1); vTaskDelay(1);
} }
for(i=0; i<display->width(); i+=4) { for (i = 0; i < display->width(); i += 4) {
display->drawLine(display->width()-1, 0, i, display->height()-1, SSD1306_WHITE); display->drawLine(display->width() - 1, 0, i, display->height() - 1, SSD1306_WHITE);
display->display(); display->display();
vTaskDelay(1); vTaskDelay(1);
} }
vTaskDelay(2000); // Pause for 2 seconds vTaskDelay(2000); // Pause for 2 seconds
//} //}
} }
void testdrawrect(Adafruit_SSD1306* display) { void testdrawrect(Adafruit_SSD1306 *display) {
//if(sysProps.oledEnabled) { // if(sysProps.oledEnabled) {
display->clearDisplay(); display->clearDisplay();
for(int16_t i=0; i<display->height()/2; i+=2) { 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->drawRect(i, i, display->width() - 2 * i, display->height() - 2 * i, SSD1306_WHITE);
display->display(); // Update screen with each newly-drawn rectangle display->display(); // Update screen with each newly-drawn rectangle
vTaskDelay(1); vTaskDelay(1);
} }
vTaskDelay(2000); vTaskDelay(2000);
//} //}
} }

View File

@ -21,7 +21,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
// Initialize the temperature sensor once with the provided I2C address // Initialize the temperature sensor once with the provided I2C address
if (tSensor == nullptr) { if (tSensor == nullptr) {
tSensor = new TI_TMP102_Compatible(addr); 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 { } else {
ESP_LOGW(tag, "TSensor already initialized; ignoring re-init request (addr 0x%02X)", addr); 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 // Fan is off - check if we should turn it on
if (temperature >= sp1) { if (temperature >= sp1) {
fanIsOn = true; 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 { } else {
newDuty = 0.0f; // Stay off newDuty = 0.0f; // Stay off
} }
@ -86,7 +86,7 @@ void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
if (temperature < (sp1 - hyst)) { if (temperature < (sp1 - hyst)) {
fanIsOn = false; fanIsOn = false;
newDuty = 0.0f; 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; float tempRatio = (temperature - sp1) / tempRange;
newDuty = fp1 + (tempRatio * powerRange); 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 // 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) // Apply new duty cycle if changed (with small tolerance to avoid constant updates)
if (fabs(currentDuty - newDuty) > 0.1f) { if (fabs(currentDuty - newDuty) > 0.1f) {
pwmOut->setOutput(newDuty); 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");
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,673 +0,0 @@
#include "AppUpgrade.h"
#include "esp_log.h"
#include <MD5Builder.h>
#include <LittleFS.h>
#include <memory>
#include "global.h"
#include "JsonConstrain.h"
#include "BLE_UpdateService.h"
#include <HTTPClient.h>
#include <Update.h>
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<char*>(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<JsonObject>() || !jsonManifest["firmware"]["md5"].is<const char*>()) {
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<JsonObject>();
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);
}
*/

View File

@ -1,716 +0,0 @@
#include "AppUpgrade.h"
#include "esp_log.h"
#include <MD5Builder.h>
#include <LittleFS.h>
#include <memory>
#include "global.h"
#include "JsonConstrain.h"
#include "BLE_UpdateService.h"
#include <HTTPClient.h>
#include <Update.h>
#include <cstring>
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<HTTP_RETRY_COUNT; ++attempt){
if(g_UpdateCancelFlag) return ManifestCheckResult::ERROR_FETCH_FAILED;
HTTPClient http;
http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
payload = http.getString();
http.end();
break;
}
ESP_LOGW(TAG, "Manifest GET failed (attempt %d/%d): %d", attempt+1, HTTP_RETRY_COUNT, httpCode);
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;
}
if(payload.length() > 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<HTTP_RETRY_COUNT; ++attempt){
if(g_UpdateCancelFlag) return false;
http.begin(url);
httpCode = http.GET();
if(httpCode == HTTP_CODE_OK) break;
ESP_LOGW(TAG, "File GET failed (attempt %d/%d): %d", attempt+1, HTTP_RETRY_COUNT, httpCode);
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: %d", httpCode);
updateProgress(UpdateStatus::ERROR, 0, "Download failed");
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){
String errMsg = String(localPath) + " MD5 failed";
updateProgress( UpdateStatus::ERROR, 0, errMsg.c_str() );
}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) {
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<char*>(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<JsonObject>() || !jsonManifest["firmware"]["md5"].is<const char*>()) {
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<HTTP_RETRY_COUNT; ++attempt){
if(g_UpdateCancelFlag) return false;
http.begin(firmwareUrl);
httpCode = http.GET();
if(httpCode == HTTP_CODE_OK) break;
ESP_LOGW(TAG, "Firmware GET failed (attempt %d/%d): %d", attempt+1, HTTP_RETRY_COUNT, httpCode);
http.end();
if(attempt+1 < HTTP_RETRY_COUNT) vTaskDelay(pdMS_TO_TICKS(HTTP_RETRY_DELAY_MS));
}
if (httpCode != HTTP_CODE_OK) {
ESP_LOGE(TAG, "Firmware download failed: %d", httpCode);
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Firmware download failed");
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) {
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<int>(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<JsonObject>();
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);
}
*/

View File

@ -1,178 +0,0 @@
#include "ATALights.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <Arduino.h>
#include "Animations.h"
#include <functional>
#include <NeoPixelBus.h>
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<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip1)->Begin();
if (strip2) static_cast<NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>*>(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<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>>(strip1, numLeds1, RgbColor(255, 0, 0)); // Red for Strip 1
setStripColor<NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>>(strip2, numLeds2, RgbColor(0, 255, 0)); // Green for Strip 2
delay(1000);
setStripColor<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>>(strip1, numLeds1, RgbColor(0, 0, 255)); // Blue for Strip 1
setStripColor<NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>>(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<NeoGrbFeature, Neo800KbpsMethod>(numLeds, dataPin);
} else if (colorOrder == "RGB") {
return new NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>(numLeds, dataPin);
} else if (colorOrder == "BGR") {
return new NeoPixelBus<NeoBgrFeature, Neo800KbpsMethod>(numLeds, dataPin);
}
}
Serial.println("Unsupported chipset or color order!");
return nullptr;
}
// Function to set all LEDs of a strip to a specific color
template <typename T>
void setStripColor(void* strip, int numLeds, RgbColor color) {
if (strip) {
T* actualStrip = static_cast<T*>(strip);
for (int i = 0; i < numLeds; i++) {
actualStrip->SetPixelColor(i, color);
}
actualStrip->Show();
}
}

View File

@ -1,4 +0,0 @@
#pragma once
#include <NimBLEDevice.h>
#include <ArduinoJson.h>

View File

@ -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<JsonObject>();
String ssid = wifiJson["ssid"].as<String>();
String pass = wifiJson["pass"].as<String>();
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<uint8_t*>(&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
}

View File

@ -1,240 +0,0 @@
#include "BTSerial.h"
#include <esp_task_wdt.h>
#include <ArduinoJson.h>
#include <FS.h>
#include <LittleFS.h>
#include <NimBLEDevice.h>
#include <string>
#include "command_processor.h"
#include "led_strip.h"
#include "global.h"
#include "JsonConstrain.h"
#include "my_buzzer.h"
#include "common/led_animation.h"
static const char* tag = "ble";
TaskHandle_t BTSerial_Task_Handle;
bool BTDeviceConnected = false;
BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
BLECharacteristic * pRxCharacteristic;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID_DEF "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX_DEF "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX_DEF "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
String BLEDeviceName;
String BLEKey;
String SERVICE_UUID;
String CHARACTERISTIC_UUID_RX;
String CHARACTERISTIC_UUID_TX;
#define replyActive true
void Init_BTSerial(void)
{
File file = LittleFS.open("/cfg/ble.json");
if(!file){
ESP_LOGE(tag, "Error opening ble.json...");
}
else{
JsonDocument doc;
DeserializationError error = deserializeJson(doc, file);
file.close();
if(error){ ESP_LOGE(tag, "ble.json deserialize error!.."); return;}
JsonObject bleJson = doc.as<JsonObject>();
// if(jsonConstrainBool(bleJson, "en", false)){
SERVICE_UUID = jsonConstrainString(tag, bleJson, "service-uuid", SERVICE_UUID_DEF);
ESP_LOGD(tag, "SERVICE_UUID: %s", SERVICE_UUID.c_str());
CHARACTERISTIC_UUID_RX = jsonConstrainString(tag, bleJson, "char-uuid-rx", CHARACTERISTIC_UUID_RX_DEF);
ESP_LOGD(tag, "Char UUID RX: %s", CHARACTERISTIC_UUID_RX.c_str());
CHARACTERISTIC_UUID_TX = jsonConstrainString(tag, bleJson, "char-uuid-tx", CHARACTERISTIC_UUID_TX_DEF);
ESP_LOGD(tag, "Char UUID TX: %s", CHARACTERISTIC_UUID_TX.c_str());
BLEDeviceName = jsonConstrainString(tag, bleJson, "device-name", "ATA_COMM");
String hexStr = String(chipInfo.macByte[0], HEX);
hexStr.toUpperCase();
BLEDeviceName += '_';
BLEDeviceName += hexStr;
BLEKey = jsonConstrainString(tag, bleJson, "key", "123456");
int core = jsonConstrain<int>(tag, bleJson, "core", 0, 1, 0);
ESP_LOGD(tag, "BLE SSID: %s, key: %s, core: %d", BLEDeviceName.c_str(),BLEKey.c_str(), core);
xTaskCreatePinnedToCore(BTSerial_Task, "BTSerial_Task", 12000, NULL, 1, &BTSerial_Task_Handle, core);
//}
}
}
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
BTDeviceConnected = true;
//BLEDevice::startAdvertising();//adding this line allows for multiple simultaneous BLE connections
};
/*
void onConnect(BLEServer* pServer, BLEClient* pClient) {
BTDeviceConnected = true;
BLEAddress connectedAddress = pClient->getPeerAddress();
Log.traceln("Client connected: %s", connectedAddress.toString().c_str());
}
*/
void onDisconnect(BLEServer* pServer) {
BTDeviceConnected = false;
}
/***************** New - Security handled here ********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
ESP_LOGD(tag, "Server PassKeyRequest");
return 123456;
}
bool onConfirmPIN(uint32_t pass_key){
ESP_LOGD(tag, "The passkey YES/NO number: %d", pass_key);
return true;
}
void onAuthenticationComplete(ble_gap_conn_desc desc){
ESP_LOGD(tag, "Starting BLE work!");
}
/*******************************************************************/
};
#define MAX_PACKET_PARAMS 8
int packet_data[MAX_PACKET_PARAMS];
int paramCount = 0;
class MyCallbacks: public BLECharacteristicCallbacks
{
void onWrite(BLECharacteristic *pCharacteristic)
{
std::string rxValue = pCharacteristic->getValue();
ESP_LOGD(tag, "raw: %s", rxValue.c_str());
if(!rxValue.empty() && ((rxValue[0] == '$') || rxValue[0] == '%')){
extractCommand(&rxValue[0], packet_data, &paramCount); // delimited command
// Call Animation Index Update
if(packet_data[0] == 100){
int cntDown = 0;
if(packet_data[2]){
cntDown = packet_data[2];
}
animProps.event[packet_data[1]].countDown = cntDown;
animProps.event[packet_data[1]].type = EV_NORMAL;
PostNewEvent(animProps.event[packet_data[1]]);
}else{
// TODO: Process other Bluetooth commands
}
if(replyActive){
rxValue[0] = '%';
pTxCharacteristic->setValue(rxValue);
pTxCharacteristic->notify();
}
// print params
ESP_LOGD(tag, "packet: %c%d, %d", rxValue[0], packet_data[0], packet_data[1]);
}
}
};
void extractCommand(char* packet, int* data, int* count)
{
int base = 10;
//if(*packet == '#'){
// base = 16;
//}
packet++;
char* token = strtok(packet, ",\n");
int8_t index = 0;
while (token != NULL && index < MAX_PACKET_PARAMS) {
data[index] = strtol(token, NULL, base);
index++;
token = strtok(NULL, ",\n");
}
*count = index;
}
void BTSerial_Task(void *parameters){
vTaskDelay(1000);
// Extend watchdog timer
esp_task_wdt_init(5, false);
// Create the BLE Device.
BLEDevice::init(BLEDeviceName.c_str());
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService((const char*)SERVICE_UUID.c_str());
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_TX.c_str(), NIMBLE_PROPERTY::NOTIFY );
/***************************************************
NOTE: DO NOT create a 2902 descriptor, it will be created auto.. if notifications
or indications are enabled on a characteristic.
pCharacteristic->addDescriptor(new BLE2902());
****************************************************/
pRxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_RX.c_str(), NIMBLE_PROPERTY::WRITE );
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
vTaskDelay(100);
pServer->getAdvertising()->start();
ESP_LOGV(tag, "Waiting for a client...");
ANIMATION_EVENT newEvent;
for(;;){
//if (BTDeviceConnected) {
//pTxCharacteristic->setValue(&txValue, 1);
//pTxCharacteristic->notify();
//txValue++;
//vTaskDelay(100); // bluetooth stack will go into congestion, if too many packets are sent
//}
// disconnecting
if (!BTDeviceConnected && oldDeviceConnected) {
vTaskDelay(750); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
oldDeviceConnected = BTDeviceConnected;
newEvent.animIndex = POP_BLE_DISC;
newEvent.type = EV_INJECT;
PostNewEvent(newEvent);
ESP_LOGI(tag, "Client disconnected...");
ESP_LOGI(tag, "Advertising again...");
Buzzer_Play_Tune(TUNE_BLE_DISCONNECTED);
}
// connecting
if (BTDeviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = BTDeviceConnected;
newEvent.animIndex = POP_BLE_CONN;
newEvent.type = EV_INJECT;
PostNewEvent(newEvent);
ESP_LOGI(tag, "Client connected...");
Buzzer_Play_Tune(TUNE_BLE_CONNECTED);
}
vTaskDelay(200);
}
}

View File

@ -1,16 +0,0 @@
#ifndef _BTSERIAL_H
#define _BTSERIAL_H
#include <Arduino.h>
extern bool BTDeviceConnected;
extern String BLEDeviceName;
void Init_BTSerial(void);
void BTSerial_Task(void *parameters);
void extractCommand(char* packet, int* data, int* count);
#endif

View File

@ -1,82 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="event-box.js"></script>
<script src="hue-select.js"></script>
<title>Event Container</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 0;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
width: 100%;
max-width: 700px;
min-width: 300px;
margin: 0 auto;
}
input[type="checkbox"] {
transform: scale(2.5);
}
input[type="checkbox"].css-checkbox + label.css-label {
display: inline-block;
margin-left: 30px;
}
.checkbox-container {
display: flex;
align-items: center;
justify-content: left;
}
label {
display: block;
margin-bottom: 1px;
padding-left: 10px;
}
</style>
</head>
<body>
<div>
<br>
<div>
<event-box id="event1"></event-box>
<!--<hue-select id="hueItem"></hue-select>-->
</div>
</div>
<script>
window.onload = function() { onLoad(); };
const event1 = document.getElementById('event1');
const hueItem = document.getElementById('hueItem');
function onLoad(){
//debugger;
hueItem.setHue(145);
/*
event1.setTitle("Event 0");
debugger;
//event1.setHueValue(101);
//event1.setParam1Name(`Param1`);
event1.setParam2Name("Cooling");
event1.setCheck1Name("CHECK1");
event1.setCheck2Name(`Check2`);
event1.setHueValue(140);
event1.setSpeedSettings("SPEED: ");
event1.addOptionToList(0, "Animation0");
event1.addOptionToList(1, "Animation1");
event1.addOptionToList(2, "Animation2");
event1.setAnimationIndex(1);
*/
}
</script>
</body>
</html>

View File

@ -1,164 +0,0 @@
import os
import shutil
import hashlib
import json
def copy_folder_to_destination(src_path, dest_path, skip_dirs=None, skip_files=None):
# Ensure the destination directory exists
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
# Remove existing folder if present
if os.path.exists(dest_path):
shutil.rmtree(dest_path)
# Create destination directory
os.makedirs(dest_path)
# Walk through source directory
for root, dirs, files in os.walk(src_path):
# Remove directories to skip from dirs list
if skip_dirs:
dirs[:] = [d for d in dirs if d not in skip_dirs]
# Calculate relative path
rel_path = os.path.relpath(root, src_path)
dest_dir = os.path.join(dest_path, rel_path)
# Create corresponding destination directory
os.makedirs(dest_dir, exist_ok=True)
# Copy files that aren't in skip_files
for file in files:
if skip_files and file in skip_files:
continue
src_file = os.path.join(root, file)
dest_file = os.path.join(dest_dir, file)
shutil.copy2(src_file, dest_file)
def calculate_md5(file_path):
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def get_file_size(file_path):
return os.path.getsize(file_path)
def update_json_file(json_array, folder_path):
# Create new data array for files
file_array = []
# Walk through the copied folder and collect file details
for root, _, files in os.walk(folder_path):
for file in files:
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, folder_path)
# Replace backslashes with forward slashes
relative_path = relative_path.replace('\\', '/')
file_entry = {
"remote": os.path.join("data/", relative_path),
"local": os.path.join("/", relative_path),
"md5": calculate_md5(file_path),
"size": get_file_size(file_path)
}
file_array.append(file_entry)
# Replace the contents of the input json_array with new data
json_array.clear()
json_array.extend(file_array)
def update_files(src_path, dest_path, skip_dirs=None, skip_files=None):
# Check if the source folder exists
if not os.path.isdir(src_path) or not os.path.isdir(dest_path):
print("Invalid folder path!")
return
# Copy all data contents
copy_folder_to_destination(src_path, dest_path, skip_dirs, skip_files)
print("Folder copied successfully.")
def main():
here_path = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Path of the folder to copy (you can modify this)
src_folder_name = "data"
src_path = os.path.join(project_path, src_folder_name)
#print(f"source path: {src_path}")
# Path of the destination folder
dest_folder_name = "firmware_update\\latest\\data"
dest_path = os.path.join(project_path, dest_folder_name)
#print(f"destination path: {dest_path}")
# Path of the firmware binary file
bin_name = ".pio\\build\\esp32s3dev\\firmware.bin"
# Skip these directories
skip_dirs = ["boards", "booths"]
# Skip these files
skip_files = ["wifi.json", "system.json", "luma-stiks.json", ]
update_files = input("Do you want to update the files? (y/n): ")
# *********************** Copy Data Files ***********************
if update_files.lower() == "y":
update_files(src_path, dest_path, skip_dirs, skip_files)
# *********************** Copy Binary file ***********************
update_firmware = input("Do you want to update the firmware? (y/n): ")
if update_firmware.lower() == "y":
# Copy firmware.bin to the destination
bin_path = os.path.join(project_path, bin_name)
shutil.copy(bin_path, here_path)
#print(f"firmware path: {bin_path}")
print("firmware.bin copied successfully.")
# *********************** Process update.json ***********************
# Update the JSON file
json_path = os.path.join(here_path, "update.json")
print(f"json path: {json_path}")
# Read existing JSON
with open(json_path, "r") as f:
try:
json_doc = json.load(f)
except json.JSONDecodeError:
print("Invalid JSON file!")
return
# process the files array
#if update_files.lower() == "y":
json_files_array = json_doc["files"]
update_json_file(json_files_array, dest_path)
#print(f"Folder {os.path.basename(src_path)} processed successfully.")
# *********************** Process firmwware.bin in update.json ***********************
# process the firmware
if update_firmware.lower() == "y":
json_firmware = json_doc["firmware"]
firmware_path = os.path.join(here_path, "firmware.bin")
json_firmware["md5"] = calculate_md5(firmware_path)
json_firmware["size"] = get_file_size(firmware_path)
# Write updated JSON
with open(json_path, "w") as f:
json.dump(json_doc, f, indent=4)
print("Update JSON files created successfully.")
if __name__ == "__main__":
main()

View File

@ -1,47 +0,0 @@
#include <NeoPixelBus.h>
// Define constants for maximum LEDs
#define MAX_LEDS 300 // Adjust based on your LED requirements
// Wrapper class for dynamic LED strips
class DynamicLedStrip {
private:
void* strip; // Pointer to the NeoPixelBus object
int numLeds; // Number of LEDs
String colorOrder; // Color order (for reference)
public:
DynamicLedStrip() : strip(nullptr), numLeds(0), colorOrder("") {}
// Initialize the LED strip
void initialize(int dataPin, int numLeds, const String& chipType, const String& colorOrder) {
this->numLeds = numLeds;
this->colorOrder = colorOrder;
if (chipType == "WS2812" || chipType == "SK6812") {
if (colorOrder == "GRB") {
strip = new NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>(numLeds, dataPin);
} else if (colorOrder == "RGB") {
strip = new NeoPixelBus<NeoRgbFeature, Neo800KbpsMethod>(numLeds, dataPin);
} else if (colorOrder == "BGR") {
strip = new NeoPixelBus<NeoBgrFeature, Neo800KbpsMethod>(numLeds, dataPin);
}
}
if (strip) {
static_cast<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip)->Begin();
} else {
Serial.println("Unsupported chipset or color order!");
}
}
// Set all LEDs to a specific color
void setColor(const RgbColor& color) {
if (strip) {
for (int i = 0; i < numLeds; i++) {
static_cast<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip)->SetPixelColor(i, color);
}
static_cast<NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod>*>(strip)->Show();
}
}
};

View File

@ -1,337 +0,0 @@
#!/usr/bin/env python3
"""Upload firmware, manifest, and data assets to a MinIO (S3-compatible) bucket.
Features preserved from original GCS script:
- Optional backup (copies existing objects under destination prefix to timestamped folder under backups/)
- Upload firmware.bin, update.json, and recursively mirror a data directory
- Cache-Control set to disable caching on clients
Switches from google.cloud.storage to boto3 (S3 API) for MinIO compatibility.
"""
import os
import sys
import datetime
import json
from pathlib import Path
try:
import boto3
from botocore.exceptions import ClientError
from botocore.config import Config
except ImportError:
print("ERROR: boto3 is required. Install with: pip install boto3")
sys.exit(1)
# =============================================================================
# CONFIGURATION CONSTANTS (edit as needed or supply via environment variables)
# =============================================================================
CREATE_BACKUP = False
UPLOAD_FIRMWARE = False
UPLOAD_MANIFEST = True
UPLOAD_DATA = False
# Bucket / endpoint configuration
BUCKET_NAME = os.getenv('MINIO_BUCKET', 'boothifier')
DESTINATION_DIR = os.getenv('MINIO_DEST_PREFIX', 'latest') # prefix inside bucket
BACKUPS_DIR = os.getenv('MINIO_BACKUPS_PREFIX', 'backups')
LOCAL_ROOT_PATH = Path(__file__).parent.resolve()
# Optional service account style JSON key (generated by MinIO Console). Expected fields:
# {"url":"https://minio.example.com/api/v1/service-account-credentials","accessKey":"...","secretKey":"...","api":"s3v4","path":"auto"}
MINIO_KEY_FILE = LOCAL_ROOT_PATH / 'minio-boothifier-key.json'
# Defaults before loading file / env
_json_access = None
_json_secret = None
_json_url = None
def _load_json_key():
global _json_access, _json_secret, _json_url
try:
if MINIO_KEY_FILE.is_file():
with open(MINIO_KEY_FILE, 'r', encoding='utf-8') as fh:
data = json.load(fh)
_json_access = data.get('accessKey') or None
_json_secret = data.get('secretKey') or None
_json_url = data.get('url') or None
except Exception as e:
print(f"WARN: Failed to load MinIO key file '{MINIO_KEY_FILE.name}': {e}")
_load_json_key()
def _derive_endpoint(url_value: str) -> str:
if not url_value:
return 'https://s3-minio.boothwizard.com'
# Remove known API suffix if present (/api/...)
# e.g. https://s3-minio.boothwizard.com/api/v1/service-account-credentials -> https://s3-minio.boothwizard.com
parts = url_value.split('/api/')
return parts[0] if parts else url_value
# MinIO credentials with precedence: ENV > JSON file > fallback
MINIO_ENDPOINT = os.getenv('MINIO_ENDPOINT') or _derive_endpoint(_json_url)
MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') or _json_access or 'CHANGE_ME_ACCESS'
MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY') or _json_secret or 'CHANGE_ME_SECRET'
MINIO_REGION = os.getenv('MINIO_REGION', 'us-east-1') # MinIO ignores but boto3 wants some value
# Addressing / SSL options
MINIO_ADDRESSING = os.getenv('MINIO_ADDRESSING_STYLE', 'path').lower() # 'path' or 'virtual'
MINIO_VERIFY_SSL = os.getenv('MINIO_TLS_VERIFY', '1') not in ('0','false','no')
MINIO_DEBUG = os.getenv('MINIO_DEBUG', '0') in ('1','true','yes')
MINIO_ALLOW_VARIANTS = os.getenv('MINIO_ALLOW_ENDPOINT_VARIANTS', '0') in ('1','true','yes') # normally false with nginx redirect
LOCAL_FIRMWARE_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'firmware.bin')
LOCAL_MANIFEST_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'update.json')
LOCAL_DATA_DIRECTORY = str(LOCAL_ROOT_PATH / 'latest' / 'data')
# =============================================================================
# HELPERS
# =============================================================================
def s3_client():
"""Create an S3 client pointed at MinIO endpoint, forcing path-style unless overridden, with short timeouts."""
addressing = 'path' if MINIO_ADDRESSING not in ('virtual','auto') else 'virtual'
cfg = Config(
s3={'addressing_style': addressing},
signature_version='s3v4',
connect_timeout=3,
read_timeout=5,
retries={'max_attempts': 2}
)
if MINIO_DEBUG:
masked_key = (MINIO_ACCESS_KEY[:3] + '...' + MINIO_ACCESS_KEY[-3:]) if MINIO_ACCESS_KEY else 'None'
print(f"[DEBUG] Creating client: endpoint={MINIO_ENDPOINT} addressing={addressing} verifySSL={MINIO_VERIFY_SSL} region={MINIO_REGION} accessKey={masked_key}")
return boto3.client(
's3',
endpoint_url=MINIO_ENDPOINT,
aws_access_key_id=MINIO_ACCESS_KEY,
aws_secret_access_key=MINIO_SECRET_KEY,
region_name=MINIO_REGION,
verify=MINIO_VERIFY_SSL,
config=cfg,
)
def _endpoint_variants(base: str):
"""Return endpoint variants only if explicitly allowed; otherwise just the base (nginx handles forwarding)."""
if not MINIO_ALLOW_VARIANTS:
return [base]
# Fallback to previous expanded logic if variants are enabled
try:
variants = []
if not base:
return variants
base = base.rstrip('/')
proto_sep = '://'
if proto_sep in base:
scheme, rest = base.split(proto_sep,1)
else:
scheme, rest = 'https', base
host_port = rest
if ':' in host_port:
host, port = host_port.split(':',1)
else:
host, port = host_port, ''
variants.append(f"{scheme}://{host_port}")
common_ports = ['9000','443','80']
for p in common_ports:
if port != p:
variants.append(f"{scheme}://{host}:{p}")
alt_scheme = 'http' if scheme == 'https' else 'https'
variants.append(f"{alt_scheme}://{host_port}")
for p in common_ports:
if port != p:
variants.append(f"{alt_scheme}://{host}:{p}")
seen = set()
uniq = []
for v in variants:
if v not in seen:
uniq.append(v)
seen.add(v)
return uniq
except Exception:
return [base]
def create_validated_client():
"""Validate (or create) client using only provided endpoint unless variants enabled."""
global MINIO_ENDPOINT
primary = MINIO_ENDPOINT
variants = _endpoint_variants(primary) or [primary]
errors = []
probe_bucket = BUCKET_NAME # we will head the target bucket directly
for candidate in variants:
saved = MINIO_ENDPOINT
MINIO_ENDPOINT = candidate
if MINIO_DEBUG:
print(f"[DEBUG] Probing endpoint candidate: {candidate}")
try:
c = s3_client()
try:
c.head_bucket(Bucket=probe_bucket)
if MINIO_DEBUG:
print(f"[DEBUG] head_bucket succeeded on {candidate} for '{probe_bucket}'.")
return c
except ClientError as e:
msg = str(e)
# Acceptable if bucket not found (we can create later)
if any(code in msg for code in ('404', 'NoSuchBucket', 'NotFound')):
if MINIO_DEBUG:
print(f"[DEBUG] Bucket not found on {candidate} (expected if first deploy). Using this endpoint.")
return c
if 'API Requests must be made to API port' in msg:
errors.append(f"{candidate}: wrong port (console endpoint)")
else:
errors.append(f"{candidate}: {msg}")
MINIO_ENDPOINT = saved
except Exception as ex:
errors.append(f"{candidate}: {ex}")
MINIO_ENDPOINT = saved
continue
print("ERROR: Could not validate any endpoint candidate.")
for e in errors:
print(' - ' + e)
print("Provide correct API endpoint (e.g. https://host:9000) via MINIO_ENDPOINT env var.")
sys.exit(3)
def list_objects(client, prefix: str):
"""Generator yielding object keys under a prefix (non-recursive listing with pagination)."""
kwargs = {'Bucket': BUCKET_NAME, 'Prefix': prefix}
while True:
resp = client.list_objects_v2(**kwargs)
for obj in resp.get('Contents', []):
yield obj['Key']
if not resp.get('IsTruncated'):
break
kwargs['ContinuationToken'] = resp['NextContinuationToken']
def normalize_prefix(p: str) -> str:
p = p.strip('/')
return p
def join_key(*parts: str) -> str:
parts_clean = [p.strip('/') for p in parts if p is not None and p != '']
return '/'.join(parts_clean)
def backup_existing_files(client, destination_prefix: str, backups_prefix: str, backup_folder: str):
if not destination_prefix:
prefix = ''
else:
prefix = destination_prefix + '/'
print(f"Scanning existing objects under '{prefix}' for backup...")
for key in list_objects(client, prefix):
if backups_prefix and key.startswith(backups_prefix + '/'): # Skip prior backups
continue
# relative path within destination
relative = key[len(prefix):] if prefix and key.startswith(prefix) else key
backup_key = join_key(backups_prefix, backup_folder, relative)
print(f"Backup copy: {key} -> {backup_key}")
client.copy_object(
Bucket=BUCKET_NAME,
CopySource={'Bucket': BUCKET_NAME, 'Key': key},
Key=backup_key,
MetadataDirective='COPY'
)
def upload_file(client, local_path: str, key: str, cache_control: str = 'private, max-age=0, no-transform'):
if not os.path.isfile(local_path):
print(f"WARN: File missing, skipping: {local_path}")
return
print(f"Upload: {local_path} -> s3://{BUCKET_NAME}/{key}")
extra_args = { 'CacheControl': cache_control }
client.upload_file(local_path, BUCKET_NAME, key, ExtraArgs=extra_args)
def upload_directory(client, local_directory: str, destination_prefix: str):
if not os.path.isdir(local_directory):
print(f"WARN: Data directory missing: {local_directory}")
return
for root, _, files in os.walk(local_directory):
for fname in files:
full = os.path.join(root, fname)
rel = os.path.relpath(full, local_directory)
key = join_key(destination_prefix, rel)
upload_file(client, full, key)
def ensure_bucket(client):
"""Ensure bucket exists; provide diagnostics if HeadBucket returns 400/other errors."""
try:
client.head_bucket(Bucket=BUCKET_NAME)
if MINIO_DEBUG:
print(f"[DEBUG] Bucket '{BUCKET_NAME}' exists.")
return
except ClientError as e:
code = e.response.get('Error', {}).get('Code')
status = e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
print(f"HeadBucket failed (code={code}, status={status}).")
# List buckets for diagnostics
try:
resp = client.list_buckets()
bucket_names = [b['Name'] for b in resp.get('Buckets', [])]
print(f"Available buckets: {bucket_names or 'None'}")
except Exception as le:
print(f"WARN: list_buckets failed: {le}")
if code in ('404', 'NoSuchBucket', 'NotFound'):
print(f"Bucket '{BUCKET_NAME}' not found. Attempting to create...")
try:
client.create_bucket(Bucket=BUCKET_NAME)
print(f"Created bucket '{BUCKET_NAME}'.")
return
except ClientError as ce:
print(f"ERROR: Cannot create bucket: {ce}")
sys.exit(2)
if status == 400:
print("HINTS: \n - Verify endpoint URL (MINIO_ENDPOINT).\n - Ensure no trailing slash in endpoint.\n - Check that TLS verify matches server cert (set MINIO_TLS_VERIFY=0 to test).\n - Confirm bucket name is correct and DNS compatible.\n - Credentials may lack permission: verify access key policies.")
# Retry once forcing path style if not already
if MINIO_ADDRESSING != 'path':
print("Retrying with path-style addressing...")
os.environ['MINIO_ADDRESSING_STYLE'] = 'path'
new_client = s3_client()
try:
new_client.head_bucket(Bucket=BUCKET_NAME)
print("Second attempt succeeded with path-style addressing.")
return
except ClientError as e2:
print(f"Second HeadBucket attempt failed: {e2}")
print(f"ERROR: head_bucket ultimately failed: {e}")
sys.exit(2)
# =============================================================================
# MAIN
# =============================================================================
def main():
dest_prefix = normalize_prefix(DESTINATION_DIR)
backups_prefix = normalize_prefix(BACKUPS_DIR) if BACKUPS_DIR else ''
client = create_validated_client()
if MINIO_DEBUG:
print("[DEBUG] Starting ensure_bucket phase...")
ensure_bucket(client)
if CREATE_BACKUP:
ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
backup_folder = f"backup_{ts}"
print(f"Creating backup under '{backups_prefix}/{backup_folder}' from prefix '{dest_prefix}'")
backup_existing_files(client, dest_prefix, backups_prefix, backup_folder)
# Firmware
if UPLOAD_FIRMWARE:
firmware_key = join_key(dest_prefix, 'firmware.bin') if dest_prefix else 'firmware.bin'
upload_file(client, LOCAL_FIRMWARE_PATH, firmware_key)
# Manifest
if UPLOAD_MANIFEST:
manifest_key = join_key(dest_prefix, 'update.json') if dest_prefix else 'update.json'
upload_file(client, LOCAL_MANIFEST_PATH, manifest_key)
# Data directory
if UPLOAD_DATA:
data_prefix = join_key(dest_prefix, 'data') if dest_prefix else 'data'
upload_directory(client, LOCAL_DATA_DIRECTORY, data_prefix)
print("All uploads complete.")
if __name__ == '__main__':
main()

View File

@ -1,623 +0,0 @@
{{NAVBAR}}
<!DOCTYPE html>
<html>
<head>
<title>App Control Configuration</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="global-style.css" rel="stylesheet">
<style>
#table{
overflow: auto;
}
#first_td_th {
width:325px;
}
#v2_td_th {
width:200px;
}
#color-picker{
margin: 0;
padding: 0;
border: 0;
width: 35px;
height: 35px;
background-color: transparent;
}
#select-anim {
width:175px;
}
input{
cursor:pointer;
}
#slide-density{
width: 70%;
}
#slide-speed{
accent-color: rgb(181, 53, 53);
width: 70%;
}
input::-webkit-slider-runnable-track {
width: 450px;
/*background: linear-gradient(to right, red, orange, yellow, lime, green, Turquoise, Cyan, Blue, Violet, Magenta, Crimson, red);*/
border: none;
border-radius: 15px;
}
</style>
</head>
<body>
<h1>App Control Configuration</h1>
<!--<form action="/upload" method="POST" enctype="multipart/form-data"> -->
<fieldset>
<legend>Saved Animation Profiles</legend>
<table>
<tr>
<td id="first_td_th">
<label for="selSavedAnimProfiles">Profiles:</label>
<select name= "selSavedAnimProfiles" id="selSaveddAnimProfiles" style="width:150px">
<option>(1)</option>
<option>(2)</option>
<option>(3)</option>
<option>(4)</option>
<option>(5)</option>
<option>(6)</option>
<option>(7)</option>
<option>(8)</option>
</select>
&emsp;&emsp;
<label for="profileName">Name:</label>
<input type="text" name="inputProfileName" id="profileName">
&emsp;&emsp;&emsp;
<button id="saveProfile" onclick="sendProfilesToServer()">Save Profile</button>
</td>
</tr>
</table>
</fieldset>
<tr><td>
<div id="spacer-20"></div>
</td></tr>
<fieldset>
<legend id="legendLights">Countdown Animation ( White Fill )</legend>
<table>
<tr>
<td id="first_td_th">
<label for="constlight">Light Min: (0-100)</label> <br>
<input type="range" name="constLightMin" id="constlight" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="constlight">Light Max:</label> <br>
<input type="range" name="constLightMax" id="constlight" min="0" max="100" value="90" step="1">
</td>
<td>
<!-- <button>Try</button> -->
</td>
</tr>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
<tr>
<td id="first_td_th">
<label for="Holdtime">Hold time(ms):</label><br>
<input type="number" name="holdTime" id="holdtime" min="0" max="5000" value="500">
</td>
<td>
<label for="Ramptime">Ramp down(ms):</label><br>
<input type="number" name="rampTime" id="Ramptime" min="0" max="5000" value="500">
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim0">Event 1</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim0" id="select-anim0">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim0" id="color-picker" >&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim0" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density0" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed0" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(0)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim1">Event 2</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim1" id="select-anim1">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim1" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim1" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density1" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed1" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(1)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim2">Event 3</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim2" id="select-anim2">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim2" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim2" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density2" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed2" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(2)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim3">Event 4</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim3" id="select-anim3">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim3" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim3" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density3" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed3" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(3)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim4">Event 5</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim4" id="select-anim4">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim4" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim4" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density4" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed4" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(4)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim5">Event 6</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim5" id="select-anim5">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim5" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim5" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density5" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed5" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(5)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim6">Event 7</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim6" id="select-anim6">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim6" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim6" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density6" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed6" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(6)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset>
<legend id="legendAnim7">Event 8</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim7" id="select-anim7">
<option>Animation 1</option>
<option>Animation 2</option>
</select>
</td>
<td>
<label for="color-picker">Main:</label><br>
<input type="color" name="main-color-anim7" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Base:</label><br>
<input type="color" name="base-color-anim7" id="color-picker">&emsp;
</td>
<td>
<label for="slide-density">Density:</label> <br>
<input type="range" name="slide-density7" id="slide-density" min="0" max="100" value="25" step="1">
</td>
<td>
<label for="slide-speed">Speed:</label> <br>
<input type="range" name="slide-speed7" id="slide-speed" min="0" max="100" value="25" step="1">
</td>
<td>
<button onclick="sendPlayAnim(7)">Try</button>
</td>
</tr>
</table>
</fieldset>
<!--</form> -->
<script>
//const websocket = new WebSocket(`ws://${window.location.hostname}/wsdata`);
//const websocket = new WebSocket({{WEBSOCKET_ADDR}});
// Initialize Names
var animProf = [8];
var profilesJson;
const inputProfileName = document.getElementsByName('inputProfileName')[0];
const selSavedAnimProfiles = document.getElementsByName('selSavedAnimProfiles')[0];
const constLightMin = document.getElementsByName('constLightMin')[0];
const constLightMax = document.getElementsByName('constLightMax')[0];
const holdTime = document.getElementsByName('holdTime')[0];
const rampTime = document.getElementsByName('rampTime')[0];
for (let i = 0; i < 8; i++) {
animProf[i] = {
selAnim: document.getElementsByName('sel-anim' + i)[0],
colMain: document.getElementsByName('main-color-anim' + i)[0],
colBase: document.getElementsByName('base-color-anim' + i)[0],
density: document.getElementsByName('slide-density' + i)[0],
speed: document.getElementsByName('slide-speed' + i)[0]
};
}
// When new profile is selected
selSaveddAnimProfiles.addEventListener('change', function(event) {
const selectedValue = event.target.selectedIndex;
console.log('Selected value:', selectedValue);
setProfile(selectedValue);
});
// update the animation data
function setProfile(index){
// update profiles
inputProfileName.value = profilesJson.profiles[index].name;
//selSavedAnimProfiles.selectedIndex = index;
constLightMin.value = profilesJson.countdown.min;
constLightMax.value = profilesJson.countdown.max;
holdTime.value = profilesJson.countdown.hold;
rampTime.value = profilesJson.countdown.ramp;
for (let i = 0; i < 8; i++) {
animProf[i].selAnim.selectedIndex = profilesJson.profiles[index].anims[i].anim;
animProf[i].colMain.value = profilesJson.profiles[index].anims[i].colmain;
animProf[i].colBase.value = profilesJson.profiles[index].anims[i].colbase;
animProf[i].density.value = profilesJson.profiles[index].anims[i].density;
animProf[i].speed.value = profilesJson.profiles[index].anims[i].speed;
}
}
/*
// Send test animation for viewing
function sendPlayAnim(animIndex){
const type = 'try-anim';
console.log("try index= " + animIndex);
profileIndex = selSavedAnimProfiles.selectedIndex;
const playAnim = {
anim: animProf[animIndex].selAnim.selectedIndex,
colmain: animProf[animIndex].colMain.value,
colbase: animProf[animIndex].colBase.value,
density: animProf[animIndex].density.value,
speed: animProf[animIndex].speed.value
};
const payload = { type:"try-anim", data: JSON.stringify(playAnim) }
console.log(payload);
websocket.send( JSON.stringify(payload) );
}
// Send complete json to server
function sendProfilesToServer(event) {
//event.preventDefault();
var index = selSavedAnimProfiles.selectedIndex;
profilesJson.profiles[index].name = inputProfileName.value;
profilesJson.countdown = {
min: constLightMin.value,
max: constLightMax.value,
hold: holdTime.value,
ramp: rampTime.value
};
for(let i = 0; i < 8; i++){
profilesJson.profiles[index].anims[i] = {
anim: animProf[i].selAnim.selectedIndex,
colmain: animProf[i].colMain.value,
colbase: animProf[i].colBase.value,
density: animProf[i].density.value,
speed: animProf[i].speed.value
};
}
const payload = { type:"save-profiles", data: JSON.stringify(profilesJson) }
//console.log(profilesJson);
websocket.send( JSON.stringify(payload) );
};
// Receive profiles json
websocket.onmessage = (event) => {
try{
console.log('Received data:', event.data);
profilesJson = JSON.parse(event.data);
index = profilesJson.index;
inputProfileName.value = profilesJson.profiles[index].name;
//Upate profile list
for(let i = 0; i < 8; i++){
opText = selSavedAnimProfiles.options[i].textContent + ' - ' + profilesJson.profiles[i].name;
selSavedAnimProfiles.options[i].textContent = opText;
}
setProfile(index);
}
catch(error){
console.error('Error parsing received JSON data:', error);
}
};
// Get profiles json
websocket.onopen = () => {
const type = 'get-profiles';
const data = {};
const payload = { type: type, data: data }
websocket.send(JSON.stringify(payload));
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
};
function handleColorChange(event) {
const colorPicker = event.target;
const colorValue = colorPicker.value;
// Extract the hue and lightness values from the color value
const hue = colorValue.substring(1, 4);
const lightness = colorValue.substring(5, 7);
// Set the saturation value to a fixed value (e.g., 100%)
const lockedColorValue = `hsl(${hue}, 100%, ${lightness}%)`;
colorPicker.value = lockedColorValue;
}
*/
// save profilesJson
function postProfiles(){
fetch('/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'type': 'profiles'
} ,
body: JSON.stringify(jsonProfiles)
})
.then(response => {
if (response.ok) {
// Handle successful response
console.log('Request successful');
} else {
throw new Error('Request failed');
}
})
.catch(error => {
// Handle any errors that occurred during the request
console.error(error);
});
}
// send anim to test out
function postPlayAnim(animIndex){
fetch('/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'type': 'play-anim'
} ,
body: JSON.stringify(jsonProfiles.profiles[0])
})
.then(response => {
if (response.ok) {
// Handle successful response
console.log('Request successful');
} else {
throw new Error('Request failed');
}
})
.catch(error => {
// Handle any errors that occurred during the request
console.error(error);
});
}
// Get profiles
function getProfiles(){
fetch('/get',{
method: 'GET',
headers:{'type':'profiles'}
})
.then(response => {
const index = response.headers.get('index');
if (response.ok) {
return response.json();
} else {
throw new Error('Request failed');
}
})
.then(data => {
profilesJsaon = data;
console.log(data);
setProfile(index);
})
.catch(error => {
console.error(error);
});
}
</script>
</body>
</html>

View File

@ -1,508 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATA Firmware Update</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
text-align: center;
}
h1 {
font-size: 22px;
margin-bottom: 20px;
}
.status-container {
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 15px;
}
.status-indicator-ble {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-wifi {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-internet {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
/* Adds space above the WiFi Connect button */
.btn-container.wifi {
margin-top: 20px;
}
button {
flex: 1;
max-width: 130px;
padding: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background 0.3s ease;
}
button:disabled {
background-color: #ccc;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
textarea {
width: 100%;
height: 300px;
font-size: 14px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: none;
}
.input-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-top: 15px;
}
input {
width: 90%;
max-width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input::placeholder {
text-align: center;
}
@media (max-width: 480px) {
body {
padding: 15px;
}
h1 {
font-size: 20px;
}
button {
font-size: 14px;
padding: 8px;
}
input, textarea {
font-size: 14px;
}
}
</style>
</head>
<body>
<h1>ATA Firmware Update</h1>
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">Device: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-wifi"></span>
<label id="status-wifi-client">Wifi Client: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-internet"></span>
<label id="status-internet">Internet: ...</label>
</div>
<div class="status-container">
<label id="status-current-version">Curr Version: ...</label>
</div>
<div class="status-container">
<label id="status-new-version">New Version: ...</label>
</div>
<!-- Buttons -->
<div class="btn-container">
<button id="bleConnectBtn">Connect</button>
<button id="checkStatusBtn" disabled>Check Status</button>
</div>
<!-- Log Area -->
<textarea id="logArea" readonly></textarea>
<div class="btn-container">
<button id="checkVersionBtn" disabled>Check Version</button>
<button id="startUpgradeBtn" disabled>Start Update</button>
</div>
<!-- Wi-Fi Input Fields -->
<div class="input-container">
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div style="display: flex; align-items: center; gap: 5px;">
<input type="checkbox" id="showPassword" style="width: auto;">
<label for="showPassword">Show Password</label>
</div>
</div>
<!-- Added margin-top above this button -->
<div class="btn-container wifi">
<button id="wifiConnectBtn" disabled>Connect Wifi</button>
</div>
<script>
// Constants
const BLE_SERVER_NAME = "ATALIGHTS"; // Replace with your server name
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0"; // Replace with your service UUID
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
let bleDevice = null;
let bleCharacteristic1 = null;
let bleCharacteristic2 = null;
let bleConnected = false;
const WIFI_STAT = { WIFI_DISCONNECTED:0, WIFI_BAD_CREDS:1, WIFI_NO_AP:2, WIFI_CONNECTED:3 };
let updatePacket = {
wifiConnected: false,
wifiOnline: false,
wifiIP: [0, 0, 0, 0],
currVersion: [0, 0, 0],
newVersion: [0, 0, 0]
};
// Log messages to the textarea
function logMessage(message) {
const logArea = document.getElementById('logArea');
logArea.value += message + '\n';
logArea.scrollTop = logArea.scrollHeight;
}
// Function to scan for BLE devices
async function scanForDevices() {
logMessage('Scanning for BLE devices...');
try {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [BLE_SERVICE_UUID]
});
if (device) {
logMessage(`Found device: ${device.name || "Unnamed"} (ID: ${device.id})`);
} else {
logMessage('No devices found.');
}
} catch (error) {
logMessage(`Scan failed: ${error.message}`);
}
}
// Function to connect to the BLE server
async function connectToBle() {
try {
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ name: BLE_SERVER_NAME }],
optionalServices: [BLE_SERVICE_UUID]
});
//logMessage(`Connecting to ${bleDevice.name}`);
const server = await bleDevice.gatt.connect();
//await server.setPreferredMtu(247); // Request larger MTU size
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
// Subscribe to notifications
//await bleCharacteristic1.startNotifications();
// Add event listener for incoming notifications
//bleCharacteristic1.addEventListener('characteristicvaluechanged', handleChar1Notifications);
//logMessage('Getting characteristic...');
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
// Subscribe to notifications
await bleCharacteristic2.startNotifications();
// Add event listener for incoming notifications
bleCharacteristic2.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const decoder = new TextDecoder();
const decodedValue = decoder.decode(value);
logMessage('--> ' + decodedValue);
});
bleConnected = true;
document.getElementById('bleConnectBtn').disabled = true;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
document.getElementById('status-ble-connection').textContent = 'Device: Connected';
document.getElementById('wifiConnectBtn').disabled = false;
document.getElementById('checkStatusBtn').disabled = false;
logMessage(`Connected to ${bleDevice.name}`);
await readPacket();
processUpdatePacket(updatePacket);
} catch (error) {
if (error.message.includes("cancelled")) {
logMessage("Connection cancelled by user.");
} else {
logMessage(`Connection failed: ${error.message}`);
}
}
}
async function sendPacket(packetMsg) {
if (!bleCharacteristic1) {
console.log("Cannot send packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
//logMessage(`Sending request: ${packetMsg} (Attempt ${attempt + 1})`);
const encoder = new TextEncoder();
await bleCharacteristic1.writeValueWithResponse(encoder.encode(packetMsg));
//console.log("Request sent successfully");
return;
} catch (error) {
console.error(`Failed to send packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to send request.");
}
}
}
}
async function readPacket() {
if (!bleCharacteristic1) {
console.log("Cannot read packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
const value = await bleCharacteristic1.readValue();
const data = new Uint8Array(value.buffer);
if (data.length === 12) {
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.wifiOnline = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
//processUpdatePacket(updatePacket);
return;
}
console.log("Invalid packet length");
return;
} catch (error) {
console.error(`Failed to read packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to read packet.");
}
}
}
}
// Process update packet
function processUpdatePacket(packet) {
// Process the packet data
//console.log("Processing update packet:", packet);
if(packet.wifiConnected === true) {
if(packet.wifiConnected && packet.wifiIP[0] > 0) {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected (' + packet.wifiIP.join('.') + ')';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected';
}
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'green';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: ...';
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'gray';
}
if(packet.wifiOnline === true) {
document.getElementById('status-internet').textContent = 'Online';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'green';
document.getElementById('checkVersionBtn').disabled = false;
} else {
document.getElementById('status-internet').textContent = 'Offline';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'gray';
document.getElementById('checkVersionBtn').disabled = true;
}
if (packet.currVersion[0] > 0) {
document.getElementById('status-current-version').textContent = 'Curr Version: ' + packet.currVersion.join('.');
} else {
document.getElementById('status-current-version').textContent = 'Curr Version: ...';
}
if (packet.newVersion[0] > 0) {
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
document.getElementById('checkVersionBtn').disabled = true;
if(packet.wifiOnline && packet.newVersion[0] > packet.currVersion[0] ||
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] > packet.currVersion[1]) ||
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] === packet.currVersion[1] && packet.newVersion[2] > packet.currVersion[2])) {
//enable start upgrade button
logMessage("New Version Available:");
document.getElementById('startUpgradeBtn').disabled = false;
} else {
//disable start upgrade button
document.getElementById('startUpgradeBtn').disabled = true;
logMessage("New Version: Not Available");
}
} else {
document.getElementById('status-new-version').textContent = 'New Version: ...';
}
}
//BLE_Characteristic.addEventListener('characteristicvaluechanged', handleNotifications);
function handleChar1Notifications(event) {
const data = new Uint8Array(event.data);
if (data.length !== 12) { // 1 byte for id, 4 bytes for booleans, 4 bytes for wifiIP, 3 bytes for currVersion, 3 bytes for newVersion
console.log("Invalid packet length");
return;
}
// Update existing updatePacket object instead of creating new one
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.internetAvailable = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
processUpdatePacket(updatePacket);
}
document.getElementById('showPassword').addEventListener('change', function() {
const passwordInput = document.getElementById('wifipassword');
passwordInput.type = this.checked ? 'text' : 'password';
});
// Event listeners for buttons
document.getElementById('bleConnectBtn').addEventListener('click', connectToBle);
document.getElementById('checkStatusBtn').addEventListener('click', async () => {
if (bleCharacteristic1) {
await readPacket();
processUpdatePacket(updatePacket);
} else {
logMessage('BLE device not connected.');
}
});
document.getElementById('checkVersionBtn').addEventListener('click', async () => {
await sendPacket('version-check');
// loop and monitor the the updatePacket.newVersion
await new Promise(resolve => setTimeout(resolve, 2000));
success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
success = true;
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if(success) {
processUpdatePacket(updatePacket);
logMessage("New Version: Available");
} else {
logMessage("New Version: Not Available");
}
});
document.getElementById('wifiConnectBtn').addEventListener('click', async () => {
const ssid = document.getElementById('wifissid').value;
const password = document.getElementById('wifipassword').value;
if (ssid && password) {
// Send credentials to the device
jsonString = ' {"ssid":"' + ssid + '","pass":"' + password + '"} ';
await sendPacket('wifi-connect' + jsonString);
await readPacket();
processUpdatePacket(updatePacket);
} else {
alert('Please enter both SSID and password.');
}
});
document.getElementById('startUpgradeBtn').addEventListener('click', async () => {
try {
await sendPacket('upgrade-start');
logMessage("Upgrade Starting... Please wait.");
} catch (error) {
logMessage(`Error starting upgrade: ${error.message}`);
}
});
// Initial call to process the update packet with defaults
processUpdatePacket(updatePacket);
</script>
</body>
</html>

View File

@ -1,547 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATA Firmware Update</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
text-align: center;
}
h1 {
font-size: 22px;
margin-bottom: 5px;
}
.status-container {
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 4px;
}
.status-indicator-ble {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-wifi {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-internet {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
/* Adds space above the WiFi Connect button */
.btn-container.wifi {
margin-top: 20px;
}
button {
flex: 1;
max-width: 130px;
padding: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background 0.3s ease;
}
button:disabled {
background-color: #ccc;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
textarea {
width: 100%;
height: 300px;
font-size: 14px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: none;
}
.input-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-top: 15px;
}
input {
width: 90%;
max-width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input::placeholder {
text-align: center;
}
@media (max-width: 480px) {
body {
padding: 15px;
}
h1 {
font-size: 20px;
}
button {
font-size: 14px;
padding: 8px;
}
input, textarea {
font-size: 14px;
}
}
</style>
</head>
<body>
<h1>ATA Firmware Update</h1>
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">BLE Status: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-wifi"></span>
<label id="status-wifi-client">Wifi Client: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-internet"></span>
<label id="status-internet">Internet: ...</label>
</div>
<div class="status-container">
<label id="status-current-version">Curr Version: ...</label>
</div>
<div class="status-container">
<label id="status-new-version">New Version: ...</label>
</div>
<div class="btn-container">
<label id="ble-device-name">Device Name:</label>
</div>
<div class="btn-container">
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
</div>
<!-- Buttons -->
<div class="btn-container">
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
</div>
<!-- Log Area -->
<textarea id="logArea" readonly></textarea>
<div class="btn-container">
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
</div>
<!-- Wi-Fi Input Fields -->
<div class="input-container">
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div style="display: flex; align-items: center; gap: 5px;">
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
<label for="showPassword">Show Password</label>
</div>
</div>
<!-- Added margin-top above this button -->
<div class="btn-container wifi">
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
</div>
<script>
(function(){
'use strict';
// Constants
const BLE_SERVER_NAME = "ATALIGHTS"; // Replace with your server name
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0"; // Replace with your service UUID
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
let bleDevice = null;
let bleCharacteristic1 = null;
let bleCharacteristic2 = null;
let bleConnected = false;
const WIFI_STAT = { WIFI_DISCONNECTED:0, WIFI_BAD_CREDS:1, WIFI_NO_AP:2, WIFI_CONNECTED:3 };
const updatePacket = {
wifiConnected: false,
wifiOnline: false,
wifiIP: [0, 0, 0, 0],
currVersion: [0, 0, 0],
newVersion: [0, 0, 0]
};
// Function to automatically start the connection process when the page loads
function autoStart() {
document.getElementById('input-DeviceName').value = BLE_SERVER_NAME;
}
// Call autoStart when the page loads
window.addEventListener('DOMContentLoaded', autoStart);
function compareVersions(a, b){
for(let i=0;i<3;i++){ if(a[i] > b[i]) return 1; if(a[i] < b[i]) return -1; }
return 0;
}
// Log messages to the textarea
function logMessage(message) {
const logArea = document.getElementById('logArea');
logArea.value += message + '\n';
logArea.scrollTop = logArea.scrollHeight;
}
// Function to scan for BLE devices
async function scanForDevices() {
logMessage('Scanning for BLE devices...');
try {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [BLE_SERVICE_UUID]
});
if (device) {
logMessage(`Found device: ${device.name || "Unnamed"} (ID: ${device.id})`);
} else {
logMessage('No devices found.');
}
} catch (error) {
logMessage(`Scan failed: ${error.message}`);
}
}
// Function to connect to the BLE server
async function connectToBle() {
if(!navigator.bluetooth){
logMessage('Web Bluetooth not supported in this browser.');
return;
}
try {
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ name: document.getElementById('input-DeviceName').value }],
optionalServices: [BLE_SERVICE_UUID]
});
//logMessage(`Connecting to ${bleDevice.name}`);
const server = await bleDevice.gatt.connect();
//await server.setPreferredMtu(247); // Request larger MTU size
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
// Subscribe to notifications
//await bleCharacteristic1.startNotifications();
// Add event listener for incoming notifications
//bleCharacteristic1.addEventListener('characteristicvaluechanged', handleChar1Notifications);
//logMessage('Getting characteristic...');
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
// Subscribe to notifications
await bleCharacteristic2.startNotifications();
// Add event listener for incoming notifications
bleCharacteristic2.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const decoder = new TextDecoder();
const decodedValue = decoder.decode(value);
logMessage('--> ' + decodedValue);
});
bleConnected = true;
// Auto-reconnect / state reset handler
bleDevice.addEventListener('gattserverdisconnected', handleDisconnect);
document.getElementById('bleConnectBtn').disabled = true;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
document.getElementById('status-ble-connection').textContent ="BLE Status: Connected";
document.getElementById('wifiConnectBtn').disabled = false;
document.getElementById('checkStatusBtn').disabled = false;
logMessage(`Connected to ${bleDevice.name}`);
await readPacket();
processUpdatePacket(updatePacket);
} catch (error) {
if (error.message.includes("cancelled")) {
logMessage("Connection cancelled by user.");
} else {
logMessage(`Connection failed: ${error.message}`);
}
}
}
async function wifiConnect() {
const ssid = document.getElementById('wifissid').value;
const password = document.getElementById('wifipassword').value;
if (ssid && password) {
// Send credentials to the device (retain original spacing format expected by firmware)
const jsonString = ' {"ssid":"' + ssid.trim() + '","pass":"' + password + '"} ';
await sendPacket('wifi-connect' + jsonString);
await readPacket();
processUpdatePacket(updatePacket);
} else {
alert('Please enter both SSID and password.');
}
}
async function checkStatus() {
if (bleCharacteristic1) {
await readPacket();
processUpdatePacket(updatePacket);
} else {
logMessage('BLE device not connected.');
}
}
async function checkVersion() {
await sendPacket('version-check');
// loop and monitor the the updatePacket.newVersion
await new Promise(resolve => setTimeout(resolve, 2000));
let success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
success = true;
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if(success) {
processUpdatePacket(updatePacket);
logMessage("New Version: Available");
} else {
logMessage("New Version: Not Available");
}
}
async function startUpgrade() {
try {
await sendPacket('upgrade-start');
logMessage("Upgrade Starting... Please wait.");
} catch (error) {
logMessage(`Error starting upgrade: ${error.message}`);
}
}
function handleDisconnect(){
bleConnected = false;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'gray';
document.getElementById('status-ble-connection').textContent = 'BLE Status: Disconnected';
document.getElementById('bleConnectBtn').disabled = false;
document.getElementById('checkStatusBtn').disabled = true;
document.getElementById('wifiConnectBtn').disabled = true;
logMessage('BLE disconnected');
}
async function sendPacket(packetMsg) {
if (!bleCharacteristic1) {
console.log("Cannot send packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
//logMessage(`Sending request: ${packetMsg} (Attempt ${attempt + 1})`);
const encoder = new TextEncoder();
await bleCharacteristic1.writeValueWithResponse(encoder.encode(packetMsg));
//console.log("Request sent successfully");
return;
} catch (error) {
console.error(`Failed to send packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to send request.");
}
}
}
}
async function readPacket() {
if (!bleCharacteristic1) {
console.log("Cannot read packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
const value = await bleCharacteristic1.readValue();
const data = new Uint8Array(value.buffer);
if (data.length === 12) {
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.wifiOnline = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
//processUpdatePacket(updatePacket);
return;
}
console.log("Invalid packet length");
return;
} catch (error) {
console.error(`Failed to read packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to read packet.");
}
}
}
}
// Process update packet
function processUpdatePacket(packet) {
// Process the packet data
//console.log("Processing update packet:", packet);
if(packet.wifiConnected === true) {
if(packet.wifiConnected && packet.wifiIP[0] > 0) {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected (' + packet.wifiIP.join('.') + ')';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected';
}
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'green';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: ...';
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'gray';
}
if(packet.wifiOnline === true) {
document.getElementById('status-internet').textContent = 'Online';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'green';
document.getElementById('checkVersionBtn').disabled = false;
} else {
document.getElementById('status-internet').textContent = 'Offline';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'gray';
document.getElementById('checkVersionBtn').disabled = true;
}
if (packet.currVersion[0] > 0) {
document.getElementById('status-current-version').textContent = 'Curr Version: ' + packet.currVersion.join('.');
} else {
document.getElementById('status-current-version').textContent = 'Curr Version: ...';
}
if (packet.newVersion[0] > 0) {
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
document.getElementById('checkVersionBtn').disabled = true;
logMessage("Latest Update is: " + packet.newVersion.join('.'));
if(packet.wifiOnline && compareVersions(packet.newVersion, packet.currVersion) > 0) {
//enable start upgrade button
logMessage("New Version: Available");
document.getElementById('startUpgradeBtn').disabled = false;
} else {
//disable start upgrade button
document.getElementById('startUpgradeBtn').disabled = true;
logMessage("New Version: Not Available");
}
} else {
document.getElementById('status-new-version').textContent = 'New Version: ...';
}
}
//BLE_Characteristic.addEventListener('characteristicvaluechanged', handleNotifications);
function handleChar1Notifications(event) {
const data = new Uint8Array(event.data);
if (data.length !== 12) { // 1 byte for id, 4 bytes for booleans, 4 bytes for wifiIP, 3 bytes for currVersion, 3 bytes for newVersion
console.log("Invalid packet length");
return;
}
// Update existing updatePacket object instead of creating new one
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.wifiOnline = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
processUpdatePacket(updatePacket);
}
// Event listeners for buttons
function togglePasswordVisibility() {
const passwordInput = document.getElementById('wifipassword');
passwordInput.type = this.checked ? 'text' : 'password';
}
// Initial call to process the update packet with defaults
processUpdatePacket(updatePacket);
})();
</script>
</body>
</html>

View File

@ -1,16 +0,0 @@
{{NAVBAR}}
<!DOCTYPE html>
<html lang="en">
<head>
<title>BLE Config</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="global-style.css" rel="stylesheet">
<style>
</style>
</head>
<body>
<h1>App Control - Bluetooth Configuration</h1>
</body>
</html>

View File

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Color Picker</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="pageTitle" ></div>
<div class="secTitle"> <h1 class="secTitleFont">Countdown Animation</h1> </div>
<div class="secCntrls" height: 200px>
<label for="dropList">Animation: </label>
<select class="dropList">
<option>Custom 1</option>
<option>Custom 2</option>
<option>Custom 3</option>
<option>Custom 4</option>
<option>Rainbow</option>
<option>Chasing</option>
<option>Meteors</option>
<option>Fire (red)</option>
</select>
<label for="color-picker">Color: </label>
<input type="color" id="color-picker">
</div>
<div class="secTitle"> <h1 class="secTitleFont"> Selection Screen</h1> </div>
<div class="secCntrls">
</div>
</body>
</html>

View File

@ -1,71 +0,0 @@
#include "command_processor.h"
int boothState[16];
void process_command(int* data, int paramCount)
{
bool reply = false;
switch(*data){
case COMM_NULL:
break;
case COMM_REBOOT:
break;
case COMM_DIN:
break;
case COMM_DOUT:
break;
case COMM_RELAY:
break;
case COMM_AOUT:
break;
case COMM_AIN:
break;
case COMM_ECHO:
break;
case COMM_LED_STATUS:
break;
case COMM_GET_TEMP:
break;
case COMM_PLAY:
break;
case COMM_RF:
break;
case COMM_NEST:
break;
case COMM_BOOTH_STATE:
break;
case COMM_STRIP1:
break;
case COMM_STRIP2:
break;
case COMM_OLED:
break;
default:;
}
if(reply){
}
}
/*
int AppStates[16];
int getEvent(int index, int& ev){
return ev[index];
}
enum BoothStates = { }
void RunAnimation(int stateIndex){
switch (AppStates[stateIndex]){
case 0:
break;
case 1:
break;
}
}
*/

View File

@ -1,31 +0,0 @@
#ifndef _COMMAND_PROCESSOR_H
#define _COMMAND_PROCESSOR_H
extern int boothState[16];
void process_command(int* data, int paramCount);
enum COMM_FUNC{
COMM_NULL = 0,
COMM_REBOOT = 1,
COMM_DIN = 2,
COMM_DOUT = 3,
COMM_RELAY = 4,
COMM_AOUT = 5,
COMM_AIN = 6,
COMM_ECHO = 7,
COMM_LED_STATUS = 8,
COMM_GET_TEMP = 9,
COMM_PLAY = 10,
COMM_RF = 11,
COMM_NEST = 30,
COMM_BOOTH_STATE = 50,
COMM_STRIP1 = 75,
COMM_STRIP2 = 100,
COMM_OLED = 125
};
#endif

View File

@ -1,145 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Event Container</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 40vh;
background-color: #f0f0f0;
}
.outer-container {
display: flex;
justify-content: center;
align-items: center;
min-width: 300px;
max-width: 600px;
width: 100%;
padding: 20px;
}
.container {
border: 1px solid #ccc;
padding: 5px 20px 5px 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
width:90%;
/*margin:0 auto;*/
}
.select-container {
margin-bottom: 15px;
}
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
}
.row > * {
flex: 0 0 calc(50% - 20px); /* Adjusted width for better fit */
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 1px;
}
select, input[type="range"], input[type="color"] {
width:90%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
select{
width: 90%;
}
input[type="color"] {
height: 30px;
padding: 0;
width: 30px;
}
.row:last-child {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.checkbox-container {
display: flex;
align-items: center;
justify-content: left;
}
.try-button-container {
display: flex;
justify-content: right;
width: 100%;
}
button {
background-color: #007bff;
color: #fff;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.center-text {
text-align: center;
font-size: larger;
}
</style>
</head>
<body>
<div class="outer-container">
<div class="container">
<legend class="center-text">Event0</legend><br>
<div class="row">
<div>
<label for="animation-list" id="list-label">Event:</label>
<select id="animation-list">
</select>
</div>
<div>
<label for="color-picker">Color:</label>
<input type="color" id="color-picker">
</div>
</div>
<div class="row">
<div>
<label for="speed" id="speed-label">Speed:</label>
<input type="range" id="speed" min="1" max="10">
</div>
<div>
<label for="range1">Col. Range:</label>
<input type="range" id="range1" min="0" max="100">
</div>
</div>
<div class="row">
<div>
<label for="param1" id="param1-label">Param1:</label>
<input type="range" id="param1" min="0" max="50">
</div>
<div>
<label for="param2" id="param2-label">Param2:</label>
<input type="range" id="param2" min="0" max="100">
</div>
</div>
<div class="row">
<div class="checkbox-container">
<input type="checkbox" id="check1">Mix</input>&nbsp;
<input type="checkbox" id="check1">Fwd</input>
</div>
<div class="try-button-container">
<button id="try-button">Try</button>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,215 +0,0 @@
#ifndef _FIRMWARE_HTML_H
#define _FIRMWARE_HTML_H
#include <Arduino.h>
const char firmware_html_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<title>Firmware Update</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
h1{
text-align: center;
margin: 0;
margin-bottom: 0;
font-size: larger;
}
body {
font-family: Arial, sans-serif;
padding: 0;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
width: 100%;
max-width: 700px;
min-width: 300px;
margin: 0 auto;
}
.outer-container {
justify-content: center;
align-items: center;
padding: 4px 20px 4px 20px
}
.container {
border: 1px solid #ccc;
padding: 5px 20px 10px 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
width:90%;
margin-bottom: 20px;
}
.center-text {
text-align: center;
font-size: medium;
font-weight: bold;
margin-bottom: 10;
}
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-end;
}
.row > * {
flex: 1;
width: calc(50% - 10px);
margin-bottom: 8px;
}
.row > *:last-child {
flex: 1;
text-align: right;
}
button {
background-color: #007bff;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: auto;
}
progress{
flex: 1;
margin: 0 10px;
}
#lblprogress{
flex: 0;
}
.progress-container {
display: flex;
align-items: center;
margin-top: 10px;
}
button{
background-color: #007bff;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: auto;
}
.info {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: flex-end;
margin-top: 20px;
}
</style>
</head>
<body>
<h1 name="h1element">Firmware Update</h1>
<div class="outer-container">
<div class="container">
<legend class="center-text">Local Update</legend>
<div class="row">
<div>
<input type="file" id="update-file" name="update-file">
</div>
<div>
<button id="submit-update-local" onclick="uploadFile()">Update!</button>
</div>
</div>
<div class="progress-container">
<label id="lblprogress" for="firm-progress">Progress:</label>
<progress id="firm-progress" value="0" max="100"></progress>
<label id="lbl-firm-progress" for="firm-progress">- - - -</label>
</div>
<div class="info">
<label>File name: "ata_fw_booth_xxx.bin" or "ata_fs_booth_xxx.bin"</label>
</div>
</div>
</div>
<script>
const fileInput = document.getElementById('update-file');
const progressBar = document.getElementById('firm-progress');
const progressLabel = document.getElementById('lbl-firm-progress');
const submitButton = document.getElementById('submit-update-local');
//submitButton.addEventListener('click', uploadFile);
function uploadFile() {
event.preventDefault(); // Prevent the default form submission
const file = fileInput.files[0];
const url = '/update'; // Replace with the actual upload URL
// File Checks
//*********************************************************
// Check file extension
if (!file.name.toLowerCase().endsWith('.bin')) {
alert('Please select a file with the ".bin" extension.');
return;
}
// Check filename prefix
if (!file.name.toLowerCase().startsWith('fwata') && !file.name.toLowerCase().startsWith('lfsata')) {
alert('Please select a file with a filename starting with "fwata" or "lfsata".');
return;
}
// Check file size
const maxSizeBytes = 2.7 * 1024 * 1024; // 2.75Mb in bytes
if (file.size > maxSizeBytes) {
alert('Please select a file with a size not exceeding 2.7Mb.');
return;
}
//*********************************************************
const formData = new FormData();
formData.append('file-size', file.size); // Include the file size as a parameter
formData.append('update-file', file);
let s = "file-size: " + file.size;
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const progressPercent = Math.round((event.loaded * 100) / event.total);
progressBar.value = progressPercent;
progressLabel.innerHTML =progressBar.value + "%";
}
});
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log("received status: %d", xhr.status);
if (xhr.status === 200) {
// Upload completed successfully
alert('Upload completed!');
progressLabel.innerHTML = "Completed!";
} else if (xhr.status === 500) {
// Request was aborted (server-side)
alert('Upload aborted by the server.');
progressLabel.innerHTML = "Aborted!";
} else {
// Handle other error cases
alert('An error occurred during the upload.');
progressLabel.innerHTML = "Error!";
}
submitButton.disabled = false;
progressBar.value = 0;
progressLabel.innerHTML = "";
}
};
xhr.open('POST', url, true);
xhr.send(formData);
submitButton.disabled = true;
}
</script>
</body>
</html>
)rawliteral";
#endif

View File

@ -1,30 +0,0 @@
#ifndef CERT_H
#define CERT_H
const char * github_rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n"
"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n"
"ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n"
"MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n"
"LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n"
"RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n"
"+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n"
"PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n"
"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n"
"Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n"
"hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n"
"EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n"
"MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n"
"FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n"
"nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n"
"eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n"
"hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n"
"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n"
"vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n"
"+OkuE6N36B9K\n"
"-----END CERTIFICATE-----\n";
#endif

View File

@ -1,6 +0,0 @@
.container{
color:blue;
display: grid;
gap: 1rem;
grid-template-columns: 50% 1fr 1fr;
}

View File

@ -1,12 +0,0 @@
<head>
<link rel="stylesheet" href="gridstyles.css">
<title> CSS Grid</title>
</head>
<body>
<div class="container">
<div class="flow"> Test text 1</div>
<div class="flow"> Test text 1</div>
<div class="flow"> Test text 1</div>
<div class="flow"> Test text 1</div>
</div>
</body>

View File

@ -1 +0,0 @@
class HueSelect extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.shadowRoot.innerHTML='\n\t\t<style>\n\t\t .color-option {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tcursor: pointer;\n\t\t \t}\n\t\t \t.color-patch {\n\t\t\t\twidth: 30px;\n\t\t\t\theight: 30px;\n\t\t\t\tmargin-top: 18px;\n\t\t\t\tmargin-right: 10px;\n\t\t\t\tborder: 1px solid #000;\n\t\t \t}\n\t\t \t.hue-label {\n\t\t\t\tmargin-top: 18px;\n\t\t\t\twidth: 90px;\n\t\t\t\ttext-align: left;\n\t \t\t}\n\t\t\t.label-container {\n display: flex;\n align-items: center;\n }\n\t\t\t.dropdown {\n\t\t\t\tposition: relative;\n\t\t\t\tdisplay: inline-block;\n\t\t\t}\n\t\t \t.dropdown-content {\n\t\t\t\tdisplay: none;\n\t\t\t\tposition: absolute;\n\t\t\t\tbackground-color: #f9f9f9;\n\t\t\t\tmin-width: 180px;\n\t\t\t\tbox-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);\n\t\t\t\tz-index: 1;\n\t\t\t\tmax-height: 300px;\n\t\t\t\toverflow-y: auto;\n\t\t \t}\n\t\t\t.dropdown-content .color-option {\n\t\t\t\tpadding: 8px 12px;\n\t\t\t}\n\t\t \t.dropdown:hover .dropdown-content {\n\t\t\t\tdisplay: block;\n\t\t \t}\n\t\t</style>\n\t\t<div class="dropdown">\n\t\t \t<div class="color-option" id="selectedColor">\n\t\t\t\t<span class="color-patch" style="background-color: hsl(0, 100%, 50%)"></span>\n\t\t\t\t<div class="label-container">\n\t\t\t\t\t<label class="hue-label" id="hue-label">0</label>\n\t\t\t\t</div>\n\t\t\t</div> \n\t\t \t<div class="dropdown-content" id="colorPicker">\n\t\t\t\t\x3c!-- The options will be added dynamically using JavaScript --\x3e\n\t\t \t</div>\n\t\t</div>\n\t ',this.currentHue=0,this.colorList,this.hueLabel=this.shadowRoot.getElementById("hue-label");this.shadowRoot.getElementById("selectedColor").querySelector(".color-patch").addEventListener("mouseenter",(()=>{this.shadowRoot.querySelector(".dropdown-content").style.display="block"})),window.addEventListener("click",(t=>{const e=this.shadowRoot.querySelector(".dropdown-content");t.target.closest(".dropdown")||(e.style.display="none")})),this.createColorOptions(),this.setHue(0)}generateColors(){const t=[];for(let e=0;e<=360;e+=10)360==e&&(e=359),t.push(e);return t.push(-1),t.push(-2),t}createColorOption(t){const e=document.createElement("div");e.classList.add("color-option");const o=document.createElement("span");o.classList.add("color-patch");const n=document.createElement("span");n.classList.add("color-text"),n.textContent=t;const s=document.createElement("span");if(s.classList.add("rgb-hex"),-2===t)o.style.backgroundColor="rgb(0,0,0)",s.innerHTML="&nbsp; #000000";else if(-1===t)o.style.backgroundColor="rgb(255,255,255)",s.innerHTML="&nbsp; #FFFFFF";else{o.style.backgroundColor=`hsl(${t}, 100%, 50%)`;const e=this.hslToRgb(t,100,50).toUpperCase();s.innerHTML=`<span>&nbsp; ${e}</span>`}return e.appendChild(o),e.appendChild(n),e.appendChild(s),e.addEventListener("click",(()=>this.handleColorSelection(t))),e}createColorOptions(){const t=this.shadowRoot.querySelector(".dropdown-content");this.colorList=this.generateColors(),this.colorList.forEach((e=>{const o=this.createColorOption(e);t.appendChild(o)}))}hslToRgb(t,e,o){let n,s,l;if(t/=360,o/=100,0===(e/=100))n=s=l=o;else{const r=(t,e,o)=>(o<0&&(o+=1),o>1&&(o-=1),o<1/6?t+6*(e-t)*o:o<.5?e:o<2/3?t+(e-t)*(2/3-o)*6:t),i=o<.5?o*(1+e):o+e-o*e,d=2*o-i;n=r(d,i,t+1/3),s=r(d,i,t),l=r(d,i,t-1/3)}const r=t=>{const e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e};return`#${r(n)}${r(s)}${r(l)}`}handleColorSelection(t){this.currentHue=t;const e=this.shadowRoot.getElementById("selectedColor").querySelector(".color-patch"),o=this.getRGBfromHue(t);e.style.backgroundColor=o,console.log(e.style.backgroundColor),this.setHueLabel(t),this.hideDropdown(),this.dispatchEvent(new CustomEvent("change",{detail:{hue:t,rgb:o}}))}getSelectedHue(){return this.currentHue}getSelectedRGB(){const t=this.shadowRoot.getElementById("selectedColor").textContent.trim();return parseFloat(t)}getRGBfromHue(t){return-1==t?"#FFFFFF":-2==t?"#000000":this.hslToRgb(t,100,50)}getSelectedRgb(){const t=this.getSelectedHue();return getRGBfromHue(t)}setHue(t){let e=t;-1===t||-2===t?e=t:(e=10*Math.round(t/10),e>=360&&(e=359),e<0&&(e=0)),this.currentHue=e,this.colorList.forEach((t=>{t===e&&this.handleColorSelection(e)})),this.hideDropdown()}hideDropdown(){this.shadowRoot.querySelector(".dropdown-content").style.display="none"}setHueLabel(t){this.hueLabel.textContent="Hue: "+t}}customElements.define("hue-select",HueSelect);

File diff suppressed because it is too large Load Diff

View File

@ -1,205 +0,0 @@
#ifndef _LED_ANIMATION_H
#define _LED_ANIMATION_H
#include "common/LEDStrip.h"
#include "led_strip.h"
#include "my_board.h"
#include <functional>
#include "common/HSVTable.h"
#define HUE_MAX 767
enum EXIT_TYPE { EXIT_NORMAL, EXIT_FROM_NOTIFY, EXIT_FINISHED, EXIT_TIMEOUT };
#define HUE_CALC_METHOD 0
// TODO Fix hue range... 0-359 or 0-360;
#if HUE_CALC_METHOD == 0
#define HUEtoRGB(x) trueHSV(x)
#elif HUE_CALC_METHOD == 1
#define HUEtoRGB(x) sineHSV(x)
#elif HUE_CALC_METHOD == 2
#define HUEtoRGB(x) pwrHSV(x)
#endif
#define RGBtoHUE(x) RGBToHSV(x)
#define INFINITE_LOOP 0
enum DIR_TYPE { DT_REV, DT_FWD, DT_BOTH };
typedef struct {
int RampUpTime;
int msRampDownTime;
int msHoldTime;
int msFreq;
float minDuty;
float maxDuty;
int interval;
uint8_t pwr;
}params_whiteFill;
typedef struct {
int hue;
int cooling;
int sparking;
int interval;
uint8_t pwr;
}params_fire_hue;
typedef struct {
rgbpixel_t col1;
rgbpixel_t col2;
uint8_t range; /* hue range 0-127*/
int density;
uint8_t step; /* range step inteval*/
int interval;
uint8_t pwr;
}params_rain;
#define ANIMTESTMODE_TIMEOUT (20 * 1000) // 20 secs
typedef struct {
int EventTestCountdown = 0;
int EventsIndex; // upto 8 events based on anim-events.json
int EventsCount; // upto 8 events based on anim-events.json
int PopupAnimIndex;
bool busy;
bool repeat;
int countStatus;
}ANIM_STATUS;
typedef struct {
bool busy;
int dslrCountStatus;
}WHITE_FILL_STATUS;
extern ANIM_STATUS animStatus;
extern WHITE_FILL_STATUS whiteStatus;
float calculateSteps(int msFreq, float LEDCount, int msFillTime);
void GetOptimizedStepInterval(float &steps, int &interval, int LEDCount, int msFillTime, int startInterval, float tolerance);
float GetNextPalletHue(void);
void Init_Hue_Range_Pallet(float hue1, float hue2, int colSteps);
float GetHueRange(float hue1, float hue2);
float GetHueRangeWeb(float hue1, float hue2);
int CalcEventInterval(float speed, int min, int max);
/******************************** ANIMATION LOOP HELPER *********************************
* All Animations should use this lopp helper.
* Logic for monitoring exit notifications
* Timeout in mSec can be set to exit
**************************************************************************************/
EXIT_TYPE Animation_Loop(ANIM_STATUS &stateMon, int msFreq, TickType_t duration, std::function<int()> callback=NULL);
// min:0-100, max:0-100
//int Const_White_Fill(WHITE_FILL_STATUS &status, int msRampUpTime, int msHoldTime, int msRampDownTime, int msFreq, float minDuty, float maxDuty);
int Const_White_Fill(WHITE_FILL_STATUS &status, FRONT_LIGHT light , float delayFactor, int countDown, int msFreq);
//int Const_White_Fill_HelioType(ANIM_STATUS &stateMon, float startPoint, int msRampUpTime, int msRampDownTime, int msHoldTime, int msFreq, float minDuty, float maxDuty);
EXIT_TYPE Animation_White_Fill_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event);
EXIT_TYPE Animation_Linear_Brighten(LEDSTRIP& strip, ANIMATION_EVENT& event);
EXIT_TYPE Animation_Tick_Fill(LEDSTRIP& strip, ANIMATION_EVENT& event);
/******************************** COLORED SNAKES *********************************
* cycles=0=infinite
* (check1) "mix"=false, all appearing sectors will be the same color until the next iteration
* "mix"=true, each meteor color will be different
* (check2) "roate", add rotation to animation
* (hue range) hue range (0-360)
* (param1) sectors
* (param2) colorCount, number of color divisions
**************************************************************************************/
EXIT_TYPE Animation_Snakes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0);
void drawSnakeMix(LEDSTRIP& strip, int sectors, float hue1, float hue2, int colorCount);
/******************************** COLORED COMETS *********************************
*(check1) "mix"=false, all appearing comets will be the same color until the next iteration
* (check2) "mix"=true, each comets color will be different
* (param1) hue range (0-360)
* (param2) colorCount, number of color divisions
**************************************************************************************/
EXIT_TYPE Animation_Comets(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0);
void fadeToBlackBy(rgbpixel_t& pixel, uint8_t fadeAmount);
inline uint8_t nscale8(uint8_t i, uint8_t scale);
/******************************** COLORED SECTORS *********************************
* (check1) "mix"=false, all appearing sectors will be the same color until the next iteration
* (check2) "mix"=true, each meteor color will be different
* (param1) hue range (0-360)
* (param2) colorCount, number of color divisions
**************************************************************************************/
EXIT_TYPE Animation_Sectors(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dir=DT_BOTH, int cycles=0);
/******************************** COLORED DASHES *********************************
* (check1) "mix"=false, all appearing dashes will be the same color until the next iteration
* (check2) "mix"=true, each dash color will be different
* (param1) hue range (0-360)
* (param2) colorCount, number of color divisions
**************************************************************************************/
EXIT_TYPE Animation_Dashes(LEDSTRIP& strip, ANIMATION_EVENT& event, DIR_TYPE dirType=DT_BOTH, int cycles=0);
/******************************** HUE SPECTRUM *********************************
* (check1) "mix"=false, all appearing dashes will be the same color until the next iteration
* (check2) "mix"=true, each dash color will be different
* (param1) hue range (0-360)
* (param2) colorCount, number of color divisions
**************************************************************************************/
EXIT_TYPE Animation_Hue_Spectrum(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration);
EXIT_TYPE Animation_Hue_Spectrum_Mirrored(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration);
void Fill_Hue_Spectrum(LEDSTRIP& strip, float hue1, float hue2);
void Fill_Hue_Spectrum_Mirrored(LEDSTRIP& strip, float hue1, float hue2);
/************************ RAINBOW *********************/
EXIT_TYPE Animation_Rainbow(LEDSTRIP& strip, ANIMATION_EVENT& event, TickType_t duration);
/************************ PULSE COLOR *********************/
EXIT_TYPE Animation_Pulse_Color_Cycling(LEDSTRIP& strip, ANIMATION_EVENT& event);
/**************** FIRE ****************/
enum FIRE_COLOR { RED_FIRE, GREEN_FIRE, BLUE_FIRE};
//void Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event, FIRE_COLOR fire, TickType_t duration);
EXIT_TYPE Animation_Fire(LEDSTRIP& strip, ANIMATION_EVENT& event);
void LEDStrip_FireInit(LEDSTRIP& strip);
void Fire_Update( LEDSTRIP& strip, FIRE_COLOR fire, int Cooling, int Sparking);
rgbpixel_t GetPixelHeatColor ( FIRE_COLOR fire, uint8_t temperature);
/************************ *********************/
void Animation_Splash(LEDSTRIP& strip);
void Animation_SparkleColor(LEDSTRIP& strip, int msFreq);
void Animation_SparkleHue(LEDSTRIP& strip, ANIMATION_EVENT& event);
EXIT_TYPE Animation_Serial_Test2(LEDSTRIP& strip, int msFreq, const char* s1, const char* s2);
// msTransTime(Duration), msFreq(update interval)
void Animation_TransTo_Color(LEDSTRIP& strip, ANIM_STATUS &stateMon, int msTransTime, int msFreq, rgbpixel_t col);
void drawSectorsHueSingle(LEDSTRIP& strip, rgbpixel_t col, float hue1, float hue2, int sectors) ;
void drawSectorsHueMix(LEDSTRIP& strip, float hue1, float hue2, int sectors, int colorCount);
/************************ TEST ANIMATIONS *********************/
EXIT_TYPE Animation_SetPixel(LEDSTRIP& strip, ANIMATION_EVENT& event);
EXIT_TYPE Animation_Clear(LEDSTRIP& strip, ANIMATION_EVENT& event);
rgbpixel_t GetColorFromHue(int hue);
#endif

View File

@ -1,564 +0,0 @@
#include "led_strip.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <Arduino.h>
#include <FS.h>
#include <LittleFS.h>
#include "common/led_animation.h"
#include "common/LEDStrip.h"
#include "global.h"
#include "common/neo_colors.h"
#include "common/HSVTable.h"
#include "JsonConstrain.h"
#include "my_board.h"
#include "luma_master.h"
#include "common/luma-stiks.h"
#define STRIP1_PIN RGBLED1_Pin
#define STRIP2_PIN RGBLED2_Pin
#define POWERUP_TIME 3000
static const char* tag = "led_strip";
ANIMATION_PROPS animProps;
LEDSTRIP *strip1;
LEDSTRIP *strip2;
TaskHandle_t Strip1_Task_Handle;
TaskHandle_t Strip2_Task_Handle;
TaskHandle_t FrontLight_Task_Handle;
QueueHandle_t eventQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) );
QueueHandle_t whiteQueue = xQueueCreate( 4, sizeof( ANIMATION_EVENT ) );
ANIMATION_EVENT lastNormalEventMsg;
ANIMATION_EVENT eventMsg;
LUMA_PACKET TempLumaPacket;
int CurrentRunningEventIndex = -1;
void Init_LED_Devices(BOARD_PINS boardPins)
{
ESP_LOGD(tag, "Strips Initialization entered...");
File file = LittleFS.open("/cfg/led-devices.json");
if(!file){
ESP_LOGE(tag,"Error opening led-devices.json!");
file.close();
}else{
JsonDocument doc;
DeserializationError error = deserializeJson(doc, file);
file.close();
if(error){ ESP_LOGE(tag, "led-devices.json deserialize error!.."); return;}
// ********** STRIP1 ********************************
JsonObject stripJson = doc["strip1"];
if(jsonConstrainBool(tag, stripJson, "en", false)){
int port = jsonConstrain<int>(tag, stripJson, "i2s-ch", 0, 1, 0);
int size = jsonConstrain<int>(tag, stripJson, "size", 1, 200, 10);
//int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED1_Pin);
LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() );
int shift = jsonConstrain<int>(tag, stripJson, "shift", -size, size, 0);
int offset = jsonConstrain<int>(tag, stripJson, "offset", -size, size, 0);
int core = jsonConstrain<int>(tag, stripJson, "core", 0, 1, 0);
int powerDiv = jsonConstrain<int>(tag, stripJson, "power-div", 0, 3, 0);
strip1 = new LEDSTRIP(port, size, boardPins.rgb1, order, shift, offset);
strip1->setPowerDiv(powerDiv);
ESP_LOGI(tag,"Strip1 initialized.");
ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset);
ESP_LOGD(tag," Strip1 Task Creating...Core: %d", core);
xTaskCreatePinnedToCore(Strip1_Control_Task, "LED_Strip1_Task", 1024*10, NULL, 1, &Strip1_Task_Handle, core);
}
// ********** STRIP2 ********************************
stripJson.clear();
stripJson = doc["strip2"];
if(jsonConstrainBool(tag, stripJson, "en", false)){
int port = jsonConstrain<int>(tag, stripJson, "i2s-ch", 0, 1, 0);
int size = jsonConstrain<int>(tag, stripJson, "size", 1, 200, 10);
//int pin = jsonConstrainInt(stripJson, "pin", 0, 48, RGBLED2_Pin);
LED_ORDER order= getRGBOrder( jsonConstrainString(tag, stripJson, "rgb-order", "grb").c_str() );
int shift = jsonConstrain<int>(tag, stripJson, "shift", -size, size, 0);
int offset = jsonConstrain<int>(tag, stripJson, "offset", -size, size, 0);
int core2 = jsonConstrain<int>(tag, stripJson, "core", 0, 1, 0);
int powerDiv = jsonConstrain<int>(tag, stripJson, "power-div", 0, 3, 0);
strip2 = new LEDSTRIP(port, size, boardPins.rgb2, order, shift, offset);
strip2->setPowerDiv(powerDiv);
ESP_LOGI(tag,"Strip2 initialized.");
ESP_LOGD(tag," Size: %d, port: %d, shift: %d, offset: %d", size, port, shift, offset);
ESP_LOGD(tag,"Strip2 Task Creating...Core: %d", core2);
xTaskCreatePinnedToCore(Strip2_Control_Task, "LED_Strip2_Task", 1024*8, NULL, 1, &Strip2_Task_Handle, core2);
}else{
ESP_LOGE(tag,"Strip2 disabled");
}
JsonObject flightJson = doc["front-light"];
animProps.frontLight.enabled = jsonConstrainBool(tag, flightJson, "en", false);
if(animProps.frontLight.enabled){
animProps.frontLight.relayIndex = jsonConstrain<int>(tag, flightJson, "relay", 0, 3, 0);
animProps.frontLight.core = jsonConstrain<int>(tag, flightJson, "core", 0, 1, 1);
animProps.frontLight.style = (FRONT_LIGHT_STYLE)(flightJson, "style", 0, 1, 0);
ESP_LOGI(tag, "Front Light initialized..");
Init_FrontLight();
}
JsonObject rlightJson = doc["rear-light"];
animProps.rearLight.enabled = jsonConstrainBool(tag, rlightJson, "en", false);
if(animProps.rearLight.enabled){
animProps.rearLight.relayIndex = jsonConstrain<int>(tag, rlightJson, "relay", 0, 3, 1);
animProps.rearLight.buttonIndex = jsonConstrain<int>(tag, rlightJson, "button", 0, 2, 2);
animProps.rearLight.rampTime = jsonConstrain<int>(tag, rlightJson, "ramp", 1000, 20000, 3000);
animProps.rearLight.rampSteps = jsonConstrain<int>(tag, rlightJson, "steps", 2, 20, 8);
animProps.rearLight.min = jsonConstrain<float>(tag, rlightJson, "min", 0.0f, 50.0f, 0.0f);
animProps.rearLight.max = jsonConstrain<float>(tag, rlightJson, "max", animProps.rearLight.min, 100.0f, 100.0f);
ESP_LOGD(tag, "relay: %d, btn: %d, ramp: %d, steps: %d, min: %.2f, max: %.2f", animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps, animProps.rearLight.min, animProps.rearLight.max);
Initialize_Rear_Control(animProps.rearLight.relayIndex, animProps.rearLight.buttonIndex, animProps.rearLight.rampTime, animProps.rearLight.rampSteps , animProps.rearLight.min, animProps.rearLight.max);
ESP_LOGI(tag, "Rear Light initialized..");
}
}
}
void Init_FrontLight(void)
{
xTaskCreatePinnedToCore(FrontLight_Task, "FrontLight_Task", 5000, NULL, 1, &FrontLight_Task_Handle, animProps.frontLight.core);
}
void Init_RearLight(void)
{
//if(jsonOb.isNull()){ Serial.println(" rear_light json is Null.."); return; }
}
void Test_Animations(void){
ANIMATION_EVENT event;
strip1->fill({0,0,0}, 0, strip1->effSize);
strip1->show(true);
vTaskDelay(100);
event.hue = RGBtoHUE(col_blue);
event.hueRange = 180;
event.param1 = 11;
event.speed = 70;
LEDStrip_FireInit(*strip1);
while(1){
event.hue = 0;
event.hueRange = 359;
event.param1 = 22;
event.speed = 70;
//Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2);
event.param1 = 30;
//Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2);
event.param1 = 40;
//Animation_Sectors(*strip1, event, true, DT_BOTH, true, 6, 2);
event.param1 = 50;
Animation_Sectors(*strip1, event, DT_BOTH, 2);
event.param1 = 50;
Animation_Dashes(*strip1, event, DT_BOTH, 3);
event.param1 = 50;
Animation_Dashes(*strip1, event, DT_BOTH, 3);
/*
Animation_Fire(*strip1, event, RED_FIRE, 5000);
Animation_Fire(*strip1, event, GREEN_FIRE, 5000);
Animation_Fire(*strip1, event, BLUE_FIRE, 5000);
event.col1 = col_blue;
event.col2 = {255,136,0};
event.density = 11;
event.speed = 100;
Animation_Snakes(*strip1, event, false, DT_FWD, 2, 1);
event.col1 = HUEtoRGB(359);
event.col2 = HUEtoRGB(0);
event.density = 22;
event.speed = 70;
Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4);
event.col1 = HUEtoRGB(359);
event.col2 = HUEtoRGB(0);
event.density = 56;
event.speed = 50;
Animation_Snakes(*strip1, event, true, DT_BOTH, 12, 4);
*/
}
}
void Strip1_Control_Task(void *parameters)
{
vTaskDelay(1000); // small start delay
ESP_LOGD(tag, "Strip1 Task Entered....");
//Test_Animations();
LEDStrip_FireInit(*strip1); // must be initialized before calling fire animation
// Set default event 1 animation base on profile values
ANIMATION_EVENT ev; // empty event
ev.animIndex = 0;
ev.countDown = 6000;
ev.param2 = 75;
ev.type = EV_NON_INJECT;
PostNewEvent(ev); // Start Animation (White Fill)
animProps.event[1].type = EV_NORMAL;
PostNewEvent(animProps.event[1]); // default attraction animation
while(true){
xQueueReceive( eventQueue, &eventMsg, portMAX_DELAY );
ESP_LOGD(tag, "Queue waiting: %d", uxQueueMessagesWaiting(eventQueue));
if(eventMsg.type == EV_NORMAL ) { // EV_NORMAL
ESP_LOGD(tag, "Running event: type: EV_NORMAL, event: %d, anim: %d", eventMsg.selfIndex, eventMsg.animIndex);
lastNormalEventMsg = eventMsg; //save last Normal EventMsg
if( eventMsg.selfIndex >= 0 ) {
CurrentRunningEventIndex = eventMsg.selfIndex;
}
if(Luma_PeerCount() > 0){
TempLumaPacket.type = LPT_ANIM;
memcpy(&TempLumaPacket.data, &eventMsg, sizeof(ANIMATION_EVENT));
Luma_Master_Broadcast(TempLumaPacket);
}
if( eventMsg.selfIndex == 0 ){
RunWhitefillAnimation( eventMsg );
}else{
RunAnimation( eventMsg );
}
}
else if( eventMsg.type == EV_INJECT ){ // insert and return to previous animation
ESP_LOGD(tag, "EV_INJECT");
xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event
RunPopUpAnimations( eventMsg );
}
else if( eventMsg.type == EV_NON_INJECT ){
ESP_LOGD(tag, "EV_NON_INJECT");
RunPopUpAnimations( eventMsg );
}
else if( eventMsg.type == EV_TEST ){
ESP_LOGD(tag, "EV_TEST");
animStatus.EventTestCountdown = EVENT_TESTMODE_TIMEOUT; // start timeout countdown
// Should not re-post last event from here in case repeated tests are started.
// that would cause a backlog of the same previous event.
if( eventMsg.selfIndex == 0 ){
RunWhitefillAnimation( eventMsg);
}else{
RunAnimation( eventMsg );
}
}
}
}
void PostLastNormalEvent(){
xQueueSend( eventQueue, &lastNormalEventMsg, 100 ); // requeue previous normal event
ESP_LOGD(tag, "RePosted Last Normal Event Index: %d", lastNormalEventMsg.selfIndex);
}
// Used to cycle to the next event primarity when usinging buttons
int IncrementEventIndex(){
int x = (CurrentRunningEventIndex + 1) % animStatus.EventsCount;
ESP_LOGD(tag, "Increment Event Index from %d to %d", CurrentRunningEventIndex, x);
animProps.event[x].type = EV_NORMAL;
PostNewEvent(animProps.event[x]);
return animStatus.EventsIndex;
}
//EVENT_MSG newEvent;
void PostNewEvent( ANIMATION_EVENT &animEvent) {
//newEvent.type = type;
//newEvent.event = animEvent;
xQueueSend(eventQueue, &animEvent, 100); // queue new event
// if(type == EV_NORMAL){
// If whitefill index ( could be restarted )
// if(animEvent.animIndex == ANIM_WHITEFILL_INDEX){
//if( countDown == 0 ) { animStatus.countDown = 1000; } // TODO Countdown calc maybe wrong...double check
//if( animStatus.busy ) { animStatus.repeat = true; }
// if( FrontLight_Task_Handle ) { xTaskNotifyGive( FrontLight_Task_Handle ); }
// }
// }
if( Strip1_Task_Handle ){ xTaskNotifyGive( Strip1_Task_Handle ); }
}
void Strip2_Control_Task(void *parameters)
{
vTaskDelay(100);
ESP_LOGD(tag, "Strip2 Task Entered....\n");
for(;;){
vTaskDelay(10000);
}
}
void FrontLight_Task(void *parameters){
animStatus.busy = false;
ANIMATION_EVENT whiteMsg;
for(;;){
xQueueReceive( whiteQueue, &whiteMsg, portMAX_DELAY );
int msFillTime = 0;
if(whiteMsg.countDown > 0){ // use countdown if available
msFillTime = whiteMsg.countDown;
}else{
msFillTime = map(whiteMsg.param1, 0, 100, 0, 10000);
if(msFillTime < 1000){ msFillTime = 1000; }
}
float delayFactor = (float)map(whiteMsg.param2, 0, 100, 0, 100) / 100.0;
Const_White_Fill(whiteStatus, animProps.frontLight, delayFactor, msFillTime, 50);
}
}
void load_animation_profile(void){
File file = LittleFS.open("/cfg/anim-profile-common.json");
if(!file){
ESP_LOGE(tag, "Error opening anim-profile-common.json...");
file.close();
}else{
JsonDocument doc;
DeserializationError error = deserializeJson(doc, file);
file.close();
if(error){ ESP_LOGE(tag, "anim-profile-common.json deserialize error!.."); return;}
JsonObject frontJson = doc["countdown"];
animProps.frontLight.minDuty = jsonConstrain<float>(tag, frontJson, "min", 0.0f, 99.0f, 20.0f);
animProps.frontLight.maxDuty = jsonConstrain<float>(tag, frontJson, "max", 1.0f, 100.0f, 99.0f);
animProps.frontLight.holdTime = jsonConstrain<int>(tag, frontJson, "hold", 100, 5000, 1000);
animProps.frontLight.rampTime = jsonConstrain<int>(tag, frontJson, "ramp", 100, 5000, 500);
animProps.profileIndex = jsonConstrain<int>(tag, doc.as<JsonObject>(), "profile-index", 0, 7, 0);
ESP_LOGI(tag, "anim-profile-common.json settings loaded.");
}
}
void load_animation_profileByIndex(int index){
//for(int index = 1; index <= 8; index++){
String strFileName = "/cfg/anim-profile" + String(index + 1) + ".json";
File file = LittleFS.open(strFileName);
if(!file){
ESP_LOGE(tag, "%s", "Error opening " + strFileName);
file.close();
}else{
JsonDocument doc;
DeserializationError error = deserializeJson(doc, file);
file.close();
if(error){ ESP_LOGE(tag, "%s deserialize error!..", strFileName.c_str()); return;}
JsonObject profJson = doc.as<JsonObject>();
animProps.profileName = jsonConstrainString(tag, profJson, "name", "").c_str();
ESP_LOGD(tag, "Profile Index: %d, Name: %s", index, animProps.profileName);
//
JsonArray eventsJson = profJson["events"];
for(int i = 0; i < animStatus.EventsCount; i++){
animProps.event[i].selfIndex = i; // Self Index
if(i == 0){
animProps.event[i].animIndex = jsonConstrain<int>(tag, eventsJson[i], "anim", 0, animProps.whitefillsCount-1, 0);
}else{
animProps.event[i].animIndex = jsonConstrain<int>(tag, eventsJson[i], "anim", 0, animProps.animationsCount-1, 0);
}
animProps.event[i].hue = jsonConstrain<int>(tag, eventsJson[i], "hue", -2, 360, 0);
animProps.event[i].hueRange = jsonConstrain<int>(tag, eventsJson[i], "hue-range", 0, 360, 1);
animProps.event[i].speed = jsonConstrain<int>(tag, eventsJson[i], "speed", 0, 100, 50);
animProps.event[i].param1 = jsonConstrain<int>(tag, eventsJson[i], "param1", 0, 100, 50);
animProps.event[i].param2 = jsonConstrain<int>(tag, eventsJson[i], "param2", 0, 100, 50);
animProps.event[i].check1 = jsonConstrainBool(tag, eventsJson[i], "check1", false);
animProps.event[i].check2 = jsonConstrainBool(tag, eventsJson[i], "check2", false);
animProps.event[i].check3 = jsonConstrainBool(tag, eventsJson[i], "check3", false);
animProps.event[i].check4 = jsonConstrainBool(tag, eventsJson[i], "check4", false);
ESP_LOGD(tag, " self: %d, anim: %d, param1: %d, speed: %d", animProps.event[i].selfIndex, animProps.event[i].animIndex, animProps.event[i].param1, animProps.event[i].speed);
}
ESP_LOGI(tag, "%s settings loaded.", strFileName.c_str());
}
//}
}
void RunWhitefillAnimation(ANIMATION_EVENT &event){
//Start Constant Light Task if available
if( FrontLight_Task_Handle ){
xQueueSend(whiteQueue, &event, 100); // queue new event
xTaskNotifyGive( FrontLight_Task_Handle );
}
int ret;
switch(event.animIndex){
case 0: // Bottom/Up Fill
ESP_LOGD(tag, " Running WhiteFill: %d , bottom/up", event.animIndex);
ret = Animation_White_Fill_Mirrored(*strip1, event);
break;
case 1: // Snake Fill
ESP_LOGD(tag, " Running WhiteFill: %d , Snake fill", event.animIndex);
ret = Animation_White_Fill_Mirrored(*strip1, event);
break;
case 2: // Tick Fill
ESP_LOGD(tag, " Running WhiteFill: %d , Tick fill", event.animIndex);
ret = Animation_White_Fill_Mirrored(*strip1, event);
break;
case 3: // Smooth Brighten
ESP_LOGD(tag, " Running WhiteFill: %d , Smooth Brighten", event.animIndex);
ret = Animation_White_Fill_Mirrored(*strip1, event);
break;
case 4: // Cycle All
ESP_LOGD(tag, " Running WhiteFill: %d , Cycle All", event.animIndex);
ret = Animation_White_Fill_Mirrored(*strip1, event);
break;
default: // Random Select
ESP_LOGD(tag, " Running WhiteFill: default , bottom/up");
ret = Animation_White_Fill_Mirrored(*strip1, event);
break;
}
// if exited loop naturally... not from a notification so keep waiting
if(ret == EXIT_FINISHED || ret == EXIT_TIMEOUT){
ESP_LOGD(tag, "Whitefill normal exit... waiting");
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
}
}
void RunAnimation(ANIMATION_EVENT &event){
if(event.animIndex < 80){
switch(event.animIndex){ // AnimationEventIndex (0-X)
case 0: // Rainbow
ESP_LOGD(tag, " Running Anim: %d , Rainbow", event.animIndex);
Animation_Rainbow(*strip1, event, INFINITE_LOOP);
break;
case 1: // Hue Spectrum Mirrored
ESP_LOGD(tag, " Running Anim: %d , Hue Spectrum Mirrored", event.animIndex);
Animation_Hue_Spectrum_Mirrored(*strip1, event, INFINITE_LOOP);
break;
case 2: // Color Pulse Cycle
ESP_LOGD(tag, " Running Anim: %d , Color Pulse Cycle", event.animIndex);
Animation_Pulse_Color_Cycling(*strip1, event);
break;
case 3: // Comets Mixed Colors
ESP_LOGD(tag, " Running Anim: %d , Comets", event.animIndex);
Animation_Comets(*strip1, event, DT_BOTH, INFINITE_LOOP);
break;
case 4: // Dashes Single and Mixed Colors
ESP_LOGD(tag, " Running Anim: %d , Dashes", event.animIndex);
Animation_Dashes(*strip1, event, DT_BOTH, INFINITE_LOOP);
break;
case 5: // Snakes Single and Mixed Colors
ESP_LOGD(tag, " Running Anim: %d , Snakes", event.animIndex);
Animation_Snakes(*strip1, event, DT_BOTH, INFINITE_LOOP);
break;
case 6: // Sectors Mixed Colors (No Sigle Colors because pointless)
ESP_LOGD(tag, " Running Anim: %d , Sectors", event.animIndex);
Animation_Sectors(*strip1, event, DT_BOTH, INFINITE_LOOP);
break;
case 7: // Fire (Red)
ESP_LOGD(tag, " Running Anim: %d , Fire", event.animIndex);
Animation_Fire(*strip1, event);
break;
case 8: //
break;
case 9: // Color Strobing
break;
case 10: // Random Color Twinkle
break;
case 11: // Stacking
break;
case 12: // Rain
break;
case 13: // Stacking
break;
case 14: // RED/WHITE/BLU Flag (density selects color pallet) (sector/comets,dashes, snakes)
break;
case 15:
break;
default:
break;
}
}else{ // Non Looping Functions
switch(event.animIndex){ // AnimationEventIndex (0-X)
case 80: // clear strip
strip1->fill(col_black, 0, strip1->effSize);
strip1->show(true);
ESP_LOGD(tag, "Animation Strip1 Clear");
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
break;
case 81: // set pixel
strip1->setPixel(event.param1, GetColorFromHue(event.hue));
strip1->show(true);
ESP_LOGD(tag, "Animation Set Pixel: %d", event.param1);
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
break;
case 82: //
break;
default:
break;
}
}
}
// These animations should be quick and not infinite
void RunPopUpAnimations(ANIMATION_EVENT &event){
switch(event.animIndex){ // AnimationEventIndex (0-X)
case 0: // Start/Boot/Power Up Sequence
ANIMATION_EVENT ev;
ev.animIndex = 0;
ev.hue = -1; //{200, 200, 200}; //col_white;
ev.hueRange = 1; //col_black;
ev.countDown = 6000; // 6 seconds
ev.param2 = 75;
if( FrontLight_Task_Handle ){
xQueueSend(whiteQueue, &ev, 100); // queue new event
xTaskNotifyGive( FrontLight_Task_Handle );
}
Animation_White_Fill_Mirrored(*strip1, ev);
break;
case 1: // BLE Connected
break;
case 2: // BLE Disconnected
break;
case 3: // WiFi Connected
break;
case 4: // WiFi Disconnected
break;
case 5: // Luma Stick Connected
break;
default:
break;
}
}
// json color to rgbPixel conversion
// Unused now
rgbpixel_t hexToRGB(const char* hexColor) {
long hexValue = strtol(hexColor + 1, nullptr, 16); // Skip the '#' character
rgbpixel_t pix;
pix.red = (hexValue >> 16) & 0xFF;
pix.grn = (hexValue >> 8) & 0xFF;
pix.blu = hexValue & 0xFF;
return pix;
}

View File

@ -1,128 +0,0 @@
#ifndef _LED_STRIP_H
#define _LED_STRIP_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "common/LEDStrip.h"
#include <ArduinoJson.h>
#include "my_board.h"
extern LEDSTRIP *strip1;
extern LEDSTRIP *strip2;
extern TaskHandle_t Strip1_Task_Handle;
extern TaskHandle_t Strip2_Task_Handle;
extern TaskHandle_t FrontLight_Task_Handle;
#define ANIM_WHITEFILL_INDEX 0
#define ANIM_DEFAULT_INDEX 1
#define ANIM_EVENT_SIZE 12
#define PROFILES_SIZE 8
#define MIN_LED_UPDATE_INTERVAL 13
enum OtherANIMS { OA_SET_PIXEL=100, OA_CLEAR, };
enum FRONT_LIGHT_STYLE { FRONT_LIKE_LUMIA, FRONT_LIKE_ROAMER };
enum EVENT_TYPE { EV_NORMAL, EV_INJECT, EV_NON_INJECT, EV_TEST,};
// Single web page event (upto 8)
typedef struct{
EVENT_TYPE type;
int selfIndex;
int animIndex;
int hue;
int hueRange;
int speed;
int param1;
int param2;
bool check1;
bool check2;
bool check3;
bool check4;
int countDown;
}ANIMATION_EVENT;
typedef struct{
bool enabled;
uint8_t relayIndex;
uint8_t core;
int holdTime;
int rampTime;
float minDuty;
float maxDuty;
FRONT_LIGHT_STYLE style;
//float delayFactor;
}FRONT_LIGHT;
typedef struct{
bool enabled;
uint8_t relayIndex;
uint8_t buttonIndex;
uint8_t core;
int rampTime;
uint8_t rampSteps;
float min;
float max;
}REAR_LIGHT;
typedef struct {
EVENT_TYPE type;
ANIMATION_EVENT event;
}EVENT_MSG;
// Properties from webpage
typedef struct {
int profileIndex;
const char* profileName;
int whitefillsCount;
int animationsCount;
FRONT_LIGHT frontLight;
REAR_LIGHT rearLight;
ANIMATION_EVENT event[ANIM_EVENT_SIZE];
}ANIMATION_PROPS;
extern ANIMATION_PROPS animProps;
enum POPUP_ANIM { POP_STATUP, POP_BLE_CONN, POP_BLE_DISC, POP_WIFI_CONN, POP_WIFI_DISC, POP_STICK_CONN, POP_STICK_DISC };
void RunWhitefillAnimation(ANIMATION_EVENT &event);
void RunAnimation(ANIMATION_EVENT &event);
void RunPopUpAnimations(ANIMATION_EVENT &event);
//void SetEventIndex(int index, int countdown=0, bool test=false);
//void PostNewEvent( int countDown, EVENT_TYPE type, ANIMATION_EVENT &animEvent);
void PostNewEvent( ANIMATION_EVENT &animEvent);
int IncrementEventIndex(void);
void PostLastNormalEvent(void);
void load_animation_profile(void); // cfg/anim-profiles.json
void load_animation_profileByIndex(int); // cfg/anim-profileX.json
void Init_LED_Devices(BOARD_PINS); // cfg/led-devices.json
rgbpixel_t hexToRGB(const char* hexColor);
//void Init_LEDStrip2(void);
void Init_FrontLight(void);
//void Init_RearLight(void);
void Strip1_Control_Task(void *parameters);
void Strip2_Control_Task(void *parameters);
void FrontLight_Task(void *parameters);
#endif

View File

@ -1,755 +0,0 @@
{{NAVBAR}}
<!DOCTYPE html>
<html>
<head>
<title>App Control Configuration</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="global-style.css" rel="stylesheet">
<style>
#table{
overflow: auto;
}
#first_td_th {
width:300px;
}
#v2_td_th {
width:180px;
}
#color-picker{
margin: 0;
padding: 0;
border: 0;
width: 35px;
height: 35px;
background-color: transparent;
}
#select-anim {
width:175px;
}
select{
width:150px;
}
#slide-density{
width: 70%;
}
#slide-speed{
accent-color: rgb(181, 53, 53);
width: 70%;
}
input::-webkit-slider-runnable-track {
width: 450px;
border: none;
border-radius: 15px;
}
</style>
</head>
<body>
<h1 name="h1element">App Control Configuration</h1>
<fieldset>
<legend>Saved Animation Profiles</legend>
<table>
<tr>
<td id="first_td_th">
<label for="selSavedAnimProfiles">Profiles:</label>
<select name= "selSavedAnimProfiles" id="selSaveddAnimProfiles" style="width:150px" onchange="OnSavedAnimProfilesChanged(this)">
<option>(1)</option>
<option>(2)</option>
<option>(3)</option>
<option>(4)</option>
<option>(5)</option>
<option>(6)</option>
<option>(7)</option>
<option>(8)</option>
</select>
&emsp;&emsp;
<label for="profileName">Rename:</label>
<input type="text" name="inputProfileName" id="profileName" style="width:140px">
&emsp;&emsp;&emsp;
<button id="saveProfile" onclick="SaveProfilesToServer()">Save</button>
</td>
</tr>
<div ></div>
</table>
</fieldset>
<tr><td>
<div id="spacer-20"></div>
</td></tr>
<fieldset>
<legend id="legendLights">Countdown ( Constant Light )</legend>
<table>
<tr>
<td id="first_td_th">
<label id="constLightMin-label" for="constlightMin">Light Min:</label> <br>
<input type="range" name="constLightMin" id="constlightMin" min="0" max="99" value="25" step="1" onchange="updateLabel('Light Min: ', this)">
</td>
<td>
<label id="constLightMax-label" for="constlightMax">Light Max:</label> <br>
<input type="range" name="constLightMax" id="constlightMax" min="1" max="100" value="90" step="1" onchange="updateLabel('Light Max: ', this)">
</td>
<td>
<!-- <button>Try</button> -->
</td>
</tr>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
<tr>
<td id="first_td_th">
<label for="Holdtime">Hold time(ms):</label><br>
<input type="number" name="holdTime" id="holdtime" min="0" max="5000" value="500">
</td>
<td>
<label for="Ramptime">Ramp dn(ms):</label><br>
<input type="number" name="rampTime" id="Ramptime" min="0" max="5000" value="500">
</td>
<tr><td>
<div id="spacer-10"></div>
</td></tr>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset0">
<legend name="legendAnim0">Event0</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">White Fill Animation: </label> <br>
<select name="sel-anim0" id="select-anim0"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim0" id="color-picker" >&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim0" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density0-label" for="slide-density0">Density:</label> <br>
<input type="range" name="slide-density0" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed0-label" for="slide-speed0">Speed:</label> <br>
<input type="range" name="slide-speed0" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(0)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset1">
<legend name="legendAnim1">Event1</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim1" id="select-anim1"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim1" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim1" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density1-label" for="slide-density1">Density:</label> <br>
<input type="range" name="slide-density1" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed1-label" for="slide-speed1">Speed:</label> <br>
<input type="range" name="slide-speed1" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(1)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset2">
<legend name="legendAnim2">Event2</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim2" id="select-anim2"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim2" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim2" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density2-label" for="slide-density2">Density:</label> <br>
<input type="range" name="slide-density2" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed2-label" for="slide-speed2">Speed:</label> <br>
<input type="range" name="slide-speed2" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(2)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset3">
<legend name="legendAnim3">Event3</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim3" id="select-anim3"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim3" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim3" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density3-label" for="slide-density3">Density:</label> <br>
<input type="range" name="slide-density3" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed3-label" for="slide-speed3">Speed:</label> <br>
<input type="range" name="slide-speed3" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(3)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset4">
<legend name="legendAnim4">Event4</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim4" id="select-anim4"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim4" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim4" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density4-label" for="slide-density4">Density:</label> <br>
<input type="range" name="slide-density4" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed4-label" for="slide-speed4">Speed:</label> <br>
<input type="range" name="slide-speed4" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(4)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset5">
<legend name="legendAnim5">Event5</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim5" id="select-anim5"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim5" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim5" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density5-label" for="slide-density5">Density:</label> <br>
<input type="range" name="slide-density5" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed5-label" for="slide-speed5">Speed:</label> <br>
<input type="range" name="slide-speed5" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(5)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset6">
<legend name="legendAnim6">Event6</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim6" id="select-anim6"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim6" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim6" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density6-label" for="slide-density6">Density:</label> <br>
<input type="range" name="slide-density6" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed6-label" for="slide-speed6">Speed:</label> <br>
<input type="range" name="slide-speed6" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(6)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset7">
<legend name="legendAnim7">Event7</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim7" id="select-anim7"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim7" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim7" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density7-label" for="slide-density7">Density:</label> <br>
<input type="range" name="slide-density7" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed7-label" for="slide-speed7">Speed:</label> <br>
<input type="range" name="slide-speed7" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(7)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset8">
<legend name="legendAnim8">Event8</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim8" id="select-anim8"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim8" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim8" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density8-label" for="slide-density8">Density:</label> <br>
<input type="range" name="slide-density8" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed8-label" for="slide-speed8">Speed:</label> <br>
<input type="range" name="slide-speed8" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(8)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset9">
<legend name="legendAnim9">Event9</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim9" id="select-anim9"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim9" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim9" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density9-label" for="slide-density9">Density:</label> <br>
<input type="range" name="slide-density9" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed9-label" for="slide-speed9">Speed:</label> <br>
<input type="range" name="slide-speed9" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(9)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset10">
<legend name="legendAnim10">Event10</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim10" id="select-anim10"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim10" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim10" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density10-label" for="slide-density10">Density:</label> <br>
<input type="range" name="slide-density10" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed10-label" for="slide-speed10">Speed:</label> <br>
<input type="range" name="slide-speed10" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(10)">Try</button>
</td>
</tr>
</table>
</fieldset>
<div id="spacer-20"></div>
<fieldset name="event-fieldset11">
<legend name="legendAnim11">Event11</legend>
<table>
<tr>
<td id="v2_td_th">
<label for="sel-count-anim">Animation: </label> <br>
<select name="sel-anim11" id="select-anim11"></select>
</td>
<td>
<label for="color-picker">Col1:</label><br>
<input type="color" name="col1-color-anim11" id="color-picker">&emsp;
</td>
<td>
<label for="color-picker">Col2:</label><br>
<input type="color" name="col2-color-anim11" id="color-picker">&emsp;
</td>
<td>
<label id="slide-density11-label" for="slide-density11">Density:</label> <br>
<input type="range" name="slide-density11" id="slide-density" min="0" max="100" value="25" step="1" onchange="updateLabel('Density: ', this)">
</td>
<td>
<label id="slide-speed11-label" for="slide-speed11">Speed:</label> <br>
<input type="range" name="slide-speed11" id="slide-speed" min="0" max="100" value="25" step="1" onchange="updateLabel('Speed: ', this)">
</td>
<td>
<button onclick="postPlayAnim(11)">Try</button>
</td>
</tr>
</table>
</fieldset>
<script>
window.onload = function() { getProfilesAndEvents(); };
// Initialize Names
const EVENTCOUNT = 12;
const PROFILECOUNT = 8;
var animEvent = [EVENTCOUNT];
var profilesJson;
var eventsJson;
var animListJson;
const inputProfileName = document.getElementsByName('inputProfileName')[0];
const selSavedAnimProfiles = document.getElementsByName('selSavedAnimProfiles')[0];
const constLightMin = document.getElementsByName('constLightMin')[0];
const constLightMax = document.getElementsByName('constLightMax')[0];
const holdTime = document.getElementsByName('holdTime')[0];
const rampTime = document.getElementsByName('rampTime')[0];
var eventFieldset = [EVENTCOUNT];
var eventLegend = [EVENTCOUNT];
var lastAnimProfileIndex = 0;
var h1element = document.getElementsByName("h1element")[0];
// set event names by app type and if they are visible
for (let i = 0; i < EVENTCOUNT; i++) {
eventFieldset[i] = document.getElementsByName("event-fieldset" + i)[0];
eventLegend[i] = document.getElementsByName("legendAnim" + i)[0];
}
for (let i = 0; i < EVENTCOUNT; i++) {
animEvent[i] = {
anim: document.getElementsByName('sel-anim' + i)[0],
col1: document.getElementsByName('col1-color-anim' + i)[0],
col2: document.getElementsByName('col2-color-anim' + i)[0],
density: document.getElementsByName('slide-density' + i)[0],
speed: document.getElementsByName('slide-speed' + i)[0]
};
}
// update the form with animation data when profile list item selected
function setProfile(index){
var changeEvent = new Event("change");
// update profiles
inputProfileName.value = "";
constLightMin.value = profilesJson.countdown.min;
constLightMin.dispatchEvent(changeEvent);
constLightMax.value = profilesJson.countdown.max;
constLightMax.dispatchEvent(changeEvent);
holdTime.value = profilesJson.countdown.hold;
rampTime.value = profilesJson.countdown.ramp;
// Update Events
for (let i = 0; i < EVENTCOUNT; i++) {
animEvent[i].anim.selectedIndex = profilesJson.profiles[index].events[i].anim;
animEvent[i].col1.value = profilesJson.profiles[index].events[i].col1;
animEvent[i].col2.value = profilesJson.profiles[index].events[i].col2;
animEvent[i].density.value = profilesJson.profiles[index].events[i].density;
animEvent[i].density.dispatchEvent(changeEvent);
animEvent[i].speed.value = profilesJson.profiles[index].events[i].speed;
animEvent[i].speed.dispatchEvent(changeEvent);
}
}
// When new profile is selected
function OnSavedAnimProfilesChanged( event ){
console.log('Anim Profile Selected index:', event.selectedIndex);
console.log('Anim Profile Updated index:', lastAnimProfileIndex);
updateProfilesJson(lastAnimProfileIndex); // save current setting in ram but on in server yet
setProfile(event.selectedIndex);
lastAnimProfileIndex = event.selectedIndex;
}
//Update the profilesJson obj with updated values
function updateProfilesJson(index){
//let index = selSavedAnimProfiles.selectedIndex;
if(inputProfileName.value != ""){
profilesJson.profiles[index].name = inputProfileName.value;
selSavedAnimProfiles.options[index].text = '(' + (index + 1) + ') ' + inputProfileName.value;
selSavedAnimProfiles.options[index].value = inputProfileName.value;
}
profilesJson.countdown.min = constLightMin.value;
profilesJson.countdown.max = constLightMax.value;
profilesJson.countdown.hold = holdTime.value;
profilesJson.countdown.ramp = rampTime.value;
// Update Events
for (let i = 0; i < EVENTCOUNT; i++) {
profilesJson.profiles[index].events[i].anim = animEvent[i].anim.selectedIndex;
profilesJson.profiles[index].events[i].col1 = animEvent[i].col1.value;
profilesJson.profiles[index].events[i].col2 = animEvent[i].col2.value;
profilesJson.profiles[index].events[i].density = animEvent[i].density.value;
profilesJson.profiles[index].events[i].speed = animEvent[i].speed.value;
}
}
// save profilesJson
function SaveProfilesToServer(){
// update profilesJson obj with current settings before posting
updateProfilesJson(selSavedAnimProfiles.selectedIndex);
profilesJson["profile-index"] = selSavedAnimProfiles.selectedIndex; // Set at active profile
const params = new URLSearchParams();
params.append('type', 'anim-profiles');
const url = '/post?' + params.toString();
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' } ,
body: JSON.stringify(profilesJson) // convert to string
})
.then(response => {
if (response.ok) { console.log('Request successful');}
else { throw new Error('Request failed');}
})
.catch(error => { console.error(error);});
}
// send anim event to test out
function postPlayAnim(eventIndex){
let tempAnimProps = {};
tempAnimProps.event = eventIndex;
tempAnimProps.anim = animEvent[eventIndex].anim.selectedIndex;
tempAnimProps.col1 = animEvent[eventIndex].col1.value
tempAnimProps.col2 = animEvent[eventIndex].col2.value
tempAnimProps.density = animEvent[eventIndex].density.value;
tempAnimProps.speed = animEvent[eventIndex].speed.value;
const params = new URLSearchParams();
params.append('type', 'play-anim');
const url = '/post?' + params.toString();
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain'} ,
body: JSON.stringify(tempAnimProps)
})
.then(response => {
if (!response.ok) { throw new Error('Request failed'); }
})
.catch(error => { console.error(error); });
}
// Get profiles
function getProfilesAndEvents(){
try{
fetch_json_file('anim-profiles').then(result =>{
profilesJson = result.data;
//console.log({profilesJson});
fetch_json_file('app-events').then(result =>{
eventsJson = result.data.apps[result.data.index]
//console.log({evetnsJson});
fetch_json_file('anim-list').then(result =>{
animListJson = result.data;
//console.log({animListJson});
FillWhitefilList(animListJson.whitefills);
FillAnimationsList(animListJson.animations);
FillProfilesList(profilesJson);
selSavedAnimProfiles.selectedIndex = profilesJson['profile-index'];
lastAnimProfileIndex = selSavedAnimProfiles.selectedIndex;
setProfile(selSavedAnimProfiles.selectedIndex);
NameAndHideEvents();
});
});
});
}catch (error){
console.error(error);
}
}
function fetch_json_file(fileName){
const params = new URLSearchParams();
params.append('type', fileName);
const url = '/get?' + params.toString();
return fetch(url, {
method: 'GET'
})
.then(response => {
if (response.ok) { return response.json(); }
else { throw new Error('fetching ' + fileName + ' failed'); }
})
.then(data => { return{data:data}; })
}
// Rename legends and or hide unused events property boxes
function NameAndHideEvents(){
h1element.textContent = eventsJson.name;
for(let i = 0; i < EVENTCOUNT; i++){
eventLegend[i].textContent = eventsJson.events[i];
if(eventsJson.events[i] === ''){
eventFieldset[i].style.display = 'none';
}
}
}
// Fill Profiles List
function FillProfilesList(profJson){
for(let i = 0; i < PROFILECOUNT; i++){
selSavedAnimProfiles.options[i].text = '(' + (i + 1) + ') ' + profJson.profiles[i].name;
selSavedAnimProfiles.options[i].value = profJson.profiles[i].name;
}
}
function FillWhitefilList(whiteJson){
for(let x = 0; x < whiteJson.length; x++){
var op = document.createElement("option");
op.text = whiteJson[x];
animEvent[0].anim.appendChild(op);
}
}
// Fill Animations drop down lists
function FillAnimationsList(animJson){
for(let x = 0; x < animJson.length; x++){
if(animJson[x] == ""){break;}// stop if blank
for(let i = 1; i < EVENTCOUNT; i++){
var op = document.createElement("option");
op.text = animJson[x];
op.value = animJson[x];
animEvent[i].anim.appendChild(op);
}
}
}
function updateLabel(labelText, slider) {
try{
let label = document.getElementById(slider.name + "-label");
label.innerHTML = labelText + slider.value;
}catch(e){
debugger;
console.log(e);
}
}
</script>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Log</title>
</head>
<body>
<h1>System Log</h1>
</body>
</html>

View File

@ -1,111 +0,0 @@
#include "luma_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <FS.h>
#include <LittleFS.h>
#include "JsonConstrain.h"
#include <esp_now.h>
#include <WiFi.h>
#include "global.h"
static const char* tag = "lumaM";
uint8_t broadcastAddress[] = {0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF};
uint8_t availableStiks = 0;
LUMA_NODE lumaStation[LUMA_NODE_MAX_COUNT];
bool LUMASTIKS_READY = false;
TaskHandle_t LumaMaster_Task_Handle;
bool SendRegistrationPacket = false;
LUMA_PACKET RegistrationPacket;
esp_now_peer_info_t TempNode;
QueueHandle_t lumaQueue = xQueueCreate( 4, sizeof( LUMA_PACKET ) );
void Init_Luma_Master(void){
if (esp_now_init() != ESP_OK) {
ESP_LOGD(tag, "Error initializing ESP-NOW\n");
return;
}
xTaskCreatePinnedToCore(LumaMaster_Task, "LumaMaster_Task", 1024*6, NULL, 1, &LumaMaster_Task_Handle, CONFIG_ARDUINO_RUNNING_CORE);
ESP_LOGV(tag, "Initialized LumaMaster task created...");
esp_now_register_recv_cb(Luma_Master_Data_Received);
esp_now_register_send_cb(Luma_Master_Data_Sent);
}
void LumaMaster_Task(void *parameters){
while(1){
vTaskSuspend(NULL);
if(SendRegistrationPacket){
Luma_Send_Packet(TempNode.peer_addr, RegistrationPacket);
}
}
LUMA_PACKET lumaPacket;
while(1){
xQueueReceive( lumaQueue, &lumaPacket, portMAX_DELAY );
switch(lumaPacket.type){
case LPT_RAW:
break;
case LPT_REG:
Luma_Send_Packet(TempNode.peer_addr, lumaPacket);
break;
case LPT_ANIM:
Luma_Master_Broadcast(lumaPacket);
break;
default:
break;
}
}
}
void Luma_Master_Data_Sent(const uint8_t *mac_addr, esp_now_send_status_t status){
}
void Luma_Master_Data_Received(const uint8_t *mac, const uint8_t *data, int len){
LUMA_PACKET *packet = (LUMA_PACKET*)data;
printf("** Data Received **\n\n");
printf("Received from MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
printf("Length: %d byte(s)\n", len);
printf("Data: %d\n\n", data[0]);
switch(packet->type){
case LPT_RAW:
break;
case LPT_REG:
LUMA_REGISTER_PACKET *reg = (LUMA_REGISTER_PACKET*)&packet->data;
ESP_LOGD(tag, "Node SSID: %s", reg->ssid);
// Register
memcpy(TempNode.peer_addr, mac, 6);
TempNode.channel = WiFi.channel();
Luma_Add_Peer(TempNode);
// Notify Peer
RegistrationPacket.type = LPT_REG;
xQueueSend( lumaQueue, &RegistrationPacket, 100 );
break;
//default:
// break;
}
}
void Luma_Master_Broadcast(LUMA_PACKET packet){
}

View File

@ -1,24 +0,0 @@
#ifndef _LUMA_MASTER_H
#define _LUMA_MASTER_H
#include <Arduino.h>
#include <esp_now.h>
#include "common/luma-stiks.h"
extern bool LUMASTIKS_READY;
#define LUMA_NODE_MAX_COUNT 16
//extern LUMA_NODE lumaStation[];
void LumaMaster_Task(void *parameters);
void Init_Luma_Master(void);
void Luma_Master_Data_Received(const uint8_t *mac, const uint8_t *data, int len);
void Luma_Master_Data_Sent(const uint8_t *mac_addr, esp_now_send_status_t status);
void Luma_Master_Broadcast(LUMA_PACKET packet);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,66 +0,0 @@
#ifndef _MY_WIFI_H
#define _MY_WIFI_H
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
extern int RebootSystem;
void Wifi_Start_WebServer(void);
void Init_Wifi_Task(void);
void Wifi_Start_MDNS(void);
void Wifi_Task(void *parameters);
String getSoftAPMacAddress(void);
//void onWiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void onWiFiEvent(WiFiEvent_t event);
void Wifi_Request_Task(void *parameters);
void Setup_WebServer_Handlers(AsyncWebServer* serv);
void Wifi_Read_Credentials(const JsonObject &credJson);
void Wifi_Save_Credentials(String newSSID, String newPass);
bool isInternetConnected(void);
void Wifi_Client_Loop(void);
// Handlers
void handlePOST_RegStick(AsyncWebServerRequest *request);
void handleGET_DSLRBooth(AsyncWebServerRequest *request);
void handlePOST_Upload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void handlePOST_Update(AsyncWebServerRequest *request);
void updateCallback(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void updateFirmwareProgress(size_t progress, size_t total);
void handleGET_SendFile(AsyncWebServerRequest *request);
void onFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void handleGET_Get(AsyncWebServerRequest *request);
void handlePOST_Post(AsyncWebServerRequest *request);
void postFileUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final);
void postBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&));
String htmlProcessor(const String& var);
String HomeHtmlProcessor(const String& var);
//const char* htmlProcessor(const char* var);
String listDir(fs::FS &fs, const char *dirname, bool isSubdirectory);
const char* getFileExtension(const char* filename);
const char* getFileType(const char* ext);
const char* convertFileSize(const size_t bytes);
char* readFile(fs::FS &fs, const char* path);
void writeFile(fs::FS &fs, const char * path, const char * message);
String varReplace(const String& input, String (*callback)(const String&));
//const char* varReplace(const char* input, const char* (*callback)(const char*));
void CreateSysSummmaryPacket(JsonDocument &doc);
#endif

View File

@ -1,78 +0,0 @@
#ifndef NEO_COLORS_H
#define NEO_COLORS_H
#include <NeoPixelBus.h>
#define USE_CORRECTED_COLORS 0
const RgbColor col_black(0, 0, 0);
const RgbColor col_white(255, 255, 255);
const RgbColor col_red(255, 0, 0);
const RgbColor col_green(0, 255, 0);
const RgbColor col_blue(0, 0, 255);
#if USE_CORRECTED_COLORS == 0
const RgbColor col_orange(255, 165, 0);
const RgbColor col_yellow(255, 255, 0);
const RgbColor col_cyan(0, 255, 255);
const RgbColor col_magenta(255, 0, 255);
const RgbColor col_purple(128, 0, 128);
const RgbColor col_pink(255, 192, 203);
const RgbColor col_teal(0, 128, 128);
const RgbColor col_lime(0, 255, 0);
const RgbColor col_indigo(75, 0, 130);
const RgbColor col_maroon(128, 0, 0);
const RgbColor col_navy(0, 0, 128);
const RgbColor col_olive(128, 128, 0);
const RgbColor col_beige(245, 245, 220);
const RgbColor col_brown(165, 42, 42);
const RgbColor col_coral(255, 127, 80);
const RgbColor col_gold(255, 215, 0);
const RgbColor col_gray(128, 128, 128);
const RgbColor col_ivory(255, 255, 240);
const RgbColor col_khaki(240, 230, 140);
const RgbColor col_lavender(230, 230, 250);
const RgbColor col_peach(255, 218, 185);
const RgbColor col_periwinkle(204, 204, 255);
const RgbColor col_salmon(250, 128, 114);
const RgbColor col_sienna(160, 82, 45);
const RgbColor col_silver(192, 192, 192);
const RgbColor col_tan(210, 180, 140);
const RgbColor col_turquoise(64, 224, 208);
const RgbColor col_violet(238, 130, 238);
#else
const RgbColor col_orange(255, 128, 0);
const RgbColor col_yellow(255, 255, 0);
const RgbColor col_cyan(0, 255, 255);
const RgbColor col_magenta(255, 0, 255);
const RgbColor col_purple(170, 0, 255);
const RgbColor col_pink(255, 170, 255);
const RgbColor col_teal(0, 128, 128);
const RgbColor col_lime(128, 255, 0);
const RgbColor col_indigo(85, 0, 255);
const RgbColor col_maroon(128, 0, 0);
const RgbColor col_navy(0, 0, 128);
const RgbColor col_olive(128, 128, 0);
const RgbColor col_beige(255, 230, 204);
const RgbColor col_brown(153, 51, 0);
const RgbColor col_coral(255, 102, 102);
const RgbColor col_gold(204, 153, 0);
const RgbColor col_gray(128, 128, 128);
const RgbColor col_ivory(255, 255, 204);
const RgbColor col_khaki(204, 204, 0);
const RgbColor col_lavender(204, 153, 255);
const RgbColor col_peach(255, 204, 153);
const RgbColor col_periwinkle(153, 153, 255);
const RgbColor col_salmon(255, 153, 102);
const RgbColor col_sienna(153, 76, 0);
const RgbColor col_silver(204, 204, 204);
const RgbColor col_tan(204, 153, 102);
const RgbColor col_turquoise(0, 204, 204);
const RgbColor col_violet(204, 0, 255);
#endif
#endif

View File

@ -1,96 +0,0 @@
#include "otaupdate.h"
#include "LittleFS.h"
#include <HTTPClient.h>
#include <Update.h>
#include "githubCert.h"
#include <ArduinoJson.h>
String binFile = "manifest.json";
String rootUrl = "https://raw.githubusercontent.com/ataindustries/atacontrolboardV1/main/";
String github_token = "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";
const float FirmwareVer = 1.0;
JsonDocument doc;
int otaupdate_check(void)
{/*
String payload;
int httpCode;
WiFiClientSecure * client = new WiFiClientSecure;
HTTPClient https;
String manifest_url = rootUrl + binFile + "?token=" + github_token;
https.begin(manifest_url);
httpCode = https.GET();
if (httpCode == HTTP_CODE_OK) // if version received
{
String payload = https.getString();
JsonDocument doc;
deserializeJson(doc, payload);
float firmver = doc["firmver"].as<float>();
const char* firmfile = doc["firmfile"];
const char* fsfile = doc["fsfile"];
Serial.printf("From manifest: %.1f, %s, $s\n\r", firmver, firmfile, fsfile);
if(firmver > FirmwareVer){
Serial.println("New firmware available!");
return 1;
}else{
Serial.println("firmware is upto date!");
return 0;
}
}
*/
return 0;
}
void otaupdate_perform(void)
{
// setup events
ota_events();
// Pause unnecessary tasks
//check for update
}
void ota_events(void)
{
/*
fota->setUpdateEndCb( [](int partition)
{
//Serial.printf("Update could not finish with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" );
});
fota->setProgressCb( [](size_t progress, size_t size)
{
//if( progress == size || progress == 0 ) Serial.println();
//Serial.print(".");
});
fota->setUpdateCheckFailCb( [](int partition, int error_code)
{
//Serial.printf("Update could validate %s partition (error %d)\n", partition==U_SPIFFS ? "spiffs" : "firmware", error_code );
// error codes:
// -1 : partition not found
// -2 : validation (signature check) failed
});
fota->setUpdateFinishedCb( [](int partition, bool restart_after)
{
//Serial.printf("Update could not begin with %s partition\n", partition==U_SPIFFS ? "spiffs" : "firmware" );
if( restart_after ) {
ESP.restart();
}
});
*/
}

View File

@ -1,14 +0,0 @@
#ifndef _OTAUPDATE_H
#define _OTAUPDATE_H
#include <Arduino.h>
int otaupdate_check(float myVer);
void otaupdate_perform(String filePath);
void ota_events(void);
#endif

View File

@ -1,44 +0,0 @@
#include "rf_transceiver.h"
static const char* tag = "trx433";
TaskHandle_t RF_Task_Handle;
const int txChannel = RMT_CHANNEL_0;
const int rxChannel = RMT_CHANNEL_1;
void Init_RF_Receiver(void)
{
/*
// Configure RMT receiver
rmt_config_t rxConfig;
rxConfig.channel = (rmt_channel_t)rxChannel;
rxConfig.gpio_num = static_cast<gpio_num_t>(RMT_RX_PIN);
// Set other reception settings as needed
rmt_config(&rxConfig);
rmt_driver_install(rxConfig.channel, 1000, 0);
xTaskCreatePinnedToCore(RF_Control_Task, "RF_Task", 8000, NULL, 1, &RF_Task_Handle, 0);
*/
}
void Init_RF_Transmitter(void)
{
/*
rmt_config_t txConfig;
txConfig.channel = (rmt_channel_t)txChannel;
txConfig.gpio_num = static_cast<gpio_num_t>(RMT_TX_PIN);
// Set other transmission settings as needed
rmt_config(&txConfig);
rmt_driver_install(txConfig.channel, 0, 0);
*/
}
void RF_Control_Task(void *parameters)
{
for(;;){
vTaskDelay(100);
}
}

View File

@ -1,23 +0,0 @@
#ifndef _RF_TRANSCEIVER_H
#define _RF_TRANSCEIVER_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <Arduino.h>
#include <driver/rmt.h>
#include "my_board.h"
#define RMT_RX_PIN RX433_Pin
#define RMT_TX_PIN TX433_Pin
#define RTM_RX_CHANNEL 0
#define RTM_TX_CHANNEL 1
void Init_RF_Receiver(void);
void Init_RF_Transmitter(void);
void RF_Control_Task(void *parameters);
#endif

View File

@ -1,50 +0,0 @@
.blue {
color: blue;
}
h1{
color: yellow;
}
body{
color: red;
}
.secTitleFont{
color: white;
}
.secTitle{
min-width: none;
height: 25px;
width: auto;
padding: 2px;
margin: 2px;
display: flex;
justify-content: center;
align-items: center;
/*border: 3px solid black;*/
color: rgb(56, 132, 255);
background-color: rgb(61, 118, 153);
font-family: Tahoma, Arial, sans-serif;
font-size: smaller;
font-weight: bold;
flex-direction: ;
}
.secCntrls{
height: 100px;
width: auto;
margin: 5px;
border: 1px solid rgb(255, 255, 255);
color: rgba(255, 255, 255, 0);
font-family: Tahoma, Arial, sans-serif;
/*display: flex;
justify-content: center;
align-items: center;
position: relative;*/
}
.dropList{
margin-right: 40px;
}

View File

@ -1,190 +0,0 @@
{
"files": [
{
"remote": "data/ata-boothifier-upgrade.html",
"local": "/ata-boothifier-upgrade.html",
"md5": "92074ec24fd467545272eb9e837d644c",
"size": 16980
},
{
"remote": "data/ata-boothifier-upgradeV2.html",
"local": "/ata-boothifier-upgradeV2.html",
"md5": "484648993370e4b6aff13124bd1c55c1",
"size": 18178
},
{
"remote": "data/ata-boothifier-upgradeV3.html",
"local": "/ata-boothifier-upgradeV3.html",
"md5": "79426145db379ac15ccc742245a31ecb",
"size": 17233
},
{
"remote": "data/favicon.ico",
"local": "/favicon.ico",
"md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79",
"size": 1150
},
{
"remote": "data/flashstik-reg.html",
"local": "/flashstik-reg.html",
"md5": "394fdfe3cd3fb89a8ddb3d6edf2d2da4",
"size": 15651
},
{
"remote": "data/css/global-style.css",
"local": "/css/global-style.css",
"md5": "217a9cca8b4eae2d28fa8bc5a0f6db09",
"size": 1239
},
{
"remote": "data/css/nav.css",
"local": "/css/nav.css",
"md5": "e653a75433056f28e3f410f567b8622c",
"size": 1538
},
{
"remote": "data/images/atalogo.png",
"local": "/images/atalogo.png",
"md5": "5a18c88a4ea80c8d8d0ad52b2fdbbadc",
"size": 16690
},
{
"remote": "data/images/favicon-32x32.png",
"local": "/images/favicon-32x32.png",
"md5": "d80cf74ace3a8be487a5158bca48f9b6",
"size": 2428
},
{
"remote": "data/js/event-box.js",
"local": "/js/event-box.js",
"md5": "553f26707686038e275227a97eb96659",
"size": 12341
},
{
"remote": "data/js/fwUoload.js",
"local": "/js/fwUoload.js",
"md5": "d4a734cce529c3adf831ea46259f9c2d",
"size": 1524
},
{
"remote": "data/js/hue-select.js",
"local": "/js/hue-select.js",
"md5": "0a58f1a339af5c54aecfc5ce1aac7168",
"size": 6367
},
{
"remote": "data/js/jquery-3.7.1.js",
"local": "/js/jquery-3.7.1.js",
"md5": "fdb81281d3773a7462998fdddbe6f5bf",
"size": 196885
},
{
"remote": "data/system/ble.json",
"local": "/system/ble.json",
"md5": "faebefd5b50046a01d4a8aa27f8504d0",
"size": 307
},
{
"remote": "data/system/readme.txt",
"local": "/system/readme.txt",
"md5": "198c92a2cc06b3effce3b5ba313cc6a0",
"size": 86
},
{
"remote": "data/system/tunes.json",
"local": "/system/tunes.json",
"md5": "814999e88296bee179cef5d394f3f696",
"size": 1149
},
{
"remote": "data/system/update.json",
"local": "/system/update.json",
"md5": "01cc6a1935601085df49308cab646673",
"size": 156
},
{
"remote": "data/www/about.html",
"local": "/www/about.html",
"md5": "23f0c991c5c67e7eefe6c24aa50b59fb",
"size": 4222
},
{
"remote": "data/www/edit.html",
"local": "/www/edit.html",
"md5": "fed238dcf87d3797b08ed371330ea800",
"size": 5546
},
{
"remote": "data/www/edit_old.html",
"local": "/www/edit_old.html",
"md5": "9aafba533ac77352d30841cdc34db0c4",
"size": 4240
},
{
"remote": "data/www/failed.html",
"local": "/www/failed.html",
"md5": "a24025d56bef1cd2ed5375f6e8853dde",
"size": 828
},
{
"remote": "data/www/files.html",
"local": "/www/files.html",
"md5": "52d82d4d23b038929691cd0fb20e8ee0",
"size": 9369
},
{
"remote": "data/www/home.html",
"local": "/www/home.html",
"md5": "767fd73b733d8e37dc79252fcd20b610",
"size": 7822
},
{
"remote": "data/www/index.html",
"local": "/www/index.html",
"md5": "af690aaa4dec02691ffdb2765c837370",
"size": 806
},
{
"remote": "data/www/lights.html",
"local": "/www/lights.html",
"md5": "272f8dc423923bb5026837240f654efe",
"size": 19056
},
{
"remote": "data/www/navbar.html",
"local": "/www/navbar.html",
"md5": "89af40b990e297e41e116105b04b66ee",
"size": 533
},
{
"remote": "data/www/ok.html",
"local": "/www/ok.html",
"md5": "db42bd2ff52293c3b4d6ef62fb4e3a42",
"size": 865
},
{
"remote": "data/www/setup.html",
"local": "/www/setup.html",
"md5": "19e1f419844852800757ccd36bb7892a",
"size": 11349
},
{
"remote": "data/www/upgrade.html",
"local": "/www/upgrade.html",
"md5": "f4a3efb67be66b5214d905e605984c93",
"size": 9080
},
{
"remote": "data/www/wifi.html",
"local": "/www/wifi.html",
"md5": "c6e55946a02cb0ff28689dd10d265987",
"size": 5320
}
],
"firmware": {
"md5": "fcec6e659842cc0333880b701a3e2634",
"size": 1401712
},
"release_date": "2025-08-20",
"release_time": "08:53:05"
}

View File

@ -1,228 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Firmware Update</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/nav.css" rel="stylesheet">
<style>
h1{
text-align: center;
margin: 0;
margin-bottom: 0;
font-size: larger;
}
body {
font-family: Arial, sans-serif;
padding: 0;
background-color: #f0f0f0;
width: 100%;
max-width: 700px;
min-width: 300px;
margin: 0 auto;
}
.outer-container {
padding: 4px 20px 4px 20px
}
.container {
border: 1px solid #ccc;
padding: 5px 20px 10px 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
width:90%;
margin-bottom: 20px;
}
.center-text {
text-align: center;
font-size: medium;
font-weight: bold;
margin-bottom: 10;
}
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-end;
}
.row > * {
flex: 1;
width: calc(50% - 10px);
margin-bottom: 8px;
}
.row > *:last-child {
flex: 1;
text-align: right;
}
.webupdate {
text-align: center;
}
button {
background-color: #007bff;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: auto;
}
progress{
flex: 1;
margin: 0 10px;
}
#lblprogress{
flex: 0;
}
.progress-container {
display: flex;
align-items: center;
margin-top: 10px;
}
.info {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: flex-end;
margin-top: 20px;
}
</style>
</head>
<body>
<div id="navbar"></div>
<script>
fetch('/www/navbar.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
<h1 name="h1element">Firmware Update</h1>
<div class="outer-container">
<div class="container">
<legend class="center-text">Local Update</legend>
<div class="row">
<div>
<input type="file" id="update-file" name="update-file" accept=".bin">
</div>
<div>
<button id="submit-update-local" onclick="uploadFile(this)">Update!</button>
</div>
</div>
<div class="info">
<label>File name: "ata_fw_booth_xxx.bin" or "ata_fs_booth_xxx.bin"</label>
</div>
</div>
</div>
<div class="outer-container">
<div class="container">
<legend class="center-text">Web Update</legend>
<div class="webupdate">
<button id="submit-update-local" onclick="CheckUpdates(this)">Check for Updates</button>
</div>
<div class="webupdate">
<button id="submit-update-local" onclick="InstallFirmware">Install Firmware</button>
</div>
</div>
</div>
<div class="outer-container">
<div class="container">
<legend class="center-text">Update Progress</legend>
<div class="progress-container">
<label id="lblprogress" for="firm-progress">Progress:</label>
<progress id="firm-progress" value="0" max="100"></progress>
<label id="lbl-firm-progress" for="firm-progress">- - - -</label>
</div>
</div>
</div>
<script>
const fileInput = document.getElementById('update-file');
const progressBar = document.getElementById('firm-progress');
const progressLabel = document.getElementById('lbl-firm-progress');
const submitButton = document.getElementById('submit-update-local');
function uploadFile(event) {
event.preventDefault();
const file = fileInput.files[0];
const url = '/update'; // Replace with the actual upload URL
// File Checks
//*********************************************************
// Check file extension
if (!file.name.toLowerCase().endsWith('.bin')) {
alert('Please select a file with the ".bin" extension.');
return;
}
// Check filename prefix
if (!file.name.toLowerCase().startsWith('ata_fw_booth') && !file.name.toLowerCase().startsWith('ata_fs_booth')) {
alert('Please select a file with a filename starting with "ata_fw_booth" or "ata_fs_booth".');
return;
}
// Check file size
const maxSizeBytes = 2.7 * 1024 * 1024; // 2.75Mb in bytes
if (file.size > maxSizeBytes) {
alert('Please select a file with a size not exceeding 2.7Mb.');
return;
}
//*********************************************************
const formData = new FormData();
formData.append('file-size', file.size); // Include the file size as a parameter
formData.append('update-file', file);
let s = "file-size: " + file.size;
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const progressPercent = Math.round((event.loaded * 100) / event.total);
progressBar.value = progressPercent;
progressLabel.innerHTML =progressBar.value + "%";
}
});
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log("received status: %d", xhr.status);
if (xhr.status === 200) {
// Upload completed successfully
alert('Upload completed!');
progressLabel.innerHTML = "Completed!";
} else if (xhr.status === 500) {
// Request was aborted (server-side)
alert('Upload aborted by the server.');
progressLabel.innerHTML = "Aborted!";
} else {
// Handle other error cases
alert('An error occurred during the upload.');
progressLabel.innerHTML = "Error!";
}
submitButton.disabled = false;
progressBar.value = 0;
progressLabel.innerHTML = "";
}
};
xhr.open('POST', url, true);
xhr.send(formData);
submitButton.disabled = true;
}
function getAvailableUpdates(){
//Read Json
// Loop to create table
}
</script>
</body>
</html>

View File

@ -1,113 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BLE Interaction</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
button {
display: block;
margin: 10px 0;
padding: 10px;
font-size: 16px;
}
textarea {
width: 100%;
height: 200px;
margin: 10px 0;
font-size: 14px;
}
</style>
</head>
<body>
<h1>BLE Interaction</h1>
<button id="connectBtn">Connect</button>
<button id="checkBtn" disabled>Check</button>
<textarea id="logArea" readonly></textarea>
<button id="startUpgradeBtn" disabled>Start Upgrade</button>
<script>
// Constants
const BLE_SERVER_NAME = "BLE_Server_Name"; // Replace with your server name
const BLE_SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0"; // Replace with your service UUID
const BLE_CHARACTERISTIC_UUID = "12345678-1234-5678-1234-56789abcdef1"; // Replace with your characteristic UUID
let bleDevice = null;
let bleCharacteristic = null;
// Log messages to the textarea
function logMessage(message) {
const logArea = document.getElementById('logArea');
logArea.value += message + '\n';
logArea.scrollTop = logArea.scrollHeight;
}
// Function to connect to the BLE server
async function connectToBle() {
logMessage('Requesting BLE device...');
try {
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ name: BLE_SERVER_NAME }],
optionalServices: [BLE_SERVICE_UUID]
});
logMessage('Connecting to GATT server...');
const server = await bleDevice.gatt.connect();
logMessage('Getting service...');
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
logMessage('Getting characteristic...');
bleCharacteristic = await service.getCharacteristic(BLE_CHARACTERISTIC_UUID);
logMessage('Connected to BLE server.');
document.getElementById('checkBtn').disabled = false;
document.getElementById('startUpgradeBtn').disabled = false;
} catch (error) {
logMessage('Connection failed: ' + error.message);
}
}
// Function to send a JSON packet and wait for a reply
async function sendJsonPacket(packet) {
if (!bleCharacteristic) {
logMessage('Not connected to BLE server.');
return;
}
try {
logMessage('Sending JSON packet...');
const encoder = new TextEncoder();
const encodedPacket = encoder.encode(JSON.stringify(packet));
await bleCharacteristic.writeValue(encodedPacket);
logMessage('Waiting for response...');
const response = await bleCharacteristic.readValue();
const decoder = new TextDecoder();
const decodedResponse = decoder.decode(response);
logMessage('Received response: ' + decodedResponse);
return JSON.parse(decodedResponse);
} catch (error) {
logMessage('Error during communication: ' + error.message);
}
}
// Event listeners for buttons
document.getElementById('connectBtn').addEventListener('click', connectToBle);
document.getElementById('checkBtn').addEventListener('click', () => {
const packet = { action: 'check' };
sendJsonPacket(packet);
});
document.getElementById('startUpgradeBtn').addEventListener('click', () => {
const packet = { action: 'start_upgrade' };
sendJsonPacket(packet);
});
</script>
</body>
</html>

View File

@ -1,533 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATA Firmware Update</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
text-align: center;
}
h1 {
font-size: 22px;
margin-bottom: 5px;
}
.status-container {
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 4px;
}
.status-indicator-ble {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-wifi {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-internet {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
/* Adds space above the WiFi Connect button */
.btn-container.wifi {
margin-top: 20px;
}
button {
flex: 1;
max-width: 130px;
padding: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background 0.3s ease;
}
button:disabled {
background-color: #ccc;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
textarea {
width: 100%;
height: 300px;
font-size: 14px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: none;
}
.input-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-top: 15px;
}
input {
width: 90%;
max-width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input::placeholder {
text-align: center;
}
@media (max-width: 480px) {
body {
padding: 15px;
}
h1 {
font-size: 20px;
}
button {
font-size: 14px;
padding: 8px;
}
input, textarea {
font-size: 14px;
}
}
/* Tabs */
.tab-bar { display:flex; gap:8px; justify-content:center; margin:12px 0 16px; flex-wrap:wrap; }
.tab-bar button { max-width:none; flex:0 0 auto; background:#6c757d; }
.tab-bar button.active { background:#007bff; }
.tab-panel { display:none; }
.tab-panel.active { display:block; }
</style>
</head>
<body>
<h1>ATA Firmware Update</h1>
<!-- Tab Buttons -->
<div class="tab-bar">
<button class="tab-btn active" data-tab="tab-upgrade">Upgrade</button>
<button class="tab-btn" data-tab="tab-wifi">WiFi Comm</button>
</div>
<div id="tab-upgrade" class="tab-panel active">
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">BLE Status: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-wifi"></span>
<label id="status-wifi-client">Wifi Client: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-internet"></span>
<label id="status-internet">Internet: ...</label>
</div>
<div class="status-container">
<label id="status-current-version">Curr Version: ...</label>
</div>
<div class="status-container">
<label id="status-new-version">New Version: ...</label>
</div>
<div class="btn-container">
<label id="ble-device-name">Device Name:</label>
</div>
<div class="btn-container">
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
</div>
<!-- Buttons -->
<div class="btn-container">
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
</div>
<!-- Log Area -->
<textarea id="logArea" readonly></textarea>
<div class="btn-container">
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
</div>
</div> <!-- /tab-upgrade -->
<div id="tab-wifi" class="tab-panel">
<h2 style="margin-top:0; font-size:18px;">WiFi Connection</h2>
<div class="input-container">
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div style="display: flex; align-items: center; gap: 5px;">
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
<label for="showPassword">Show Password</label>
</div>
</div>
<div class="btn-container wifi">
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
</div>
</div><!-- /tab-wifi -->
<script>
(function(){
'use strict';
/* ================= Constants & Packet Layout ================= */
const BLE_SERVER_NAME = "ATALIGHTS"; // Keep hardcoded per instruction (ignore external JSON)
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0";
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Control / status
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Logs / events
// Packet layout (mirrors firmware struct updateStatus)
// byte 0 : wifiStatus (enum)
// byte 1 : wifiOnline (bool)
// bytes2-5: wifiIP
// bytes6-8: currVersion (major,minor,patch)
// bytes9-11: newVersion (major,minor,patch)
// bytes12-31: wifiSSID (20 bytes, null padded)
const PACKET_LEN = 32;
const OFF_WIFI_STATUS = 0;
const OFF_WIFI_ONLINE = 1;
const OFF_WIFI_IP = 2;
const OFF_CURR_VER = 6;
const OFF_NEW_VER = 9;
const OFF_WIFI_SSID = 12;
const WIFI_STAT = { DISCONNECTED:0, BAD_CREDS:1, NO_AP:2, CONNECTED:3 };
const WIFI_STAT_TEXT = ["Disconnected","Bad Creds","No AP","Connected"];
const MAX_LOG_LINES = 400;
/* ================= State ================= */
let bleDevice=null, bleCharacteristic1=null, bleCharacteristic2=null;
let bleConnected=false;
const state = {
wifiStatus: WIFI_STAT.DISCONNECTED,
wifiOnline:false,
wifiIP:[0,0,0,0],
currVersion:[0,0,0],
newVersion:[0,0,0],
wifiSSID:""
};
/* ================= Cached DOM ================= */
const el = {};
function cacheDom(){
el.bleIndicator = document.querySelector('.status-indicator-ble');
el.wifiIndicator = document.querySelector('.status-indicator-wifi');
el.internetIndicator = document.querySelector('.status-indicator-internet');
el.lblBle = document.getElementById('status-ble-connection');
el.lblWifi = document.getElementById('status-wifi-client');
el.lblInternet = document.getElementById('status-internet');
el.lblCurrVer = document.getElementById('status-current-version');
el.lblNewVer = document.getElementById('status-new-version');
el.inDeviceName = document.getElementById('input-DeviceName');
el.inSsid = document.getElementById('wifissid');
el.inPass = document.getElementById('wifipassword');
el.chkShowPass = document.getElementById('showPassword');
el.btnBleConnect = document.getElementById('bleConnectBtn');
el.btnCheckStatus = document.getElementById('checkStatusBtn');
el.btnCheckVersion = document.getElementById('checkVersionBtn');
el.btnStartUpgrade = document.getElementById('startUpgradeBtn');
el.btnWifiConnect = document.getElementById('wifiConnectBtn');
el.logArea = document.getElementById('logArea');
}
/* ================= Utilities ================= */
function logMessage(msg){
const lines = el.logArea.value.trim().length ? el.logArea.value.split(/\n/) : [];
lines.push(msg);
if(lines.length > MAX_LOG_LINES){
lines.splice(0, lines.length - MAX_LOG_LINES);
}
el.logArea.value = lines.join('\n') + '\n';
el.logArea.scrollTop = el.logArea.scrollHeight;
}
function compareVersions(a,b){
for(let i=0;i<3;i++){ if(a[i]>b[i]) return 1; if(a[i]<b[i]) return -1; }
return 0;
}
function colorIndicator(elm, color){ if(elm) elm.style.backgroundColor = color; }
function ipToString(ip){ return ip.join('.'); }
/* ================= Packet Handling ================= */
function parsePacket(data){
if(data.length !== PACKET_LEN) return false;
state.wifiStatus = data[OFF_WIFI_STATUS];
if(state.wifiStatus > WIFI_STAT.CONNECTED) state.wifiStatus = WIFI_STAT.DISCONNECTED; // clamp
state.wifiOnline = !!data[OFF_WIFI_ONLINE];
state.wifiIP = Array.from(data.slice(OFF_WIFI_IP, OFF_WIFI_IP+4));
state.currVersion = Array.from(data.slice(OFF_CURR_VER, OFF_CURR_VER+3));
state.newVersion = Array.from(data.slice(OFF_NEW_VER, OFF_NEW_VER+3));
// Extract SSID (stop at first 0)
let rawSsidBytes = data.slice(OFF_WIFI_SSID, OFF_WIFI_SSID+20);
let zeroIndex = rawSsidBytes.indexOf(0);
if(zeroIndex >= 0) rawSsidBytes = rawSsidBytes.slice(0, zeroIndex);
state.wifiSSID = rawSsidBytes.length ? String.fromCharCode(...rawSsidBytes) : "";
return true;
}
function updateUI(){
// BLE
el.lblBle.textContent = 'BLE Status: ' + (bleConnected ? 'Connected' : 'Disconnected');
colorIndicator(el.bleIndicator, bleConnected ? 'green' : 'gray');
// WiFi client
const statText = WIFI_STAT_TEXT[state.wifiStatus] || 'Unknown';
if(state.wifiStatus === WIFI_STAT.CONNECTED){
const ssidPart = state.wifiSSID ? ' SSID: '+state.wifiSSID : '';
el.lblWifi.textContent = 'Wifi Client: ' + statText + (state.wifiIP[0] ? ' ('+ipToString(state.wifiIP)+')':'' ) + ssidPart;
colorIndicator(el.wifiIndicator, 'green');
} else if(state.wifiStatus === WIFI_STAT.BAD_CREDS){
el.lblWifi.textContent = 'Wifi Client: Bad Credentials';
colorIndicator(el.wifiIndicator, 'orange');
} else if(state.wifiStatus === WIFI_STAT.NO_AP){
el.lblWifi.textContent = 'Wifi Client: AP Not Found';
colorIndicator(el.wifiIndicator, 'orange');
} else {
el.lblWifi.textContent = 'Wifi Client: ' + statText;
colorIndicator(el.wifiIndicator, 'gray');
}
// Internet
el.lblInternet.textContent = state.wifiOnline ? 'Online' : 'Offline';
colorIndicator(el.internetIndicator, state.wifiOnline ? 'green' : 'gray');
// Versions
el.lblCurrVer.textContent = state.currVersion[0] ? 'Curr Version: ' + state.currVersion.join('.') : 'Curr Version: ...';
el.lblNewVer.textContent = state.newVersion[0] ? 'New Version: ' + state.newVersion.join('.') : 'New Version: ...';
// Buttons
el.btnCheckStatus.disabled = !bleConnected;
el.btnWifiConnect.disabled = !bleConnected;
el.btnCheckVersion.disabled = !(bleConnected && state.wifiOnline);
const newerAvail = state.newVersion[0] && state.wifiOnline && compareVersions(state.newVersion, state.currVersion) > 0;
el.btnStartUpgrade.disabled = !newerAvail;
}
/* ================= BLE Operations ================= */
async function connectToBle(){
if(!navigator.bluetooth){ logMessage('Web Bluetooth not supported.'); return; }
try{
bleDevice = await navigator.bluetooth.requestDevice({
filters:[{ name: 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();
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);
// 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: ' + debugInfo + ']';
}
// Show both raw and processed data in console
console.log('--> Raw bytes:', bytes);
console.log('--> As text:', txt);
// Log message with length info for debugging
logMessage(`--> (${bytes.length} bytes) ${txt}`);
} catch(err) {
console.error('Processing error', err);
logMessage('--> Error processing message: ' + err.message);
}
});
bleConnected=true;
bleDevice.addEventListener('gattserverdisconnected', onDisconnect);
const connectedName = bleDevice.name || el.inDeviceName.value || 'Device';
logMessage('Connected to ' + connectedName);
const nameLabel = document.getElementById('ble-device-name');
if(nameLabel){ nameLabel.textContent = 'Device Name: ' + connectedName; }
await readPacket();
updateUI();
}catch(err){
logMessage( err.message.includes('cancel') ? 'Connection cancelled.' : ('Connection failed: '+err.message) );
}
}
function onDisconnect(){
bleConnected=false; updateUI(); logMessage('BLE disconnected');
}
async function sendPacket(msg){
if(!bleCharacteristic1) return;
const enc = new TextEncoder();
for(let attempt=0; attempt<3; attempt++){
try{ await bleCharacteristic1.writeValueWithResponse(enc.encode(msg)); return true; }
catch(e){ if(attempt===2) logMessage('Send failed: '+e.message); else await delay(1000); }
}
return false;
}
async function readPacket(){
if(!bleCharacteristic1) return false;
for(let attempt=0; attempt<3; attempt++){
try{
const val = await bleCharacteristic1.readValue();
const data = new Uint8Array(val.buffer);
if(parsePacket(data)) return true; else { logMessage('Packet parse failed (len='+data.length+')'); return false; }
}catch(e){ if(attempt===2) logMessage('Read failed: '+e.message); else await delay(1000); }
}
return false;
}
/* ================= Actions ================= */
async function wifiConnect(){
const ssid = el.inSsid.value.trim();
const pw = el.inPass.value;
if(!ssid || !pw){ alert('Enter SSID & password'); return; }
logMessage('Sending WiFi credentials...');
el.btnWifiConnect.disabled = true; // prevent multiple submissions while polling
await sendPacket('wifi-connect {"ssid":"'+ssid+'","pass":"'+pw+'"} ');
// Poll for status for up to 15s
const start = Date.now();
while(Date.now()-start < 15000){
await delay(1000);
await readPacket();
updateUI();
if(state.wifiStatus === WIFI_STAT.CONNECTED){
logMessage('WiFi Connected: '+ipToString(state.wifiIP));
break;
}
if(state.wifiStatus === WIFI_STAT.BAD_CREDS){ logMessage('WiFi Error: Bad Credentials'); break; }
if(state.wifiStatus === WIFI_STAT.NO_AP){ logMessage('WiFi Error: AP Not Found'); break; }
}
if(state.wifiStatus !== WIFI_STAT.CONNECTED){ logMessage('WiFi connect attempt finished with status: '+WIFI_STAT_TEXT[state.wifiStatus]); }
if(!bleConnected) return; // keep disabled if BLE disconnected during process
// Re-enable for retry unless connected
if(state.wifiStatus !== WIFI_STAT.CONNECTED){ el.btnWifiConnect.disabled = false; }
}
async function checkStatus(){ if(await readPacket()) updateUI(); }
async function checkVersion(){
el.btnCheckVersion.disabled = true;
logMessage('Checking for new version...');
await sendPacket('version-check');
const start = Date.now();
while(Date.now()-start < 15000){
await delay(750);
await readPacket();
if(state.newVersion[0]){ logMessage('Latest version: '+state.newVersion.join('.')); break; }
}
if(!state.newVersion[0]) logMessage('No new version info received');
updateUI();
}
async function startUpgrade(){
if(el.btnStartUpgrade.disabled) return;
logMessage('Starting upgrade...');
el.btnStartUpgrade.disabled = true;
await sendPacket('upgrade-start');
// Progress will arrive via characteristic2 logs
}
/* ================= Helpers ================= */
const delay = ms => new Promise(r=>setTimeout(r,ms));
function togglePasswordVisibility(){ el.inPass.type = el.chkShowPass.checked ? 'text' : 'password'; }
function init(){
cacheDom();
el.inDeviceName.value = BLE_SERVER_NAME;
el.chkShowPass.addEventListener('change', togglePasswordVisibility);
// Inline onclicks already wired; ensure functions are in scope
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn=>{
btn.addEventListener('click', ()=>{
document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('active'));
btn.classList.add('active');
const id = btn.getAttribute('data-tab');
const panel = document.getElementById(id);
if(panel) panel.classList.add('active');
});
});
updateUI();
logMessage('Ready. Enter device name or use default and press Connect.');
}
// Expose functions for inline handlers
window.connectToBle = connectToBle;
window.checkStatus = checkStatus;
window.checkVersion = checkVersion;
window.startUpgrade = startUpgrade;
window.wifiConnect = wifiConnect;
window.togglePasswordVisibility = togglePasswordVisibility;
window.addEventListener('DOMContentLoaded', init);
})();
</script>
</body>
</html>

View File

@ -1,13 +0,0 @@
Choose Control App:
1) Open app-events.json
2) Change index to corresponding app
a. 0=DSLRBooth, 1=...., 2=.....
Front Constant Light: ( to enable the light)
1) Open led-devices.json
2) Change "front-light", "en" to true
Rear Constant Light:
1) Open led-devices.json
2) Change "rear-light", "en" to true

Some files were not shown because too many files have changed in this diff Show More