diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..0b6a2f6
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,14 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "PlatformIO Build",
+ "type": "shell",
+ "command": "C:\\Users\\davie\\.platformio\\penv\\Scripts\\platformio.exe",
+ "args": [
+ "run"
+ ],
+ "group": "build"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/booths/roamer-big.json b/data/booths/roamer-big.json
index b555e9e..1a15f18 100644
--- a/data/booths/roamer-big.json
+++ b/data/booths/roamer-big.json
@@ -18,36 +18,36 @@
{
"en": true,
"freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
+ "min": 0.0,
+ "max": 100.0,
+ "default": 0.0,
"vision": false,
"deltarate": 0
},
{
"en": true,
"freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
+ "min": 0.0,
+ "max": 100.0,
+ "default": 0.0,
"vision": false,
"deltarate": 0
},
{
"en": true,
"freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
+ "min": 0.0,
+ "max": 100.0,
+ "default": 0.0,
"vision": true,
"deltarate": 0
},
{
"en": true,
"freq": 250,
- "min": 0,
- "max": 100,
- "default": 0,
+ "min": 0.0,
+ "max": 100.0,
+ "default": 0.0,
"vision": true,
"deltarate": 0
}
@@ -61,6 +61,7 @@
"min": 0.0,
"max": 100.0,
"step": 1.5,
+ "rate": 30.0,
"skip-count": 5,
"vision": true
},
@@ -71,6 +72,7 @@
"min": 0.0,
"max": 100.0,
"step": 1.5,
+ "rate": 30.0,
"skip-count": 5,
"vision": true
}
@@ -83,7 +85,7 @@
"t-sensor": {
"en": true,
"addr": 72,
- "sp1": 85.0,
+ "sp1": 80.0,
"fan-pwr1": 50.0,
"sp2": 90.0,
"fan-pwr2": 100.0,
diff --git a/data/system/ble.json b/data/system/ble.json
index c35b40d..82da5c8 100644
--- a/data/system/ble.json
+++ b/data/system/ble.json
@@ -1,5 +1,6 @@
{
- "name": "ATALIGHTS",
+ "name": "ATALIGHTS",
+ "unique": true,
"lights-service": "FFE0",
"lights-char": "FFE1",
"stick-char": "FFE2",
diff --git a/data/www/ata-boothifier-upgradeV3.html b/data/www/ata-boothifier-upgrade.html
similarity index 89%
rename from data/www/ata-boothifier-upgradeV3.html
rename to data/www/ata-boothifier-upgrade.html
index e8c4179..53868db 100644
--- a/data/www/ata-boothifier-upgradeV3.html
+++ b/data/www/ata-boothifier-upgrade.html
@@ -256,6 +256,8 @@
WiFi Client: ...
Internet: ...
+
+ Temperature: ...
Current Version: ...
New Version: ...
@@ -402,9 +404,9 @@ IMPORTANT NOTES:
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1";
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1";
- const PACKET_LEN = 32;
- const OFF_WIFI_STATUS = 0, OFF_WIFI_ONLINE = 1, OFF_WIFI_IP = 2;
- const OFF_CURR_VER = 6, OFF_NEW_VER = 9, OFF_WIFI_SSID = 12;
+ const PACKET_LEN = 30; // Packed struct: 1+1+4+4+3+3+20 = 36 bytes, but check actual size
+ const OFF_WIFI_STATUS = 0, OFF_WIFI_ONLINE = 1, OFF_TEMPERATURE = 2;
+ const OFF_WIFI_IP = 6, OFF_CURR_VER = 10, OFF_NEW_VER = 13, OFF_WIFI_SSID = 16;
const WIFI_STAT = { DISCONNECTED:0, BAD_CREDS:1, NO_AP:2, CONNECTED:3 };
const WIFI_STAT_TEXT = ["Disconnected", "Bad Credentials", "AP Not Found", "Connected"];
@@ -413,9 +415,11 @@ IMPORTANT NOTES:
/* ================= State ================= */
let bleDevice = null, bleCharacteristic1 = null, bleCharacteristic2 = null;
let bleConnected = false;
+ let versionCheckPerformed = false;
const state = {
wifiStatus: WIFI_STAT.DISCONNECTED,
wifiOnline: false,
+ temperature: 0.0,
wifiIP: [0, 0, 0, 0],
currVersion: [0, 0, 0],
newVersion: [0, 0, 0],
@@ -429,9 +433,11 @@ IMPORTANT NOTES:
el.bleIndicator = document.getElementById('indicator-ble');
el.wifiIndicator = document.getElementById('indicator-wifi');
el.internetIndicator = document.getElementById('indicator-internet');
+ el.temperatureIndicator = document.getElementById('indicator-temperature');
el.lblBle = document.getElementById('status-ble-connection');
el.lblWifi = document.getElementById('status-wifi-client');
el.lblInternet = document.getElementById('status-internet');
+ el.lblTemperature = document.getElementById('status-temperature');
el.lblCurrVer = document.getElementById('status-current-version');
el.lblNewVer = document.getElementById('status-new-version');
el.lblDeviceName = document.getElementById('ble-device-name-label');
@@ -491,17 +497,35 @@ IMPORTANT NOTES:
/* ================= Packet Handling ================= */
function parsePacket(data) {
- if(data.length !== PACKET_LEN) return false;
+ if(data.length < PACKET_LEN) return false;
+
+ // Debug: Log received packet info
+ console.log(`Received packet: ${data.length} bytes`);
+ console.log('Packet bytes:', Array.from(data.slice(0, 12)).map(b => b.toString(16).padStart(2, '0')).join(' '));
+
state.wifiStatus = data[OFF_WIFI_STATUS];
if(state.wifiStatus > WIFI_STAT.CONNECTED) state.wifiStatus = WIFI_STAT.DISCONNECTED;
+
state.wifiOnline = !!data[OFF_WIFI_ONLINE];
+
+ // Parse temperature (4-byte little-endian float at offset 2)
+ const tempBytes = data.slice(OFF_TEMPERATURE, OFF_TEMPERATURE + 4);
+ const tempDataView = new DataView(tempBytes.buffer, tempBytes.byteOffset, 4);
+ state.temperature = tempDataView.getFloat32(0, true); // true = little-endian
+
+ // Debug: Log temperature parsing
+ //console.log(`Temperature bytes: ${Array.from(tempBytes).map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
+ //console.log(`Parsed temperature: ${state.temperature}`);
+
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));
+
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;
}
@@ -527,26 +551,48 @@ IMPORTANT NOTES:
el.lblInternet.textContent = 'Internet: ' + (state.wifiOnline ? 'Online' : 'Offline');
setIndicatorStatus(el.internetIndicator, state.wifiOnline ? 'is-success' : '');
+ // Temperature
+
+ if(state.temperature > 0) {
+ el.lblTemperature.textContent = `Temperature: ${state.temperature.toFixed(1)}°F`;
+ // Set indicator based on temperature ranges
+ if(state.temperature > 90) {
+ setIndicatorStatus(el.temperatureIndicator, 'is-warning'); // Warning for high temp
+ } else if(state.temperature > 0) {
+ setIndicatorStatus(el.temperatureIndicator, 'is-success'); // Normal temp
+ } else {
+ setIndicatorStatus(el.temperatureIndicator, ''); // Unknown/invalid temp
+ }
+ } else {
+ el.lblTemperature.textContent = 'Temperature: ...';
+ setIndicatorStatus(el.temperatureIndicator, '');
+ }
+
+
// Versions
el.lblCurrVer.textContent = state.currVersion[0] ? 'Current Version: ' + state.currVersion.join('.') : 'Current Version: ...';
el.lblNewVer.textContent = state.newVersion[0] ? 'New Version: ' + state.newVersion.join('.') : 'New Version: ...';
// Buttons
+ el.btnBleConnect.disabled = bleConnected;
+ el.btnBleConnect.textContent = bleConnected ? 'Connected' : 'Connect';
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;
+ const newerAvail = versionCheckPerformed && 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]
+ if(!navigator.bluetooth){ logMessage('Web Bluetooth not supported.'); return; }
+
+ try{
+ bleDevice = await navigator.bluetooth.requestDevice({
+ filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }],
+ optionalServices:[BLE_SERVICE_UUID]
});
+
const server = await bleDevice.gatt.connect();
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
@@ -621,6 +667,7 @@ IMPORTANT NOTES:
function onDisconnect() {
bleConnected = false;
+ versionCheckPerformed = false; // Reset version check flag on disconnect
el.lblDeviceName.textContent = 'Device Name';
updateUI();
logMessage('BLE disconnected');
@@ -676,6 +723,7 @@ IMPORTANT NOTES:
async function checkVersion() {
el.btnCheckVersion.disabled = true;
+ versionCheckPerformed = false; // Reset flag at start
logMessage('Checking for new version...');
await sendPacket('version-check');
const start = Date.now();
@@ -683,6 +731,7 @@ IMPORTANT NOTES:
await delay(750);
await readPacket();
if(state.newVersion[0]) {
+ versionCheckPerformed = true; // Set flag when version is received
if(compareVersions(state.newVersion, state.currVersion) > 0) {
logMessage('New version available: ' + state.newVersion.join('.'));
} else {
@@ -691,7 +740,10 @@ IMPORTANT NOTES:
break;
}
}
- if(!state.newVersion[0]) logMessage('No new version info received');
+ if(!state.newVersion[0]) {
+ logMessage('No new version info received');
+ versionCheckPerformed = false; // Ensure flag is false if no version received
+ }
updateUI();
}
diff --git a/docs/links.txt b/docs/links.txt
new file mode 100644
index 0000000..e1253fd
--- /dev/null
+++ b/docs/links.txt
@@ -0,0 +1,4 @@
+
+SP110E Protocol:
+https://gist.github.com/mbullington/37957501a07ad065b67d4e8d39bfe012
+
diff --git a/include/ATALights.h b/include/ATALights.h
index 06f0801..4545449 100644
--- a/include/ATALights.h
+++ b/include/ATALights.h
@@ -3,11 +3,15 @@
#include
#include
#include "ColorPalettes.h"
+#include "PWM_Output.h"
#define PIXEL_INDEX -3
#define SOLID_COLOR_INDEX -2
#define OFF_INDEX -1
+#define WITH_GAPS true
+#define NO_GAPS false
+
extern uint32_t whiteTimeout;
typedef struct {
@@ -44,27 +48,39 @@ typedef struct {
extern LEDSTRIP_SETTINGS ledSettings[2];
-// Forward declarations
-//extern CRGB* leds1;
-//extern CRGB* leds2;
-void Lights_Control_Task(void *parameters);
-void Init_Lights_Task(void);
-void Lights_Control_Task_Resume(void);
-void Init_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright);
+void RGB_Lights_Control_Task(void *parameters);
+
+void Init_RGB_Lights_Task(void);
+
+void Init_RGB_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, const String& chipType, uint8_t bright);
EOrder GetEOrderByString(const String& rgbStr);
-void Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu);
-void Lights_Set_ON(void);
-void Lights_Set_OFF(void);
-void Lights_Set_Brightness(uint8_t scale);
+void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu);
+
+void RGB_Animations_ON(void);
+
+void RGB_Animations_OFF(void);
+
+void RGB_Lights_Set_Brightness(uint8_t scale);
+
void Lights_Set_White(uint8_t val);
+
//void createFirePalette(CRGBPalette16& palette, COLOR_PACK& colorPack);
void createFirePalette(CRGBPalette16& palette, const COLOR_PACK& colorPack);
+
void loadColorPack(COLOR_PACK& dest, const COLOR_PACK& src);
+//void Init_Ramp_Front_Light_Task(void);
+
+//void Ramp_Front_Light_Control_Task(void *parameters);
+
+//void Start_RampUp_Front_Light(int _rampTime=1000, int _startDelay=0);
+
+
+
diff --git a/include/Animations.h b/include/Animations.h
index 33690e5..c8bdaec 100644
--- a/include/Animations.h
+++ b/include/Animations.h
@@ -4,6 +4,8 @@
#include
#include
#include "ColorPalettes.h"
+#include "PWM_Output.h"
+#include "system.h"
#define MinLoopDelay 2
#define MaxLoopDelay 200
@@ -24,19 +26,32 @@ void Anim_Rainbow(bool volatile& loop_active_flag, CRGB* leds, int size, int spe
void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const CRGBPalette16& firePalette, int shift);
-void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, uint8_t numRepeats, int speed);
+void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, bool gaps, uint8_t numRepeats, int minSpeed, int maxSpeed) ;
//template
//void Anim_Color_Gradient_Trans(bool volatile& activeFlag, CRGB* leds, int size, TPalette& palette, uint8_t paletteSize, uint8_t numRepeats, int speed);
//void Anim_Color_Gradient_Red_Yellow_Violet(bool volatile& activeFlag, CRGB* leds, int size, int speed);
-void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier);
+void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int minSpeed, int maxSpeed, bool randomDecay, int cometMultiplier = 1);
//void Anim_Comet_Rainbow(bool volatile& activeFlag, CRGB* leds, int size, int speed);
void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB col1, CRGB col2, int totalDurationMs, int shift);
+void Anim_TimedFill_Flash(bool volatile& activeFlag, CRGB* leds, int size, PWM_Output* pwmOut, PWM_OUT_SETTINGS* pwmSettings, int pwmOutDelay, int flashTimeout, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0);
void Anim_ColorBreath(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, uint32_t timeMs, int speed);
+void Anim_Sparkle(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, int speed, uint8_t sparkleChance);
+
void Anim_GradientRotate(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, int speed);
+void Anim_SolidWhite(bool volatile& activeFlag, CRGB* leds, PWM_Output* pwmOut, int size, int brightness, int timeoutMs = -1);
+
+void Anim_Roating_Snakes(bool volatile& activeFlag, CRGB* leds, const COLOR_PACK& colorPack, int brightness);
+
+void Anim_Snakes(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, int multiplier = 1);
+
+void Anim_Birds(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed , int multiplier = 1);
+
+void Anim_Morph(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed , int morphSteps);
+
uint32_t getRandomValue(uint32_t maxValue);
diff --git a/include/AppUpgrade.h b/include/AppUpgrade.h
index d50c97b..a4696f7 100644
--- a/include/AppUpgrade.h
+++ b/include/AppUpgrade.h
@@ -18,6 +18,15 @@
#define HTTP_RETRY_COUNT 5 // Increased from 3
#define HTTP_RETRY_DELAY_MS 1000 // Increased from 500
+/**
+ * @brief Update mode enumeration
+ */
+enum class UpdateMode {
+ UPDATE_FILES_ONLY, ///< Update files only, skip firmware
+ UPDATE_FIRMWARE_ONLY, ///< Update firmware only, skip files
+ UPDATE_BOTH ///< Update both files and firmware (default)
+};
+
// Allow external cancellation
extern volatile bool g_UpdateCancelFlag;
@@ -35,15 +44,6 @@ extern TaskHandle_t Update_Task_Handle;
extern Version otaVersion;
-/**
- * @brief Update mode enumeration
- */
-enum class UpdateMode {
- UPDATE_FILES_ONLY, ///< Update files only, skip firmware
- UPDATE_FIRMWARE_ONLY, ///< Update firmware only, skip files
- UPDATE_BOTH ///< Update both files and firmware (default)
-};
-
/**
* @brief File information structure
*/
@@ -241,4 +241,5 @@ void setGlobalUpdateMode(UpdateMode mode);
UpdateMode getGlobalUpdateMode();
void setUpdateModeFilesOnly();
void setUpdateModeFirmwareOnly();
-void setUpdateModeBoth();
\ No newline at end of file
+void setUpdateModeBoth();
+
diff --git a/include/ColorPalettes.h b/include/ColorPalettes.h
index 2c68768..2df61e1 100644
--- a/include/ColorPalettes.h
+++ b/include/ColorPalettes.h
@@ -9,33 +9,182 @@ typedef struct {
} COLOR_PACK;
-const COLOR_PACK colorPack_FireRed PROGMEM = { 4, { CRGB::Red, CRGB::OrangeRed, CRGB::Yellow, CRGB::Black } };
-const COLOR_PACK colorPack_FireGreen PROGMEM = { 4, { CRGB::DarkGreen, CRGB::Green, CRGB::LightGreen, CRGB::Black } };
-const COLOR_PACK colorPack_FireBlue PROGMEM = { 4, { CRGB::DarkBlue, CRGB::Blue, CRGB::LightBlue, CRGB::Black } };
-const COLOR_PACK colorPack_FireViolet PROGMEM = { 4, { CRGB::Purple, CRGB::Blue, CRGB::Violet, CRGB::Black } };
+const COLOR_PACK colorPack_Fire_Red PROGMEM = { 4, { CRGB::Red, CRGB::OrangeRed, CRGB::Yellow, CRGB::Black } };
+const COLOR_PACK colorPack_Fire_Green PROGMEM = { 4, { CRGB::DarkGreen, CRGB::Green, CRGB::LightGreen, CRGB::Black } };
+const COLOR_PACK colorPack_Fire_Blue PROGMEM = { 4, { CRGB::DarkBlue, CRGB::Blue, CRGB::LightBlue, CRGB::Black } };
+const COLOR_PACK colorPack_Fire_Violet PROGMEM = { 4, { CRGB::Purple, CRGB::Blue, CRGB::Violet, CRGB::Black } };
// Fire (compacted: single PROGMEM array, removes duplicate size constants)
const COLOR_PACK fireColorPacks[] PROGMEM = {
- colorPack_FireRed,
- colorPack_FireGreen,
- colorPack_FireBlue,
- colorPack_FireViolet
+ colorPack_Fire_Red,
+ colorPack_Fire_Green,
+ colorPack_Fire_Blue,
+ colorPack_Fire_Violet
+};
+
+
+// Dual Color Packs
+const COLOR_PACK colorPack_RED_WHITE PROGMEM = { 2, { CRGB::Red, CRGB::White } };
+const COLOR_PACK colorPack_ORANGE_WHITE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::White } };
+const COLOR_PACK colorPack_GREEN_WHITE PROGMEM = { 2, { CRGB::Green, CRGB::White } };
+const COLOR_PACK colorPack_BLUE_WHITE PROGMEM = { 2, { CRGB::Blue, CRGB::White } };
+const COLOR_PACK colorPack_PINK_WHITE PROGMEM = { 2, { CRGB::Pink, CRGB::White } };
+const COLOR_PACK colorPack_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_YELLOW PROGMEM = { 2, { CRGB::Purple, CRGB::Yellow } };
+
+
+const COLOR_PACK colorPack_RED_YELLOW PROGMEM = { 2, { CRGB::Red, CRGB::Yellow } };
+const COLOR_PACK colorPack_BLUE_YELLOW PROGMEM = { 2, { CRGB::Blue, CRGB::Yellow } };
+const COLOR_PACK colorPack_GREEN_YELLOW PROGMEM = { 2, { CRGB::Green, CRGB::Yellow } };
+const COLOR_PACK colorPack_BLUE_GREEN PROGMEM = { 2, { CRGB::Blue, CRGB::Green } };
+const COLOR_PACK colorPack_BLUE_RED PROGMEM = { 2, { CRGB::Blue, CRGB::Red } };
+
+// Additional complementary color pairs
+
+const COLOR_PACK colorPack_ORANGE_BLUE PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Blue } };
+const COLOR_PACK colorPack_RED_GREEN PROGMEM = { 2, { CRGB::Red, CRGB::Green } };
+const COLOR_PACK colorPack_CYAN_RED PROGMEM = { 2, { CRGB::Cyan, CRGB::Red } };
+const COLOR_PACK colorPack_MAGENTA_GREEN PROGMEM = { 2, { CRGB::Magenta, CRGB::Green } };
+
+// Warm/cool combinations
+const COLOR_PACK colorPack_ORANGE_CYAN PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Cyan } };
+const COLOR_PACK colorPack_PINK_GREEN PROGMEM = { 2, { CRGB::Pink, CRGB::Green } };
+const COLOR_PACK colorPack_VIOLET_LIME PROGMEM = { 2, { CRGB::DarkViolet, CRGB::Lime } };
+
+// Analogous combinations
+const COLOR_PACK colorPack_RED_ORANGE PROGMEM = { 2, { CRGB::Red, CRGB::DarkOrange } };
+const COLOR_PACK colorPack_BLUE_PURPLE PROGMEM = { 2, { CRGB::Blue, CRGB::Purple } };
+const COLOR_PACK colorPack_YELLOW_LIME PROGMEM = { 2, { CRGB::Yellow, CRGB::Lime } };
+
+// Triadic combinations
+const COLOR_PACK colorPack_RED_CYAN PROGMEM = { 2, { CRGB::Red, CRGB::Cyan } };
+const COLOR_PACK colorPack_YELLOW_MAGENTA PROGMEM = { 2, { CRGB::Yellow, CRGB::Magenta } };
+const COLOR_PACK colorPack_GREEN_MAGENTA PROGMEM = { 2, { CRGB::Green, CRGB::Magenta } };
+
+// 25 Dual Color Packs
+const COLOR_PACK double_colorPacks[] PROGMEM = {
+ colorPack_RED_WHITE,
+ colorPack_RED_YELLOW,
+ colorPack_BLUE_WHITE,
+ colorPack_PINK_WHITE,
+ colorPack_GREEN_WHITE,
+ colorPack_PURPLE_WHITE,
+ colorPack_GREEN_YELLOW,
+ colorPack_PURPLE_PINK,
+ colorPack_CYAN_RED,
+
+ colorPack_ORANGE_WHITE,
+
+ colorPack_PURPLE_PINK,
+ colorPack_PURPLE_YELLOW,
+
+ colorPack_BLUE_YELLOW,
+
+ colorPack_BLUE_GREEN,
+ colorPack_BLUE_RED,
+ colorPack_ORANGE_BLUE,
+ colorPack_RED_GREEN,
+
+ colorPack_MAGENTA_GREEN,
+ colorPack_ORANGE_CYAN,
+ colorPack_PINK_GREEN,
+ colorPack_VIOLET_LIME,
+ colorPack_RED_ORANGE,
+ colorPack_BLUE_PURPLE,
+ colorPack_YELLOW_LIME,
+ colorPack_RED_CYAN,
+ colorPack_YELLOW_MAGENTA,
+ colorPack_GREEN_MAGENTA
};
// 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_USA PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Blue } };
-const COLOR_PACK colorPack_MEXICO PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Red } };
-const COLOR_PACK colorPack_CANADA PROGMEM = { 2, { CRGB::Red, CRGB::White } };
-const COLOR_PACK colorPack_GERMANY PROGMEM = { 3, { CRGB::Black, CRGB::Red, CRGB::Yellow } };
-const COLOR_PACK combo_colorPacks[] PROGMEM = {
- colorPack_RAINBOW,
- colorPack_USA,
- colorPack_MEXICO,
- colorPack_CANADA,
- colorPack_GERMANY
+const COLOR_PACK colorPack_RAINBOW PROGMEM = { 7, { CRGB::Red, CRGB::OrangeRed, CRGB::Yellow, CRGB::Green, CRGB::Blue, CRGB::BlueViolet, CRGB::MediumVioletRed } };
+// Triple Color Packs, Common Flags
+const COLOR_PACK colorPack_RED_WHITE_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Blue } };
+const COLOR_PACK colorPack_RED_WHITE_GREEN PROGMEM = { 3, { CRGB::Red, CRGB::White, CRGB::Green } };
+const COLOR_PACK colorPack_RED_YELLOW_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Yellow, CRGB::Blue } };
+const COLOR_PACK colorPack_RED_ORANGE_YELLOW PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Yellow } };
+const COLOR_PACK colorPack_RED_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_GREEN_WHITE_RED PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Red } };
+const COLOR_PACK colorPack_GREEN_WHITE_ORANGE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::DarkOrange } };
+const COLOR_PACK colorPack_GREEN_WHITE_BLUE PROGMEM = { 3, { CRGB::Green, CRGB::White, CRGB::Blue } };
+
+
+const COLOR_PACK colorPack_BLUE_WHITE_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::White, CRGB::Green } };
+const COLOR_PACK colorPack_BLUE_YELLOW_GREEN PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Green } };
+const COLOR_PACK colorPack_BLUE_YELLOW_RED PROGMEM = { 3, { CRGB::Blue, CRGB::Yellow, CRGB::Red } };
+
+// Additional triple color combinations
+const COLOR_PACK colorPack_PURPLE_PINK_CYAN PROGMEM = { 3, { CRGB::Purple, CRGB::Pink, CRGB::Cyan } };
+const COLOR_PACK colorPack_ORANGE_YELLOW_LIME PROGMEM = { 3, { CRGB::DarkOrange, CRGB::Yellow, CRGB::Lime } };
+const COLOR_PACK colorPack_MAGENTA_YELLOW_CYAN PROGMEM = { 3, { CRGB::Magenta, CRGB::Yellow, CRGB::Cyan } };
+const COLOR_PACK colorPack_LIME_CYAN_MAGENTA PROGMEM = { 3, { CRGB::Lime, CRGB::Cyan, CRGB::Magenta } };
+
+// Warm tone combinations
+const COLOR_PACK colorPack_RED_ORANGE_PINK PROGMEM = { 3, { CRGB::Red, CRGB::DarkOrange, CRGB::Pink } };
+const COLOR_PACK colorPack_YELLOW_ORANGE_RED PROGMEM = { 3, { CRGB::Yellow, CRGB::DarkOrange, CRGB::Red } };
+const COLOR_PACK colorPack_PINK_PURPLE_MAGENTA PROGMEM = { 3, { CRGB::Pink, CRGB::Purple, CRGB::Magenta } };
+
+// Cool tone combinations
+const COLOR_PACK colorPack_BLUE_CYAN_LIME PROGMEM = { 3, { CRGB::Blue, CRGB::Cyan, CRGB::Lime } };
+const COLOR_PACK colorPack_PURPLE_BLUE_CYAN PROGMEM = { 3, { CRGB::Purple, CRGB::Blue, CRGB::Cyan } };
+const COLOR_PACK colorPack_GREEN_CYAN_BLUE PROGMEM = { 3, { CRGB::Green, CRGB::Cyan, CRGB::Blue } };
+
+// High contrast combinations
+const COLOR_PACK colorPack_RED_GREEN_BLUE PROGMEM = { 3, { CRGB::Red, CRGB::Green, CRGB::Blue } };
+const COLOR_PACK colorPack_YELLOW_PURPLE_ORANGE PROGMEM = { 3, { CRGB::Yellow, CRGB::Purple, CRGB::DarkOrange } };
+const COLOR_PACK colorPack_PINK_LIME_PURPLE PROGMEM = { 3, { CRGB::Pink, CRGB::Lime, CRGB::Purple } };
+
+
+
+// 25 Triple Color Packs
+const COLOR_PACK tripple_colorPacks[] PROGMEM = {
+ colorPack_RED_WHITE_BLUE,
+ colorPack_RED_WHITE_GREEN,
+ colorPack_GREEN_WHITE_ORANGE,
+ colorPack_BLUE_YELLOW_GREEN,
+ colorPack_RED_GREEN_BLUE,
+ colorPack_PURPLE_PINK_CYAN,
+ colorPack_YELLOW_ORANGE_RED,
+ colorPack_BLUE_CYAN_LIME,
+ colorPack_PINK_PURPLE_MAGENTA,
+
+ colorPack_RED_YELLOW_BLUE,
+ colorPack_RED_ORANGE_YELLOW,
+ colorPack_RED_YELLOW_GREEN,
+ colorPack_RED_PURPLE_BLUE,
+ colorPack_GREEN_WHITE_RED,
+ colorPack_GREEN_WHITE_BLUE,
+ colorPack_BLUE_WHITE_GREEN,
+ colorPack_BLUE_YELLOW_RED,
+ colorPack_ORANGE_YELLOW_LIME,
+ colorPack_MAGENTA_YELLOW_CYAN,
+ colorPack_LIME_CYAN_MAGENTA,
+ colorPack_RED_ORANGE_PINK,
+ colorPack_PURPLE_BLUE_CYAN,
+ colorPack_GREEN_CYAN_BLUE,
+ colorPack_YELLOW_PURPLE_ORANGE,
+ colorPack_PINK_LIME_PURPLE
+};
+
+const COLOR_PACK colorPack_grad_blueish PROGMEM = { 3, { CRGB::Blue, CRGB::Cyan, CRGB::Green } };
+const COLOR_PACK colorPack_grad_greenish PROGMEM = { 3, { CRGB::Green, CRGB::Lime, CRGB::Cyan } };
+const COLOR_PACK colorPack_grad_redish PROGMEM = { 3, { CRGB::Red, CRGB::Magenta, CRGB::Pink } };
+const COLOR_PACK colorPack_grad_violetish PROGMEM = { 3, { CRGB::Purple, CRGB::Magenta, CRGB::Violet } };
+const COLOR_PACK colorPack_grad_yellowish PROGMEM = { 3, { CRGB::OrangeRed, CRGB::DarkOrange, CRGB::Yellow } };
+
+
+const COLOR_PACK gradient_colorPack[] PROGMEM = {
+ colorPack_grad_blueish,
+ colorPack_grad_greenish,
+ colorPack_grad_redish,
+ colorPack_grad_violetish,
+ colorPack_grad_yellowish
};
@@ -45,20 +194,22 @@ const COLOR_PACK colorPack_Single_Red PROGMEM = { 1, { CRGB::Red } };
const COLOR_PACK colorPack_Single_Orange PROGMEM = { 1, { CRGB::OrangeRed } };
const COLOR_PACK colorPack_Single_Yellow PROGMEM = { 1, { CRGB::Yellow } };
const COLOR_PACK colorPack_Single_Green PROGMEM = { 1, { CRGB::Green } };
+const COLOR_PACK colorPack_Single_Cyan PROGMEM = { 1, { CRGB::Cyan } };
const COLOR_PACK colorPack_Single_Blue PROGMEM = { 1, { CRGB::Blue } };
const COLOR_PACK colorPack_Single_Viloet PROGMEM = { 1, { CRGB::DarkViolet } };
const COLOR_PACK colorPack_Single_Magenta PROGMEM = { 1, { CRGB::Magenta } };
const COLOR_PACK colorPack_Single_White PROGMEM = { 1, { CRGB::White } };
+// 9 Single Color Packs
const COLOR_PACK single_colorPacks[] PROGMEM = {
+ colorPack_Single_White,
colorPack_Single_Red,
colorPack_Single_Orange,
colorPack_Single_Yellow,
colorPack_Single_Green,
+ colorPack_Single_Cyan,
colorPack_Single_Blue,
- colorPack_Single_Viloet,
colorPack_Single_Magenta,
- colorPack_Single_White
};
@@ -66,10 +217,12 @@ const COLOR_PACK single_colorPacks[] PROGMEM = {
// Dashes
+//const COLOR_PACK colorPack_RAINBOW_Dashes PROGMEM = { 14, { CRGB::Red, CRGB::Black, CRGB::OrangeRed, CRGB::Black, CRGB::Yellow, CRGB::Black, CRGB::Green, CRGB::Black, CRGB::Blue, CRGB::Black, CRGB::BlueViolet, CRGB::Black, CRGB::MediumVioletRed, CRGB::Black } };
const COLOR_PACK colorPack_RedBlack PROGMEM = { 2, { CRGB::Red, CRGB::Black } };
const COLOR_PACK colorPack_OrangeBlack PROGMEM = { 2, { CRGB::DarkOrange, CRGB::Black } };
const COLOR_PACK colorPack_YellowBlack PROGMEM = { 2, { CRGB::Yellow, CRGB::Black } };
const COLOR_PACK colorPack_GreenBlack PROGMEM = { 2, { CRGB::Green, CRGB::Black } };
+const COLOR_PACK colorPack_CyanBlack PROGMEM = { 2, { CRGB::Cyan, CRGB::Black } };
const COLOR_PACK colorPack_BlueBlack PROGMEM = { 2, { CRGB::Blue, CRGB::Black } };
const COLOR_PACK colorPack_IndigoBlack PROGMEM = { 2, { CRGB::Indigo, CRGB::Black } };
const COLOR_PACK colorPack_VioletBlack PROGMEM = { 2, { CRGB::MediumVioletRed, CRGB::Black } };
@@ -90,8 +243,6 @@ const COLOR_PACK dashes_ColorPacks[] PROGMEM = {
-
-
void Create_Red_Yellow_Violet_Palette(CRGBPalette16& customPalette);
diff --git a/include/PWM_Output.h b/include/PWM_Output.h
index 935dc5f..8cf92c2 100644
--- a/include/PWM_Output.h
+++ b/include/PWM_Output.h
@@ -18,11 +18,14 @@ class PWM_Output {
void setFreq(uint32_t fq);
int linearizeOutput(float inp);
void setMaxDuty(float duty);
- int getOutVal(void);
- void setResolution(uint8_t res);
- float getMaxDuty(void) {
+ float getMaxDuty(void){
return maxDuty;
}
+ float getDuty(void) {
+ return currDuty;
+ }
+ int getOutVal(void);
+ void setResolution(uint8_t res);
int currOutVal;
private:
diff --git a/include/Ramp_Lights.h b/include/Ramp_Lights.h
index 0979131..4210ff6 100644
--- a/include/Ramp_Lights.h
+++ b/include/Ramp_Lights.h
@@ -7,13 +7,13 @@ enum RAMP_STATE { RampingUp = 0, RampingDown };
class RAMP_LIGHT {
public:
- RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, int min, int max, float step);
+ RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, float min, float max, float step);
private:
PWM_Output* pwmOutput;
OneButton* button;
- int min;
- int max;
+ float min;
+ float max;
float step;
float currentValue;
bool IsOn = false;
diff --git a/include/global.h b/include/global.h
index 67bfbff..f29adc0 100644
--- a/include/global.h
+++ b/include/global.h
@@ -4,16 +4,13 @@
#include
#include "appVersion.h"
-//#define FIRMWARE_VERSION "1.0.0"
-//#define FIRMWARE_VERSION_MAJOR 1
-//#define FIRMWARE_VERSION_MINOR 4
-//#define FIRMWARE_VERSION_PATCH 4
-
extern Version localVersion;
enum COMM_MODE { COMM_WIFI_AP_BLE, COMM_WIFI_AP_CLIENT };
extern enum COMM_MODE commMode;
+extern float boardTemperature ;
+
typedef struct{
int count;
TaskHandle_t handle[16];
@@ -36,20 +33,27 @@ extern CHIP_INFO chipInfo;
//#define ENABLE_SYSTEM_STATS 0
int findUnusedLedcChannel(void);
-//void read_system_settings(void);
-//void read_app_events_settings(void);
-//void read_animations_list(void);
+
void report_system_stats(void);
+
void print_chip_info(void);
+
void get_chip_mac(char* macStr, size_t size);
+
void print_ram_info(void);
-//void Pulse_LED_Status(int mSecs);
+
void printTaskInfo(void);
+
void printTaskCPUUsage(TaskHandle_t xTask);
+
void printTaskStackWatermark(TaskHandle_t xTask);
+
void addTaskHandleToList(SYS_TASK_HANDLES &list);
+
String getMacAddress(void);
+
String macToStr(uint8_t* mac);
+
bool updateJsonDocument(JsonDocument& doc, const char* filePath);
void Log_CPU_Load(void);
diff --git a/include/my_device.h b/include/my_device.h
new file mode 100644
index 0000000..a9ae4f9
--- /dev/null
+++ b/include/my_device.h
@@ -0,0 +1,18 @@
+#pragma once
+#include
+#include "system.h"
+#include "PWM_Output.h"
+#include "Ramp_Lights.h"
+
+
+extern SYS_SETTINGS sys_settings;
+extern PWM_Output *pwmOutputs[4];
+extern RAMP_LIGHT *rampLight1;
+extern RAMP_LIGHT *rampLight2;
+
+void Init_ADC(void);
+float readBoardInputVoltage(void);
+void Get_Board_and_Booth_File_Paths(const char *, String &, String &);
+void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath);
+void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]);
+void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]);
diff --git a/include/my_tsensor.h b/include/my_tsensor.h
index 995007e..84e7ae3 100644
--- a/include/my_tsensor.h
+++ b/include/my_tsensor.h
@@ -2,21 +2,12 @@
#include
#include "PWM_Output.h"
+#include "system.h"
-typedef struct{
- bool enabled;
- float temperature;
- float Setpoint1;
- float Setpoint2;
- float fanPower1;
- float fanPower2;
- float hyst;
-}T_SENSOR;
-
-extern T_SENSOR tSensorSettings;
+extern TSENSOR_SETTINGS *t_settings;
extern TI_TMP102_Compatible *tSensor;
-void Init_TSensor(uint8_t addr);
+void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings);
void UpdateFanControl(float temperature, PWM_Output* pwmOut);
diff --git a/src/ATALights.cpp b/src/ATALights.cpp
index 5f5c250..faeacde 100644
--- a/src/ATALights.cpp
+++ b/src/ATALights.cpp
@@ -10,6 +10,10 @@
#include "ColorPalettes.h"
#include "global.h"
//#include
+#include "PWM_Output.h"
+#include "my_device.h"
+#include "system.h"
+#include "my_device.h"
#define FASTLED_CORE 0
static const char* tag = "strips";
@@ -17,12 +21,15 @@ static const char* tag = "strips";
uint32_t whiteTimeout = 0;
TaskHandle_t Animation_Task_Handle;
+TaskHandle_t Ramp_Front_Light_Task_Handle;
LEDSTRIP_SETTINGS ledSettings[2];
volatile bool AnimationLooping = false;
ANIM_EVENT prevAnimEvent = {0};
QueueHandle_t animationQueue = xQueueCreate( 4, sizeof( ANIM_EVENT ) );
-void Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){
+bool fillAnimationActive = false;
+
+void RGB_Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){
// Trigger to exit curring looping animation
if(AnimationLooping){
@@ -39,65 +46,163 @@ void Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){
xQueueSend( animationQueue, &event, 25 );
}
-void Lights_Set_ON(){
- Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu);
+void RGB_Animations_ON(){
+ RGB_Lights_Set_Animation(prevAnimEvent.AnimationIndex, prevAnimEvent.data.red, prevAnimEvent.data.grn, prevAnimEvent.data.blu);
}
-void Lights_Set_OFF(){
- Lights_Set_Animation(OFF_INDEX, 0, 0, 0);
+void RGB_Animations_OFF(){
+ RGB_Lights_Set_Animation(OFF_INDEX, 0, 0, 0);
}
-void Lights_Set_Brightness(uint8_t scale){
+void RGB_Lights_Set_Brightness(uint8_t scale){
FastLED.setBrightness(scale);
}
+// Set the white LEDs brightness (0-255)
void Lights_Set_White(uint8_t val){
//pwmOut[0]->setOutput(val / 2.5f);
}
-void Init_Lights_Task(void){
-
+void Init_RGB_Lights_Task(void){
ledSettings[0].leds = new CRGB[ledSettings[0].size];
- Init_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);
ledSettings[1].leds = new CRGB[ledSettings[1].size];;
- Init_Strip(ledSettings[1].leds, ledSettings[1].pin, ledSettings[1].size, ledSettings[1].rgbOrder, ledSettings[1].chip, ledSettings[1].bright);
+ Init_RGB_Strip(ledSettings[1].leds, ledSettings[1].pin, ledSettings[1].size, ledSettings[1].rgbOrder, ledSettings[1].chip, ledSettings[1].bright);
ESP_LOGD(tag, "Initializing Strip2: Pin: %d, size: %d, order: %s, chip: %s", ledSettings[1].pin, ledSettings[1].size, ledSettings[1].rgbOrder, ledSettings[1].chip);
- xTaskCreatePinnedToCore(Lights_Control_Task, "Lights_Task", 1024*6, NULL, 1, &Animation_Task_Handle, FASTLED_CORE);
+ xTaskCreatePinnedToCore(RGB_Lights_Control_Task, "Lights_Task", 1024*6, NULL, 1, &Animation_Task_Handle, FASTLED_CORE);
ESP_LOGI(tag, "Lights Task Created...");
}
+
/*
-void Init_Ramp_Lights_Task(void){
-
- xTaskCreatePinnedToCore(Ramp_Lights_Control_Task, "Ramp_Lights_Task", 1024*1, NULL, 1, &Animation_Task_Handle, (FASTLED_CORE ? 1 : 0));
- ESP_LOGI(tag, "Ramp Lights Task Created...");
-}
-
-void RampUpLights(int level)
-{
-
-}
-
-void Ramp_Lights_Control_Task(void *parameters)
-{
- static *OutputPWM* pwmOut = NULL;
- pwmOut = pwmOut[sys_settings.rampLightSettings[0].pwmOutIndex];
-
- while(1){
- sys_settings.rampLightSettings[rampIndex].pwmOutIndex
- while()
-
- vTaskDelay(100 / portTICK_PERIOD_MS);
- vTaskSuspend(NULL);
+void Init_Ramp_Front_Light_Task(void){
+ // Check if pwmOutputs[0] is available before creating the task
+ if(pwmOutputs[0] == nullptr) {
+ ESP_LOGE(tag, "Cannot create Ramp Front Light Task: pwmOutputs[0] is not initialized");
+ return;
}
+ // Create task with moderate stack size - 2KB should be sufficient
+ BaseType_t result = xTaskCreatePinnedToCore(Ramp_Front_Light_Control_Task, "Ramp_Front_Light_Task", 1024*2, NULL, 1, &Ramp_Front_Light_Task_Handle, (FASTLED_CORE ? 1 : 0));
+
+ if(result != pdPASS) {
+ ESP_LOGE(tag, "Failed to create Ramp Front Light Task, error: %d", result);
+ Ramp_Front_Light_Task_Handle = NULL;
+ } else {
+ ESP_LOGI(tag, "Ramp Front Light Task Created...");
+ }
}
*/
+/*
+#define RAMP_INTERVAL 25
+#define FRONTLIGHT_TIMEOUT 20000
+int startDelay = 0;
+int rampTime = 0;
+bool rampRestart = false;
+float FrontLightMin = 0.0;
+
+void Ramp_Front_Light_Control_Task(void *parameters)
+{
+ float currPwmValue = 0.0;
+ float outValue = 0.0;
+ int timeCounter = 0;
+
+ while(1) {
+
+ if(rampRestart == false){
+ vTaskSuspend(NULL); // Wait to be triggered
+ }
+ rampRestart = false; // Clear the flag immediately
+
+ // Safety check: Ensure pwmOutputs[0] is initialized
+ if(pwmOutputs[0] == nullptr) {
+ ESP_LOGE(tag, "pwmOutputs[0] is null, cannot control front light");
+ rampRestart = false;
+ continue;
+ }
+
+ // Wait for start delay to finish
+ while(!rampRestart && fillAnimationActive && startDelay > 0){
+ vTaskDelay(10 / portTICK_PERIOD_MS);
+ startDelay -= 10;
+ }
+
+ if(rampRestart){
+ continue; // Restart if requested during delay
+ }
+
+ //currPwmValue = pwmOutputs[0]->getDuty(); // Get current PWM value
+ currPwmValue = FrontLightMin; // Always start from min value
+ pwmOutputs[0]->setOutput(currPwmValue); // Ensure starting point is set
+
+ // Ramp Up the Front Light linearly
+ timeCounter = 0; // Reset timer for ramp up
+ int rampMs = rampTime;
+ while(!rampRestart && fillAnimationActive && rampMs > 0){
+ timeCounter += RAMP_INTERVAL;
+ outValue = map(timeCounter, 0, rampTime, currPwmValue, 100);
+ pwmOutputs[0]->setOutput(outValue);
+ vTaskDelay(RAMP_INTERVAL / portTICK_PERIOD_MS);
+ rampMs -= RAMP_INTERVAL;
+ }
+
+ if(rampRestart){
+ continue; // Restart the ramp up if requested
+ }
+
+ // Max Wait fill animation to end or timeout
+ int frontLightTimeout = FRONTLIGHT_TIMEOUT;
+ while(!rampRestart && fillAnimationActive && frontLightTimeout > 0){
+ vTaskDelay(10 / portTICK_PERIOD_MS);
+ frontLightTimeout -= 10;
+ }
+
+ if(rampRestart){
+ pwmOutputs[0]->setOutput(0); // Ensure it's fully off
+ continue; // Restart the ramp up if requested
+ }
+
+ // Ramp Down the Front Light linearly
+ timeCounter = 0; // Reset timer for ramp down
+ rampMs = rampTime;
+ while(!rampRestart && fillAnimationActive && rampMs > 0){
+ timeCounter += RAMP_INTERVAL;
+ outValue = map(timeCounter, 0, rampTime, 100 , currPwmValue);
+ pwmOutputs[0]->setOutput(outValue);
+ vTaskDelay(RAMP_INTERVAL / portTICK_PERIOD_MS);
+ rampMs -= RAMP_INTERVAL;
+ }
+
+ // Cycle completed - ensure clean final state
+ if(!rampRestart){
+ pwmOutputs[0]->setOutput(FrontLightMin); // Ensure final state is minimum
+ fillAnimationActive = false; // Clear animation flag
+ }
+ }
+}
+
+
+void Start_RampUp_Front_Light(int _rampTime, int _startDelay)
+{
+ // Check if the task exists before trying to control it
+ if(Ramp_Front_Light_Task_Handle == NULL) {
+ ESP_LOGW(tag, "Cannot start front light ramp: task not created");
+ return;
+ }
+
+ rampTime = _rampTime;
+ startDelay = _startDelay;
+ fillAnimationActive = true;
+ rampRestart = true;
+ vTaskResume(Ramp_Front_Light_Task_Handle);
+}
+*/
+
void Animation_Loop_Exit(void){
if( Animation_Task_Handle ){
xTaskNotifyGive( Animation_Task_Handle );
@@ -117,17 +222,6 @@ EOrder GetEOrderByString(const String& rgbStr){
}
-void LightsON(void){
- FastLED.show();
-}
-
-
-void LightsOff(void){
- FastLED.clear();
- FastLED.show();
-}
-
-
/*
void setPixel(LEDSTRIP_SETTINGS& strip, int pixelIndex, CRGB col){
int x = calcPixelIndex(strip, pixelIndex);
@@ -169,7 +263,7 @@ inline void setPixel2(int pixelIndex, const CRGB col) {
}
-void Init_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) {
// Validate inputs
if (!leds || size <= 0) {
ESP_LOGE(tag, "Invalid LED array or size");
@@ -354,25 +448,18 @@ void Init_Strip(CRGB* leds, uint8_t pin, int size, const String& colorOrder, con
}
-void Lights_Control_Task_Resume(void){
- vTaskResume(Animation_Task_Handle);
-}
-
-
-void Lights_Control_Task(void *parameters){
+void RGB_Lights_Control_Task(void *parameters){
ANIM_EVENT AnimEvent;
CRGB col;
COLOR_PACK colorPack;
CRGBPalette16 firePalette;
- ESP_LOGD(tag, "Lights Control Task Entered & Suspended...");
- vTaskSuspend(NULL);
- ESP_LOGD(tag, "Lights Control Task Resumed...");
- vTaskDelay(1000);
+ // Wait for other tasks to initialize (including front light task)
+ vTaskDelay(pdMS_TO_TICKS(500)); // wait for everything to settle
- Lights_Set_Brightness(48);
- Lights_Set_Animation(1, 0, 0, 0); // set rainbow animation
+ RGB_Lights_Set_Brightness(48);
+ RGB_Lights_Set_Animation(23, 0, 0, 0); // set comet rainbow animation
while (true) {
@@ -399,47 +486,97 @@ void Lights_Control_Task(void *parameters){
ESP_LOGD(tag, "LEDs Off");
break;
case 0:
+ fill_solid(ledSettings[0].leds, ledSettings[0].size, CRGB::Black);
+ FastLED.show();
break;
- case 1:
- Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 30);
- break;
- case 2: case 3: case 4: { // Timed Fill Animations
- int timeDuration = (AnimEvent.AnimationIndex-1) * 1000;
- Anim_TimedFill(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, CRGB::Black, CRGB::White, timeDuration, ledSettings[0].shift);
- whiteTimeout = 20;
+ 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 5: case 6: case 7: case 8: {// Fire Animations
- COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 5]; // copy const pack to mutable
+ 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);
+ }
+ break;
+ case 7:
+ Anim_Rainbow(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60);
+ break;
+ case 8: case 9: case 10: case 11: case 12:
+ Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, gradient_colorPack[AnimEvent.AnimationIndex - 8], 80);
+ break;
+ case 13: case 14: case 15: case 16: case 17: { // Fire Animations
+ COLOR_PACK fp = fireColorPacks[AnimEvent.AnimationIndex - 13]; // copy const pack to mutable
createFirePalette(firePalette, fp);
Anim_Fire(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, 60, firePalette, ledSettings[0].shift);
break;
}
- case 9: case 10: case 11: case 12: case 13: {//
- COLOR_PACK ccp = combo_colorPacks[AnimEvent.AnimationIndex - 9]; // copy const pack to mutable
- //loadColorPack(colorPack, colorPack_USA);
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, ccp, 1, 80);
+ case 18: case 19: case 20:// Sparkle/Twinkle
+ Anim_Sparkle(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, tripple_colorPacks[0], 40, 20);
break;
- }
- case 14: case 15: case 16: case 17: case 18: {
- COLOR_PACK dcp = dashes_ColorPacks[AnimEvent.AnimationIndex - 14]; // copy const pack to mutable
- //loadColorPack(colorPack, colorPack_RedBlack);
- Anim_Color_Sectors(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, dcp, 4, 80);
+ 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 19: case 20: case 21: case 22: case 23: {
- int idx = AnimEvent.AnimationIndex - 19;
- Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, combo_colorPacks[idx], 80, RANDOM_DECAY, true, 1);
+ case 22: // Rain Animation
break;
- }
- case 24:
- loadColorPack(colorPack, colorPack_RAINBOW);
- Anim_ColorBreath(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 7000, 90);
+
+
+ // Comets
+ case 23:
+ Anim_Comets(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack_RAINBOW, 40, 85, RANDOM_DECAY, 1);
break;
- case 25:
- loadColorPack(colorPack, colorPack_USA);
- Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 50);
+ 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;
diff --git a/src/Animations.cpp b/src/Animations.cpp
index 01d2157..430ac81 100644
--- a/src/Animations.cpp
+++ b/src/Animations.cpp
@@ -10,7 +10,9 @@
#include "esp_log.h"
#include
#include "PWM_Output.h"
+#include "esp_log.h"
+static const char* tag = "anims";
typedef struct{
float minSpeed;
@@ -64,6 +66,46 @@ void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function callback) {
+ if (!callback) {
+ ESP_LOGE("Animation_Loop", "Invalid callback function");
+ return;
+ }
+
+ loop_active_flag = true;
+
+ ulTaskNotifyTake(pdTRUE, 0); // Clear any pending notifications
+
+ TickType_t xLastWakeTime;
+ TickType_t elapsedTicks;
+ TickType_t delayTicks;
+ while(loop_active_flag) {
+ xLastWakeTime = xTaskGetTickCount();
+
+ int loopDelay = 0;
+ try {
+ loopDelay = callback(); // Call animation function and get delay value
+ } catch (const std::exception& e) {
+ ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what());
+ break;
+ }
+
+ if(!loop_active_flag) return;
+
+ // Ensure minimum delay protection
+ loopDelay = max(loopDelay, MinLoopDelay);
+
+ // Calculate remaining time with overflow protection
+ elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
+ delayTicks = (elapsedTicks < loopDelay) ? (loopDelay - elapsedTicks) : 0;
+
+ // Check for termination request
+ if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; }
+ }
+
+ loop_active_flag = false;
+}
+
// Animation Loop Template
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function callback) {
loop_active_flag = true;
@@ -195,8 +237,8 @@ void Anim_Rainbow(bool volatile& activeFlag, CRGB* leds, int size, int speed){
// Fire parameters (adjustable)
const uint8_t FIRE_COOLING = 66;
-const uint8_t FIRE_SPARKING = 62;
-const uint8_t FIRE_brightness = 255;
+const uint8_t FIRE_SPARKING = 55;
+const uint8_t FIRE_brightness = 240;
void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const CRGBPalette16& firePalette, int shift = 0) {
if (!leds || size <= 0) return;
@@ -252,31 +294,60 @@ void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const
}
-void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, uint8_t numRepeats, int speed) {
+void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, bool gaps, uint8_t numRepeats, int minSpeed, int maxSpeed) {
// Validate inputs
if (size <= 0 || numRepeats <= 0 || colorPack.size <= 0) return;
+ // Calculate effective colors per repeat (original colors + gaps if enabled)
+ int effectiveColorsPerRepeat = gaps ? (colorPack.size * 2) : colorPack.size;
+
// Calculate sector size
- int sectorSize = size / (colorPack.size * numRepeats);
+ int sectorSize = size / (effectiveColorsPerRepeat * numRepeats);
if (sectorSize < 1) sectorSize = 1;
// Initialize pattern
for (int repeat = 0; repeat < numRepeats; repeat++) {
for (int color = 0; color < colorPack.size; color++) {
- int startIdx = (repeat * colorPack.size + color) * sectorSize;
- int endIdx = startIdx + sectorSize;
- if (endIdx > size) endIdx = size;
-
- // Fill sector with the specified color
- fill_solid(&leds[startIdx], endIdx - startIdx, colorPack.col[color]);
+ if (gaps) {
+ // With gaps: each color takes position color*2, gap takes color*2+1
+ int colorStartIdx = (repeat * effectiveColorsPerRepeat + color * 2) * sectorSize;
+ int colorEndIdx = colorStartIdx + sectorSize;
+ if (colorEndIdx > size) colorEndIdx = size;
+
+ // Fill sector with the specified color
+ if (colorStartIdx < size) {
+ fill_solid(&leds[colorStartIdx], colorEndIdx - colorStartIdx, colorPack.col[color]);
+ }
+
+ // Add black gap after the color
+ int gapStartIdx = (repeat * effectiveColorsPerRepeat + color * 2 + 1) * sectorSize;
+ int gapEndIdx = gapStartIdx + sectorSize;
+ if (gapEndIdx > size) gapEndIdx = size;
+
+ if (gapStartIdx < size) {
+ fill_solid(&leds[gapStartIdx], gapEndIdx - gapStartIdx, CRGB::Black);
+ }
+ } else {
+ // Without gaps: original behavior
+ int startIdx = (repeat * colorPack.size + color) * sectorSize;
+ int endIdx = startIdx + sectorSize;
+ if (endIdx > size) endIdx = size;
+
+ // Fill sector with the specified color
+ fill_solid(&leds[startIdx], endIdx - startIdx, colorPack.col[color]);
+ }
}
}
// Animate rotation
bool direction = true; // true = forward, false = backward
int loopCounter = 0;
+ int speed = minSpeed;
+ int loopDuration = (size * CYCLES_PER_DIRECTION);
+ int thirdLoop = loopDuration / 3;
+ int twoThirdLoop = (2 * loopDuration) / 3;
CRGB temp;
- Animation_Loop(activeFlag, speed, [&]() -> int {
+ Animation_Loop_Variable(activeFlag, [&]() -> int {
if (direction) {
temp = leds[0];
@@ -288,15 +359,32 @@ void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const C
leds[0] = temp;
}
- // Direction Switching Logic
- loopCounter++;
- if(loopCounter >= (size * CYCLES_PER_DIRECTION)){
+ // Speed ramping: 1/3 aggressive ramp up, 1/3 constant, 1/3 aggressive ramp down
+ if (loopCounter < thirdLoop) {
+ // First third: aggressive ease-out acceleration (very fast start, sharp slowdown)
+ float progress = (float)loopCounter / (float)thirdLoop; // 0.0 to 1.0
+ float eased = 1.0f - pow(1.0f - progress, 4.0f); // ease-out quartic (power of 4)
+ speed = minSpeed + (int)((maxSpeed - minSpeed) * eased);
+ }
+ else if (loopCounter < twoThirdLoop) {
+ // Middle third: constant at maxSpeed
+ speed = maxSpeed;
+ }
+ else {
+ // Last third: aggressive ease-in deceleration (sharp start, fast finish)
+ float progress = (float)(loopCounter - twoThirdLoop) / (float)thirdLoop; // 0.0 to 1.0
+ float eased = pow(progress, 4.0f); // ease-in quartic (power of 4)
+ speed = maxSpeed - (int)((maxSpeed - minSpeed) * eased);
+ }
+
+ // Direction switching logic (single increment)
+ if (++loopCounter >= loopDuration) {
direction = !direction;
loopCounter = 0;
}
FastLED.show();
- return 0;
+ return max(MaxSpeed - speed, MinLoopDelay);
});
}
@@ -306,10 +394,10 @@ void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const C
* Comets Animation
*******************************************************************************/
#define COMET_SIZE_FACTOR 0.2
-#define COMET_FADE_FACTOR1 64 /* longer tail */
+#define COMET_FADE_FACTOR1 32 /* longer tail */
#define COMET_FADE_FACTOR2 192 /* shorter tail */
-#define COMET_FADE_FACTOR COMET_FADE_FACTOR2
-#define MAX_COMETS 16 // Maximum number of comets supported
+//#define COMET_FADE_FACTOR COMET_FADE_FACTOR2
+#define MAX_COMETS 8 // Maximum number of comets supported
/*
void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) {
// Validate inputs
@@ -336,7 +424,8 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
bool direction = true; // true = forward, false = backward
int loopCounter = 0;
try {
- Animation_Loop(activeFlag, speed, [&]() -> int {
+ 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) {
@@ -380,8 +469,7 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
}
*/
-
-void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) {
+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;
@@ -396,7 +484,8 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
if (cometSize < 1) cometSize = 1;
// Set fade factor
- uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1;
+ 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};
@@ -410,8 +499,12 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
int loopCounter = 0;
int pos;
CRGB color;
+ int speed = minSpeed;
+ int loopDuration = (size * CYCLES_PER_DIRECTION);
+ int thirdLoop = loopDuration / 3;
+ int twoThirdLoop = (2 * loopDuration) / 3;
try {
- Animation_Loop(activeFlag, speed, [&]() -> int {
+ Animation_Loop_Variable(activeFlag, [&]() -> int {
// Fade all LEDs
for (int i = 0; i < size; i++) {
if (!randomDecay) {
@@ -421,7 +514,7 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
}
}
- // Move and draw comets
+ // Move and draw comets
for (int i = 0; i < totalComets; i++) {
if (direction) {
cometPositions[i] = (cometPositions[i] + 1) % size;
@@ -432,22 +525,44 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
// Draw comet with solid color
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 % size + size) % size; // safe modulus
+ // Tail follows the direction of movement
+ pos = direction ? (cometPositions[i] - j) : (cometPositions[i] + j);
+ pos = (pos % size + size) % size; // safe modulus
leds[pos] += color;
}
}
- // Direction change
- if (++loopCounter >= (size * CYCLES_PER_DIRECTION)) {
+ // Speed ramping: 1/3 aggressive ramp up, 1/3 constant, 1/3 aggressive ramp down
+ if (loopCounter < thirdLoop) {
+ // First third: aggressive ease-out acceleration (very fast start, sharp slowdown)
+ float progress = (float)loopCounter / (float)thirdLoop; // 0.0 to 1.0
+ float eased = 1.0f - pow(1.0f - progress, 4.0f); // ease-out quartic (power of 4)
+ speed = minSpeed + (int)((maxSpeed - minSpeed) * eased);
+ }
+ else if (loopCounter < twoThirdLoop) {
+ // Middle third: constant at maxSpeed
+ speed = maxSpeed;
+ }
+ else {
+ // Last third: aggressive ease-in deceleration (sharp start, fast finish)
+ float progress = (float)(loopCounter - twoThirdLoop) / (float)thirdLoop; // 0.0 to 1.0
+ float eased = pow(progress, 4.0f); // ease-in quartic (power of 4)
+ speed = maxSpeed - (int)((maxSpeed - minSpeed) * eased);
+ }
+
+ if (++loopCounter >= loopDuration) {
direction = !direction;
loopCounter = 0;
+ speed = minSpeed;
}
FastLED.show();
- return 0;
+
+ return max(MaxSpeed - speed, MinLoopDelay);
});
+
+ fill_solid(leds, size, CRGB::Black);
+
} catch (const std::exception& e) {
ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what());
} catch (...) {
@@ -455,107 +570,195 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
}
}
-/*
-void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0, PWM_Output* pwmOutput = nullptr) {
+
+void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0)
+{
if (!leds || size <= 1 || totalDurationMs <= 0) return;
const int halfSize = size / 2;
const float msPerLed = totalDurationMs / (float)halfSize;
unsigned long startTime = millis();
- // Define the point at which PWM begins ramping (75% of fill time)
- const float pwmStartPoint = 0.75f;
- const int pwmStartLeds = halfSize * pwmStartPoint;
+ fill_solid(leds, size, baseCol);
+
+ int prevLedsToLight = 0;
+ unsigned long currentTime;
+ unsigned long elapsedTime;
+ int ledsToLight, pos;
+ int loopInterval = 90;
+ Animation_Loop(activeFlag, loopInterval, [&]() -> int {
+ currentTime = millis();
+ elapsedTime = currentTime - startTime;
+
+ // return 0 to loop infinitely
+ if(elapsedTime > (totalDurationMs + loopInterval)) return 0;
+
+ // Calculate how many LEDs should be lit based on elapsed time
+ ledsToLight = (elapsedTime / msPerLed);
+ if (ledsToLight > halfSize) ledsToLight = halfSize;
+
+ // Fill LEDs up to current position
+ for (int i = 0; i < ledsToLight; i++) {
+ pos = (i + shift + size) % size;
+ leds[pos] = fillCol;
+ leds[(size - 1 - i + shift + size) % size] = fillCol; // Correct mirroring calculation
+ }
+
+ // Update LEDs only when necessary
+ if(prevLedsToLight < ledsToLight){
+ FastLED.show();
+ }
+ prevLedsToLight = ledsToLight;
+
+ return 0; // Always return 0 to continue looping
+ });
+
+
+}
+
+
+void Anim_TimedFill_Flash(bool volatile& activeFlag, CRGB* leds, int size, PWM_Output* pwmOut, PWM_OUT_SETTINGS* pwmSettings, int pwmOutDelay, int flashTimeout, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift)
+{
+ if (!leds || size <= 1 || totalDurationMs <= 0 || !pwmOut || !pwmSettings) return;
+
+ const int halfSize = size / 2;
+ const float msPerLed = totalDurationMs / (float)halfSize;
+ unsigned long startTime = millis();
+ unsigned long flashStartTime = 0;
+ bool flashTimeoutStarted = false;
+ bool pwmStarted = false;
+
+ // Calculate PWM parameters
+ const int pwmRampDuration = totalDurationMs - pwmOutDelay;
+ const float pwmMin = pwmSettings->min;
+ const float pwmMax = pwmSettings->max;
+ const float pwmRange = pwmMax - pwmMin;
+
+ fill_solid(leds, size, baseCol);
- // Initialize PWM output to 0 if provided
- if (pwmOutput) {
- pwmOutput->setOutput(0.0f);
+ // Set initial PWM to minimum
+ pwmOut->setOutput(pwmMin);
+
+ int prevLedsToLight = 0;
+ unsigned long currentTime;
+ unsigned long elapsedTime;
+ int ledsToLight, pos;
+ float pwmValue;
+ int loopInterval = 90;
+
+ Animation_Loop(activeFlag, loopInterval, [&]() -> int {
+ currentTime = millis();
+ elapsedTime = currentTime - startTime;
+
+ // Phase 1: Fill animation with synchronized PWM ramp
+ if (elapsedTime <= (totalDurationMs + loopInterval)) {
+ // Calculate how many LEDs should be lit based on elapsed time
+ ledsToLight = (elapsedTime / msPerLed);
+ if (ledsToLight > halfSize) ledsToLight = halfSize;
+
+ // Fill LEDs up to current position
+ for (int i = 0; i < ledsToLight; i++) {
+ pos = (i + shift + size) % size;
+ leds[pos] = fillCol;
+ leds[(size - 1 - i + shift + size) % size] = fillCol; // Correct mirroring calculation
+ }
+
+ // Update LEDs only when necessary
+ if(prevLedsToLight < ledsToLight){
+ FastLED.show();
+ }
+ prevLedsToLight = ledsToLight;
+
+ // PWM control: start ramping after pwmOutDelay
+ if (elapsedTime >= pwmOutDelay && !pwmStarted) {
+ pwmStarted = true;
+ ESP_LOGI("Anim_TimedFill_Flash", "Starting PWM ramp from %.1f to %.1f over %dms", pwmMin, pwmMax, pwmRampDuration);
+ }
+
+ if (pwmStarted && pwmRampDuration > 0) {
+ // Linear interpolation from min to max over remaining time
+ int pwmElapsed = elapsedTime - pwmOutDelay;
+ if (pwmElapsed < 0) pwmElapsed = 0;
+ if (pwmElapsed > pwmRampDuration) pwmElapsed = pwmRampDuration;
+
+ float progress = (float)pwmElapsed / (float)pwmRampDuration;
+ pwmValue = pwmMin + (progress * pwmRange);
+ pwmOut->setOutput(pwmValue);
+ }
+ }
+ // Phase 2: Animation complete, start flash timeout
+ else if (!flashTimeoutStarted) {
+ flashTimeoutStarted = true;
+ flashStartTime = currentTime;
+ ESP_LOGI("Anim_TimedFill_Flash", "Fill complete, starting flash timeout of %dms", flashTimeout);
+ }
+ // Phase 3: Flash timeout period
+ else {
+ unsigned long flashElapsed = currentTime - flashStartTime;
+ if (flashElapsed >= flashTimeout) {
+ // Flash timeout expired, set PWM to minimum
+ pwmOut->setOutput(pwmMin);
+ ESP_LOGI("Anim_TimedFill_Flash", "Flash timeout expired, PWM set to minimum");
+ }
+ // Continue in infinite loop regardless of timeout status
+ }
+
+ return 0; // Always return 0 to continue looping infinitely
+ });
+
+ pwmOut->setOutput(pwmMin);
+}
+
+
+void Anim_SolidWhite(bool volatile& activeFlag, CRGB* leds, PWM_Output* pwmOut, int size, int brightness, int timeoutMs) {
+ if (!leds || size <= 0 || !pwmOut) return;
+
+ const uint8_t origBright = FastLED.getBrightness();
+ const uint8_t fullBrightness = brightness;
+ const uint8_t reducedBrightness = brightness / 2; // 50% brightness
+ const float reducedPwmOutput = 50.0f; // 50% PWM output
+
+ unsigned long startTime = millis();
+ bool timeoutReached = false;
+ bool brightnessReduced = false;
+
+ // Set initial full brightness
+ FastLED.setBrightness(fullBrightness);
+ pwmOut->setOutput(100);
+
+ fill_solid(leds, size, CRGB::White);
+ FastLED.show();
+
+ Animation_Loop(activeFlag, 50, [&]() -> int {
+ // Check if timeout is specified and has been reached
+ if (timeoutMs > 0 && !timeoutReached) {
+ unsigned long currentTime = millis();
+ unsigned long elapsedTime = currentTime - startTime;
+
+ if (elapsedTime >= timeoutMs) {
+ timeoutReached = true;
+
+ // Reduce brightness to 50% for both RGB and PWM
+ if (!brightnessReduced) {
+ if(pwmOut){
+ pwmOut->setOutput(reducedPwmOutput);
+ }
+ FastLED.setBrightness(reducedBrightness);
+ FastLED.show(); // Update display with new brightness
+ brightnessReduced = true;
+ //ESP_LOGI("Anim_SolidWhite", "Timeout reached - brightness reduced to 50%%");
+ }
+ }
+ }
+
+ // No animation, just maintain the solid white state
+ return 0;
+ });
+ if(pwmOut){
+ pwmOut->setOutput(0); // Turn off PWM output
}
-
- fill_solid(leds, size, baseCol);
-
- int prevLedsToLight = 0;
- unsigned long currentTime;
- unsigned long elapsedTime;
- int ledsToLight, pos;
- Animation_Loop(activeFlag, 90, [&]() -> int {
- currentTime = millis();
- elapsedTime = currentTime - startTime;
-
- // Calculate how many LEDs should be lit based on elapsed time
- ledsToLight = (elapsedTime / msPerLed);
- if (ledsToLight > halfSize) ledsToLight = halfSize;
-
- // Fill LEDs up to current position
- for (int i = 0; i < ledsToLight; i++) {
- pos = (i + shift + size) % size;
- leds[pos] = fillCol;
- leds[(size - 1 - i + shift + size) % size] = fillCol; // Correct mirroring calculation
- }
-
- // Handle PWM output ramp starting at 75% of fill time
- if (pwmOutput && ledsToLight >= pwmStartLeds) {
- // Calculate PWM value as percentage of remaining fill time
- // Map from [pwmStartLeds, halfSize] to [0, 100]
- float pwmValue = map(ledsToLight, pwmStartLeds, halfSize, 0, 100);
- // Ensure pwmValue is in range [0, 100]
- pwmValue = constrain(pwmValue, 0.0f, 100.0f);
- // Set the PWM output
- pwmOutput->setOutput(pwmValue);
- }
-
- // Update LEDs only when necessary
- if(prevLedsToLight < ledsToLight){
- FastLED.show();
- }
- prevLedsToLight = ledsToLight;
-
- // Return 1 when complete
- return (ledsToLight >= halfSize) ? 1 : 0;
- });
+ FastLED.setBrightness(origBright); // Restore original brightness
}
-*/
-
-
-void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0) {
- if (!leds || size <= 1 || totalDurationMs <= 0) return;
-
- const int halfSize = size / 2;
- const float msPerLed = totalDurationMs / (float)halfSize;
- unsigned long startTime = millis();
-
- fill_solid(leds, size, baseCol);
-
- int prevLedsToLight = 0;
- unsigned long currentTime;
- unsigned long elapsedTime;
- int ledsToLight, pos;
- Animation_Loop(activeFlag, 90, [&]() -> int {
- currentTime = millis();
- elapsedTime = currentTime - startTime;
-
- // Calculate how many LEDs should be lit based on elapsed time
- ledsToLight = (elapsedTime / msPerLed);
- if (ledsToLight > halfSize) ledsToLight = halfSize;
-
- // Fill LEDs up to current position
- for (int i = 0; i < ledsToLight; i++) {
- pos = (i + shift + size) % size;
- leds[pos] = fillCol;
- leds[(size - 1 - i + shift + size) % size] = fillCol; // Correct mirroring calculation
- }
-
- // Update LEDs only when necessary
- if(prevLedsToLight < ledsToLight){
- FastLED.show();
- }
- prevLedsToLight = ledsToLight;
-
- // Return 1 when complete
- return (ledsToLight >= halfSize) ? 1 : 0;
- });
-}
-
#define MIN_BRIGHTNESS 2
void Anim_ColorBreath(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, uint32_t timeMs, int speed) {
@@ -657,6 +860,7 @@ void Anim_ColorWipe(bool volatile& activeFlag, CRGB* leds, int size, const COLOR
});
}
+
void Anim_Sparkle(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, int speed, uint8_t sparkleChance) {
if (!leds || size <= 0 || colors.size <= 0) return;
@@ -675,6 +879,7 @@ void Anim_Sparkle(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_P
});
}
+
void Anim_TheaterChase(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, int speed, uint8_t spacing) {
if (!leds || size <= 0 || colors.size <= 0 || spacing == 0) return;
@@ -697,6 +902,350 @@ void Anim_TheaterChase(bool volatile& activeFlag, CRGB* leds, int size, const CO
}
+#define SNAKE_CYCLES_PER_ROTATION 4
+void Anim_Snakes(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed , int multiplier) {
+ if (!leds || size <= 0 || colorPack.size <= 0 || multiplier <= 0) return;
+
+ // Determine number of snakes (colors * multiplier), cap to a safe maximum
+ const int USER_NUM = colorPack.size * multiplier;
+ const int MAX_SNAKES = 32;
+ int numSnakes = USER_NUM;
+ if (numSnakes > MAX_SNAKES) numSnakes = MAX_SNAKES;
+ if (numSnakes <= 0) return;
+
+ // Snake parameters: compute per-snake max length based on total snakes
+ const int maxSnakeLengthPerSection = size / numSnakes; // Full space available per snake
+ if (maxSnakeLengthPerSection < 2) return; // Need at least 2 pixels per snake section
+
+ enum SnakeState : uint8_t { GROWING = 0, FILLING = 1, SHRINKING = 2 };
+
+ struct Snake {
+ int head;
+ int length;
+ int maxLength;
+ int startPos;
+ int endPos;
+ bool forward;
+ CRGB color;
+ SnakeState state;
+ };
+
+ // Allocate snakes and per-snake cycle flags
+ Snake* snakes = new (std::nothrow) Snake[numSnakes];
+ if (!snakes) return;
+ bool* cycleDone = new (std::nothrow) bool[numSnakes];
+ if (!cycleDone) { delete[] snakes; return; }
+ for (int i = 0; i < numSnakes; i++) cycleDone[i] = false;
+
+ // Initialize
+ bool globalDirection = true; // rotation direction and snake forward when cycle resets
+ int completedCycles = 0;
+ int rotationOffset = 0; // total rotation applied to whole-array
+
+ for (int i = 0; i < numSnakes; i++) {
+ snakes[i].startPos = (i * size) / numSnakes;
+ snakes[i].endPos = ((i + 1) * size) / numSnakes - 1;
+ snakes[i].head = snakes[i].startPos;
+ snakes[i].length = 1;
+ snakes[i].maxLength = maxSnakeLengthPerSection;
+ snakes[i].forward = globalDirection;
+ snakes[i].color = colorPack.col[i % colorPack.size];
+ snakes[i].state = GROWING;
+ }
+
+ // Temporary buffer to draw snakes before rotating whole array
+ CRGB* buf = new (std::nothrow) CRGB[size];
+ if (!buf) { delete[] snakes; delete[] cycleDone; return; }
+
+ Animation_Loop(activeFlag, speed, [&]() -> int {
+ // Clear drawing buffer
+ fill_solid(buf, size, CRGB::Black);
+
+ // Update snake states
+ bool anyGrowing = false;
+ for (int s = 0; s < numSnakes; s++) {
+ Snake& sn = snakes[s];
+
+ switch (sn.state) {
+ case GROWING:
+ // Move head toward wall and extend
+ if (sn.forward) {
+ if (sn.head < sn.endPos) { sn.head++; sn.length++; }
+ else { sn.state = FILLING; }
+ } else {
+ if (sn.head > sn.startPos) { sn.head--; sn.length++; }
+ else { sn.state = FILLING; }
+ }
+ if (sn.length > sn.maxLength) sn.length = sn.maxLength;
+ break;
+
+ case FILLING:
+ if (sn.length < sn.maxLength) { sn.length++; }
+ else { sn.state = SHRINKING; }
+ break;
+
+ case SHRINKING:
+ if (sn.length > 1) { sn.length--; }
+ else {
+ // Completed one grow+shrink cycle for this snake
+ sn.forward = globalDirection; // align to current global direction
+ sn.state = GROWING;
+ sn.length = 1;
+ cycleDone[s] = true;
+ }
+ break;
+ }
+
+ if (sn.state == GROWING || sn.state == FILLING) anyGrowing = true;
+ }
+
+ // If all snakes signaled completion, count a completed cycle and reset flags
+ bool allDone = true;
+ for (int s = 0; s < numSnakes; s++) { if (!cycleDone[s]) { allDone = false; break; } }
+ if (allDone) {
+ completedCycles++;
+ for (int s = 0; s < numSnakes; s++) cycleDone[s] = false;
+ }
+
+ // After required cycles, flip global direction
+ if (completedCycles >= SNAKE_CYCLES_PER_ROTATION) {
+ globalDirection = !globalDirection;
+ completedCycles = 0;
+ // apply new direction to snakes so their next grow starts in that direction
+ for (int s = 0; s < numSnakes; s++) snakes[s].forward = globalDirection;
+ }
+
+ // Rotation speed: 2x while any snake is growing/filling, 1x otherwise
+ int rotationSpeed = anyGrowing ? 2 : 1;
+ if (globalDirection) rotationOffset = (rotationOffset + rotationSpeed) % size;
+ else rotationOffset = (rotationOffset - rotationSpeed + size) % size;
+
+ // Draw snakes into buffer (no rotation applied here)
+ for (int s = 0; s < numSnakes; s++) {
+ Snake& sn = snakes[s];
+ for (int i = 0; i < sn.length; i++) {
+ int pos = sn.forward ? (sn.head - i) : (sn.head + i);
+ if (pos < sn.startPos || pos > sn.endPos) continue;
+ if (pos < 0 || pos >= size) continue;
+
+ float brightness = 1.0f - (float(i) / float(sn.maxLength)) * 0.7f;
+ uint8_t scale = static_cast(constrain((int)(brightness * 255.0f), 0, 255));
+ CRGB pixel = sn.color;
+ pixel.nscale8_video(scale);
+
+ if (buf[pos] == CRGB::Black) buf[pos] = pixel; else buf[pos] += pixel;
+ }
+ }
+
+ // Rotate the whole buffer into the real LED array using rotationOffset
+ for (int i = 0; i < size; i++) {
+ int dst = (i + rotationOffset) % size;
+ leds[dst] = buf[i];
+ }
+
+ FastLED.show();
+ return 0;
+ });
+
+ delete[] buf;
+ delete[] snakes;
+ delete[] cycleDone;
+}
+
+#define BIRD_CYCLES_PER_ROTATION 4
+void Anim_Birds(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed , int multiplier) {
+ if (!leds || size <= 0 || colorPack.size <= 0 || multiplier <= 0) return;
+
+ // Determine number of snakes (colors * multiplier), cap to a safe maximum
+ const int USER_NUM = colorPack.size * multiplier;
+ const int MAX_SNAKES = 32;
+ int numSnakes = USER_NUM;
+ if (numSnakes > MAX_SNAKES) numSnakes = MAX_SNAKES;
+ if (numSnakes <= 0) return;
+
+ // Snake parameters: compute per-snake max length based on total snakes
+ const int maxSnakeLengthPerSection = size / numSnakes; // Full space available per snake
+ if (maxSnakeLengthPerSection < 2) return; // Need at least 2 pixels per snake section
+
+ enum SnakeState : uint8_t { GROWING = 0, FILLING = 1, SHRINKING = 2 };
+
+ struct Snake {
+ int head;
+ int length;
+ int maxLength;
+ int startPos;
+ int endPos;
+ bool forward;
+ CRGB color;
+ SnakeState state;
+ };
+
+ // Allocate snakes and per-snake cycle flags
+ Snake* snakes = new (std::nothrow) Snake[numSnakes];
+ if (!snakes) return;
+ bool* cycleDone = new (std::nothrow) bool[numSnakes];
+ if (!cycleDone) { delete[] snakes; return; }
+ for (int i = 0; i < numSnakes; i++) cycleDone[i] = false;
+
+ // Initialize
+ bool globalDirection = true; // rotation direction and snake forward when cycle resets
+ int completedCycles = 0;
+ int rotationOffset = 0; // total rotation applied to whole-array
+
+ for (int i = 0; i < numSnakes; i++) {
+ snakes[i].startPos = (i * size) / numSnakes;
+ snakes[i].endPos = ((i + 1) * size) / numSnakes - 1;
+ snakes[i].head = snakes[i].startPos;
+ snakes[i].length = 1;
+ snakes[i].maxLength = maxSnakeLengthPerSection;
+ snakes[i].forward = globalDirection;
+ snakes[i].color = colorPack.col[i % colorPack.size];
+ snakes[i].state = GROWING;
+ }
+
+ // Temporary buffer to draw snakes before rotating whole array
+ CRGB* buf = new (std::nothrow) CRGB[size];
+ if (!buf) { delete[] snakes; delete[] cycleDone; return; }
+
+ Animation_Loop(activeFlag, speed, [&]() -> int {
+ // Clear drawing buffer
+ fill_solid(buf, size, CRGB::Black);
+
+ // Update snake states
+ bool anyGrowing = false;
+ for (int s = 0; s < numSnakes; s++) {
+ Snake& sn = snakes[s];
+
+ switch (sn.state) {
+ case GROWING:
+ // Move head toward wall and extend
+ if (sn.forward) {
+ if (sn.head < sn.endPos) { sn.head++; sn.length++; }
+ else { sn.state = FILLING; }
+ } else {
+ if (sn.head > sn.startPos) { sn.head--; sn.length++; }
+ else { sn.state = FILLING; }
+ }
+ if (sn.length > sn.maxLength) sn.length = sn.maxLength;
+ break;
+
+ case FILLING:
+ if (sn.length < sn.maxLength) { sn.length++; }
+ else { sn.state = SHRINKING; }
+ break;
+
+ case SHRINKING:
+ if (sn.length > 1) { sn.length--; }
+ else {
+ // Completed one grow+shrink cycle for this snake
+ sn.forward = globalDirection; // align to current global direction
+ sn.state = GROWING;
+ sn.length = 1;
+ cycleDone[s] = true;
+ }
+ break;
+ }
+
+ if (sn.state == GROWING || sn.state == FILLING) anyGrowing = true;
+ }
+
+ // If all snakes signaled completion, count a completed cycle and reset flags
+ bool allDone = true;
+ for (int s = 0; s < numSnakes; s++) { if (!cycleDone[s]) { allDone = false; break; } }
+ if (allDone) {
+ completedCycles++;
+ for (int s = 0; s < numSnakes; s++) cycleDone[s] = false;
+ }
+
+ // After required cycles, flip global direction
+ if (completedCycles >= SNAKE_CYCLES_PER_ROTATION) {
+ globalDirection = !globalDirection;
+ completedCycles = 0;
+ // apply new direction to snakes so their next grow starts in that direction
+ for (int s = 0; s < numSnakes; s++) snakes[s].forward = globalDirection;
+ }
+
+ // Rotation speed: 2x while any snake is growing/filling, 1x otherwise
+ int rotationSpeed = anyGrowing ? 2 : 1;
+ if (globalDirection) rotationOffset = (rotationOffset + rotationSpeed) % size;
+ else rotationOffset = (rotationOffset - rotationSpeed + size) % size;
+
+ // Draw snakes into buffer (no rotation applied here)
+ for (int s = 0; s < numSnakes; s++) {
+ Snake& sn = snakes[s];
+ for (int i = 0; i < sn.length; i++) {
+ int pos = sn.forward ? (sn.head - i) : (sn.head + i);
+ if (pos < sn.startPos || pos > sn.endPos) continue;
+ if (pos < 0 || pos >= size) continue;
+
+ float brightness = 1.0f - (float(i) / float(sn.maxLength)) * 0.7f;
+ uint8_t scale = static_cast(constrain((int)(brightness * 255.0f), 0, 255));
+ CRGB pixel = sn.color;
+ pixel.nscale8_video(scale);
+
+ if (buf[pos] == CRGB::Black) buf[pos] = pixel; else buf[pos] += pixel;
+ }
+ }
+
+ // Rotate the whole buffer into the real LED array using rotationOffset
+ for (int i = 0; i < size; i++) {
+ int dst = (i + rotationOffset) % size;
+ leds[dst] = buf[i];
+ }
+
+ FastLED.show();
+ return 0;
+ });
+
+ delete[] buf;
+ delete[] snakes;
+ delete[] cycleDone;
+}
+
+
+// Morph between colors in the color pack smoothly
+void Anim_Morph(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed , int morphSteps) {
+ if (!leds || size <= 0 || colorPack.size < 2 || morphSteps <= 0) return;
+
+ int currentColorIndex = 0;
+ int nextColorIndex = 1;
+ int step = 0;
+
+ CRGB startColor, endColor, blendedColor;
+ Animation_Loop(activeFlag, speed, [&]() -> int {
+ // Get start and end colors for current morph
+ startColor = colorPack.col[currentColorIndex];
+ endColor = colorPack.col[nextColorIndex];
+
+ // Blend colors based on current step.
+ // Compute blendAmount in [0..255] so that step==0 => 0, step==morphSteps => 255.
+ uint32_t ba = 0;
+ if (morphSteps > 0) {
+ ba = (uint32_t)step * 255u / (uint32_t)morphSteps;
+ if (ba > 255u) ba = 255u;
+ }
+ uint8_t blendAmount = (uint8_t)ba;
+ blendedColor = blend(startColor, endColor, blendAmount);
+
+ // Fill all LEDs with the blended color
+ fill_solid(leds, size, blendedColor);
+ FastLED.show();
+
+ // Advance to next step. When step reaches morphSteps we have shown the
+ // final blended color (endColor). After that, advance to the next pair.
+ if (step < morphSteps) {
+ step++;
+ } else {
+ // completed this morph
+ step = 0;
+ currentColorIndex = nextColorIndex;
+ nextColorIndex = (nextColorIndex + 1) % colorPack.size;
+ }
+
+ return 0;
+ });
+}
+
uint32_t getRandomValue(uint32_t maxValue) {
return esp_random() % maxValue;
diff --git a/src/AppUpgrade.cpp b/src/AppUpgrade.cpp
index ca843c0..c6b6f28 100644
--- a/src/AppUpgrade.cpp
+++ b/src/AppUpgrade.cpp
@@ -237,11 +237,20 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
md5.begin();
size_t totalRead = 0;
- // Create temporary filename - try root directory first to avoid path issues
- String baseName = String(localPath);
- baseName.replace("/", "_");
- baseName.replace("\\", "_");
- String tempPath = "/temp_" + baseName + ".download";
+ // Create temporary filename in the same directory as the target file
+ String targetDir = String(localPath);
+ int lastSlash = targetDir.lastIndexOf('/');
+ String tempPath;
+
+ if (lastSlash >= 0) {
+ // Extract directory and filename
+ String directory = targetDir.substring(0, lastSlash + 1);
+ String filename = targetDir.substring(lastSlash + 1);
+ tempPath = directory + "temp_" + filename + ".download";
+ } else {
+ // File is in root directory
+ tempPath = "/temp_" + String(localPath) + ".download";
+ }
// Clean up any existing temp file first
if (fileSystem.exists(tempPath.c_str())) {
@@ -360,9 +369,9 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
// We'll still keep this file but report it as a verification failure
// Ensure target directory exists before rename
String dirPath = String(localPath);
- int lastSlash = dirPath.lastIndexOf('/');
- if (lastSlash > 0) {
- dirPath = dirPath.substring(0, lastSlash);
+ int targetLastSlash = dirPath.lastIndexOf('/');
+ if (targetLastSlash > 0) {
+ dirPath = dirPath.substring(0, targetLastSlash);
if (!fileSystem.exists(dirPath.c_str())) {
ESP_LOGI(TAG, "Creating target directory: %s", dirPath.c_str());
String dummyFile = dirPath + "/.dummy";
@@ -396,9 +405,9 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
// Ensure target directory exists before rename
String dirPath = String(localPath);
- int lastSlash = dirPath.lastIndexOf('/');
- if (lastSlash > 0) {
- dirPath = dirPath.substring(0, lastSlash);
+ int targetLastSlash = dirPath.lastIndexOf('/');
+ if (targetLastSlash > 0) {
+ dirPath = dirPath.substring(0, targetLastSlash);
if (!fileSystem.exists(dirPath.c_str())) {
ESP_LOGI(TAG, "Creating target directory: %s", dirPath.c_str());
String dummyFile = dirPath + "/.dummy";
@@ -922,7 +931,7 @@ void firmwareUpdateTask(void* parameter) {
if (updater->IsUpdateAvailable()) {
bool filesUpdated = true;
- bool firmwareUpdated = 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) {
@@ -962,7 +971,7 @@ void firmwareUpdateTask(void* parameter) {
if (needsRestart) {
ESP_LOGI(TAG, "Firmware update successful, restarting...");
- sendUpdateMessage("Restarting ", true, 100);
+ sendUpdateMessage("Restarting... ", true, 100);
vTaskDelay(2000);
ESP.restart();
} else {
@@ -971,7 +980,6 @@ void firmwareUpdateTask(void* parameter) {
}
}
-cleanup:
} catch (const std::exception& e) {
ESP_LOGE(TAG, "Update failed with exception: %s", e.what());
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, e.what());
@@ -980,6 +988,7 @@ cleanup:
updateProgress(AppUpdater::UpdateStatus::ERROR, 0, "Unknown error during update");
}
+cleanup:
// Clean up watchdog before exit
esp_task_wdt_delete(NULL);
diff --git a/src/BLE_SP110E.cpp b/src/BLE_SP110E.cpp
index 8fcc164..15ed1b0 100644
--- a/src/BLE_SP110E.cpp
+++ b/src/BLE_SP110E.cpp
@@ -60,27 +60,44 @@ uint8_t calculateChecksum(const uint8_t bArr[]) {
return (uint8_t)((bArr[2]) | ((bArr[0] << 1) & 254 & 105) | bArr[1]);
}
+
// Class for handling characteristic events
-/*
class SP110ECallbacks : public NimBLECharacteristicCallbacks {
- void onWrite(NimBLECharacteristic *pCharacteristic) override {
- std::string rawValue = pCharacteristic->getValue();
- const uint8_t* value = reinterpret_cast(rawValue.data());
- size_t length = rawValue.length();
- if (length >= 3) {
- ESP_LOGI(tag, "Data received 0x%02X, 0x%02X, 0x%02X (length %zu):", value[0], value[1], value[2], length);
- } else {
- ESP_LOGI(tag, "Data received (length %zu)", length);
+private:
+ static constexpr uint32_t DUPLICATE_FILTER_MS = 100; // 100ms filter window
+ static constexpr size_t MAX_PACKET_SIZE = 32; // Maximum expected packet size
+
+ uint32_t lastPacketTime = 0;
+ uint8_t lastPacketData[MAX_PACKET_SIZE] = {0};
+ size_t lastPacketLen = 0;
+
+ bool isDuplicatePacket(const uint8_t* data, size_t len) {
+ uint32_t currentTime = millis();
+
+ // Check if within the filter time window
+ if (currentTime - lastPacketTime > DUPLICATE_FILTER_MS) {
+ return false; // Outside filter window, not a duplicate
}
-
- sendToAllClients(value, length);
- process_BLE_SP110E_Command(value, length, pCharacteristic);
+
+ // Check if packet length and content match
+ if (len != lastPacketLen) {
+ return false; // Different length, not a duplicate
+ }
+
+ // Compare packet data
+ if (memcmp(data, lastPacketData, len) != 0) {
+ return false; // Different content, not a duplicate
+ }
+
+ return true; // Same packet within filter window
+ }
+
+ void updateLastPacket(const uint8_t* data, size_t len) {
+ lastPacketTime = millis();
+ lastPacketLen = len > MAX_PACKET_SIZE ? MAX_PACKET_SIZE : len;
+ memcpy(lastPacketData, data, lastPacketLen);
}
-};
-*/
-// Class for handling characteristic events
-class SP110ECallbacks : public NimBLECharacteristicCallbacks {
public:
void onWrite(NimBLECharacteristic* pCharacteristic) override {
if (!pCharacteristic) return;
@@ -94,6 +111,15 @@ public:
const uint8_t* data = reinterpret_cast(raw.data());
+ // Check for duplicate packets within the filter window
+ if (isDuplicatePacket(data, len)) {
+ ESP_LOGD(tag, "Duplicate packet filtered (len=%zu)", len);
+ return; // Ignore duplicate packet
+ }
+
+ // Update last packet tracking
+ updateLastPacket(data, len);
+
// Log up to first 16 bytes to avoid log spam
//logBytes(data, len);
@@ -126,6 +152,7 @@ private:
}
};
+
class LightStickCallbacks : public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic *pCharacteristic) override {
pCharacteristic->setValue("Hello...");
@@ -133,6 +160,7 @@ class LightStickCallbacks : public NimBLECharacteristicCallbacks {
}
};
+
// Function to send data to all connected clients in chunks based on MTU
void sendToAllClients(const uint8_t* data, size_t len) {
if (!pStickCharacteristic) {
@@ -205,40 +233,32 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
}
uint8_t command = val[3];
- ESP_LOGI(tag, "Command received: 0x%02X", command);
+ //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
switch (command) {
case TURN_ON:
- Lights_Set_ON();
+ RGB_Animations_ON();
led_status.enable = 1;
- //ESP_LOGI(tag, "Lights ON");
+ ESP_LOGI(tag, "RGB Lights ON");
break;
case TURN_OFF:
- Lights_Set_OFF();
+ RGB_Animations_OFF();
led_status.enable = 0;
- //ESP_LOGI(tag, "Lights OFF");
+ ESP_LOGI(tag, "RGB Lights OFF");
break;
case SET_STATIC_COLOR:
- if(len < 7) {
- ESP_LOGW(tag, "SET_STATIC_COLOR command requires 3 parameters (R,G,B)");
- break;
- }
led_status.red = val[1];
led_status.green = val[2];
led_status.blue = val[0];
- Lights_Set_Animation(SOLID_COLOR_INDEX, val[0], val[1], val[2]);
+ RGB_Lights_Set_Animation(SOLID_COLOR_INDEX, val[0], val[1], val[2]);
//ESP_LOGI(tag, "Color set to R:%d G:%d B:%d", led_status.red, led_status.green, led_status.blue);
break;
case SET_BRIGHT:
- if(len < 5) {
- ESP_LOGW(tag, "SET_BRIGHT command requires 1 parameter (brightness)");
- break;
- }
led_status.bright = val[0];
- Lights_Set_Brightness(val[0]);
+ RGB_Lights_Set_Brightness(val[0]);
//ESP_LOGI(tag, "Bright set to %d", led_status.bright);
break;
case SET_WHITE:
@@ -248,7 +268,7 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
break;
case SET_PRESET:
led_status.preset = val[0];
- Lights_Set_Animation(val[0], val[1], val[2], 0);
+ RGB_Lights_Set_Animation(val[0], val[1], val[2], 0);
//ESP_LOGI(tag, "Animation set to %d", led_status.preset);
break;
case SET_SPEED:
@@ -294,7 +314,6 @@ void process_BLE_SP110E_Command(const uint8_t* val, uint8_t len, NimBLECharacter
}
-
void Init_BLE_SP110E(NimBLEServer* pServer) {
if (!pServer) {
ESP_LOGE(tag, "Invalid BLE server pointer");
diff --git a/src/BLE_UpdateService.cpp b/src/BLE_UpdateService.cpp
index 3232b82..ca3b652 100644
--- a/src/BLE_UpdateService.cpp
+++ b/src/BLE_UpdateService.cpp
@@ -19,9 +19,10 @@ NimBLECharacteristic *pUpgradeCharacteristic2 = nullptr;
enum WIFI_STAT : byte { WIFI_DISCONNECTED=0, WIFI_BAD_CREDS=1, WIFI_NO_AP=2, WIFI_CONNECTED=3 };
-struct updateStatus {
+struct __attribute__((packed)) updateStatus {
WIFI_STAT wifiStatus = WIFI_DISCONNECTED;
bool wifiOnline = false;
+ float temperature = 0.0;
byte wifiIP[4] = {0, 0, 0, 0};
byte currVersion[3] = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH};
byte newVersion[3] = {0, 0, 0};
@@ -129,11 +130,23 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
updatePacket.newVersion[2] = otaVersion.patch();
}
+ updatePacket.temperature = boardTemperature; // Reset temperature after read
+
// Only populate the control characteristic with the status packet
if (pCharacteristic == pUpgradeCharacteristic1) {
+ ESP_LOGI(tag, "Packet size: %d bytes, temp: %.2f", sizeof(updatePacket), updatePacket.temperature);
+
+ // Debug: Log first few bytes of packet for verification
+ uint8_t* packetBytes = reinterpret_cast(&updatePacket);
+ ESP_LOGI(tag, "Packet bytes: [0]=%d [1]=%d [2-5]=%.2f [6-9]=%d.%d.%d.%d",
+ packetBytes[0], packetBytes[1], updatePacket.temperature,
+ packetBytes[6], packetBytes[7], packetBytes[8], packetBytes[9]);
+
pCharacteristic->setValue(reinterpret_cast(&updatePacket), sizeof(updatePacket));
ESP_LOGI(tag, "Upgrade status read");
}
+
+
}
};
diff --git a/src/BleServer.cpp b/src/BleServer.cpp
index ff9f713..76fb565 100644
--- a/src/BleServer.cpp
+++ b/src/BleServer.cpp
@@ -35,12 +35,13 @@ class ServerCallbacks : public NimBLEServerCallbacks {
};
*/
+
class ServerCallbacks : public NimBLEServerCallbacks {
public:
void onConnect(NimBLEServer* /*pServer*/) override {
ESP_LOGI(tag, "Client connected");
ensureAdvertising("onConnect");
- Buzzer_Play_Tune(TUNE_CONNECTED);
+ Buzzer_Play_Tune(TUNE_CONNECTED, 1);
}
void onDisconnect(NimBLEServer* /*pServer*/) override {
@@ -68,6 +69,7 @@ private:
}
};
+
/*
void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
@@ -111,6 +113,7 @@ void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
}
*/
+
void Init_BleServer(bool isSP110EActive, bool isUpgradeActive) {
ESP_LOGI(tag, "Initializing BLE...");
diff --git a/src/BleSettings.cpp b/src/BleSettings.cpp
index 3e2d549..33dfebf 100644
--- a/src/BleSettings.cpp
+++ b/src/BleSettings.cpp
@@ -3,6 +3,7 @@
#include
#include
#include "esp_log.h"
+#include "global.h" // for get_chip_mac
static const char *tag = "ble-settings";
@@ -62,6 +63,15 @@ void Load_BLE_Settings(const String &configPath) {
BTUpgradeCharacteristic1UUID = safeJsonString(root, "upgrade-char1", BTUpgradeCharacteristic1UUID.c_str());
BTUpgradeCharacteristic2UUID = safeJsonString(root, "upgrade-char2", BTUpgradeCharacteristic2UUID.c_str());
+ // Appeand a semi unique suffix from the MAC address to the device name
+ char macSuffix[13] = {0}; // Just need 2 chars + null terminator
+ get_chip_mac(macSuffix, sizeof(macSuffix)); // Only get first 2 chars of MAC
+ BTDeviceName += "-"; // Add a separator
+ BTDeviceName += macSuffix[0]; // Add first character
+ BTDeviceName += macSuffix[1]; // Add second character
+
+ BTDeviceName = "SP110E";
+
ESP_LOGI(tag, "Loaded BLE config: name=%s svc=%s char1=%s stick=%s upg_svc=%s upg1=%s upg2=%s",
BTDeviceName.c_str(), BTServiceUUID.c_str(), BTSP110ECharacteristicUUID.c_str(),
BTStickCharacteristicUUID.c_str(), BTUpgradeServiceUUID.c_str(),
diff --git a/src/Ramp_Lights.cpp b/src/Ramp_Lights.cpp
index 115a9f0..b696ce9 100644
--- a/src/Ramp_Lights.cpp
+++ b/src/Ramp_Lights.cpp
@@ -6,7 +6,7 @@ static const char* tag = "ramp";
// TODO add tickSkip to instanciation
#define TickDelayCount 5
-RAMP_LIGHT::RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, int min, int max, float step)
+RAMP_LIGHT::RAMP_LIGHT(OneButton* button, PWM_Output* pwmOutput, float min, float max, float step)
: button(button), pwmOutput(pwmOutput), min(min), max(max), step(step) {
button->attachClick([](void* context) { static_cast(context)->singleClick(); }, this);
@@ -52,10 +52,11 @@ void RAMP_LIGHT::longPressStop(){
void RAMP_LIGHT::duringLongPress(){
if(IsOn){
if (tickCount > 0 && --tickCount == 0) {
- currentValue += (rampState == RampingUp) ? step : -step;
+ // When ramping down, currentValue may go below min if step is large; constrain ensures bounds.
+ // Ensure currentValue stays within [min, max] bounds for safe PWM operation
currentValue = constrain(currentValue, min, max);
pwmOutput->setOutput(currentValue);
- ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput->currOutVal, pwmOutput->getOutVal());
+ //ESP_LOGD(tag, "duty: %f, sent val: %d, actual val: %d", currentValue, pwmOutput->currOutVal, pwmOutput->getOutVal());
tickCount = TickDelayCount;
}
}
diff --git a/src/global.cpp b/src/global.cpp
index 870c4a2..b0bdf77 100644
--- a/src/global.cpp
+++ b/src/global.cpp
@@ -16,6 +16,9 @@ enum COMM_MODE commMode = COMM_WIFI_AP_BLE;
CHIP_INFO chipInfo;
Version localVersion = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH};
+float boardTemperature = 10.3; // More realistic temperature for testing
+
+// Get the MAC address of the chip and format it as a string
void get_chip_mac(char* macStr, size_t size) {
uint64_t chipMAC = ESP.getEfuseMac();
uint8_t macByte[6];
diff --git a/src/main.cpp b/src/main.cpp
index d0c468f..028827f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -35,7 +35,9 @@
#include "BLE_SP110E.h"
#include "BleSettings.h"
-#define FREERTOs_DIAGNOSTICS 0
+#include "my_device.h"
+
+#define FREERTOS_DIAGNOSTICS 0
#define OLED_ENABLED 0
#define WIFI_ENABLED 0
#define STRIPS_ENABLED 1
@@ -65,20 +67,59 @@ LUMA_PACKET lumaPacket;
#endif
static const char *tag = "main";
-SYS_SETTINGS sys_settings;
-PWM_Output *pwmOutputs[4];
-RAMP_LIGHT *rampLight1;
-RAMP_LIGHT *rampLight2;
-bool UpgradeMode = false;
+// Timer handles for periodic tasks
+TimerHandle_t buttonScanTimer = NULL;
+#define buttonScanInterval 10 // ms
+
+TimerHandle_t temperatureTimer = NULL;
+TimerHandle_t statusLedTimer = NULL;
+#define statusLedInterval 500 // ms
+
+TimerHandle_t upgradeHeartbeatTimer = NULL;
+#define upgradeHeartbeatInterval 5000 // ms
+
+TimerHandle_t diagnosticsTimer = NULL;
+#define diagnosticsInterval 60000 // ms
+
+
-void Init_ADC(void);
-float readBoardInputVoltage(void);
void setupLogLevels(esp_log_level_t logLevel);
-void Get_Board_and_Booth_File_Paths(const char *, String &, String &);
-void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath);
-void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]);
-void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]);
+
+// Timer callback functions
+void ButtonScanCallback(TimerHandle_t xTimer) {
+ for (int i = 0; i < 3; i++) {
+ if (boardButtons[i] != NULL) {
+ boardButtons[i]->tick();
+ }
+ }
+}
+
+void TemperatureCallback(TimerHandle_t xTimer) {
+ if (sys_settings.tSensorSettings.enabled) {
+ boardTemperature = tSensor->readTemperatureF();
+ // Fan Control
+ UpdateFanControl(boardTemperature, pwmOutputs[sys_settings.tSensorSettings.pwmIndex]);
+ }
+}
+
+void StatusLedCallback(TimerHandle_t xTimer) {
+ if (sys_settings.boardPins.stat[1] >= 0) {
+ static bool ledState = false;
+ setStatusPin2(ledState = !ledState);
+ }
+}
+
+void UpgradeHeartbeatCallback(TimerHandle_t xTimer) {
+ Buzzer_Play_Tune(TUNE_LOWBEEP);
+}
+
+void DiagnosticsCallback(TimerHandle_t xTimer) {
+ #if FREERTOs_DIAGNOSTICS
+ print_task_watermarks();
+ #endif
+}
+
void checkLEDCChannels()
{
@@ -94,6 +135,8 @@ void checkLEDCChannels()
#define Button1Pin 8
void setup()
{
+ bool UpgradeMode = false;
+
// Serial Port
Serial.begin(115200);
while (!Serial);
@@ -120,7 +163,7 @@ void setup()
Init_File_System();
// Print all system files
- if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){
+ if (digitalRead(sys_settings.boardPins.btn[1]) == LOW){
printAllSystemFiles();
}
@@ -160,25 +203,25 @@ void setup()
Init_ADC();
// Initialize Temperature Sensor
- Init_TSensor(72);
+ Init_TSensor(72, &sys_settings.tSensorSettings);
float val = readBoardInputVoltage();
ESP_LOGI(tag, "Input Volage = %f", val);
- // Initialize BLE
+ // Initialize BLE & Wifi
+ // If button 1 is held during boot, enable upgrade mode
Load_BLE_Settings("/system/ble.json");
if (digitalRead(sys_settings.boardPins.btn[0]) == LOW)
{
setStatusPin1(true);
- UpgradeMode = true;
ESP_LOGW(tag, "Upgrade Mode Triggered");
ESP_LOGW(tag, "Enabling BLE and Update Service");
Init_BleServer(true, true);
ESP_LOGW(tag, "Enabling Wifi AP and Client");
Wifi_Init();
-
- //Buzzer_Play_Tune(TUNE_UPGRADE_MODE);
+ UpgradeMode = true;
+ upgradeHeartbeatTimer = xTimerCreate("UpgradeHeartbeat", pdMS_TO_TICKS(5000), pdTRUE, NULL, UpgradeHeartbeatCallback);
}
else
{
@@ -198,54 +241,46 @@ void setup()
#endif
#if STRIPS_ENABLED
- Init_Lights_Task();
+ Init_RGB_Lights_Task();
+ //vTaskDelay(100);
+ //Init_Ramp_Front_Light_Task();
#endif
- Buzzer_Play_Tune(TUNE_BOOT);
+ // Create and start software timers
+ buttonScanTimer = xTimerCreate("ButtonScan", pdMS_TO_TICKS(buttonScanInterval), pdTRUE, NULL, ButtonScanCallback);
+ temperatureTimer = xTimerCreate("Temperature", pdMS_TO_TICKS(sys_settings.tSensorSettings.intervalMs), pdTRUE, NULL, TemperatureCallback);
+ statusLedTimer = xTimerCreate("StatusLED", pdMS_TO_TICKS(statusLedInterval), pdTRUE, NULL, StatusLedCallback);
+
+
+ #if FREERTOs_DIAGNOSTICS
+ diagnosticsTimer = xTimerCreate("Diagnostics", pdMS_TO_TICKS(60000), pdTRUE, NULL, DiagnosticsCallback);
+ #endif
- // TODO... Test if this is still necessary need to configure pin 0 for some reason
- // pinMode(0, INPUT); // button0/boot pin
+ // Start the timers
+ if (buttonScanTimer) xTimerStart(buttonScanTimer, 25);
+ if (temperatureTimer && sys_settings.tSensorSettings.enabled) xTimerStart(temperatureTimer, 100);
+ if (statusLedTimer) xTimerStart(statusLedTimer, 0);
+ if (upgradeHeartbeatTimer && UpgradeMode) xTimerStart(upgradeHeartbeatTimer, upgradeHeartbeatInterval);
+
+ #if FREERTOS_DIAGNOSTICS
+ if (diagnosticsTimer) xTimerStart(diagnosticsTimer, diagnosticsInterval);
+ #endif
+
+ if(UpgradeMode){
+ Buzzer_Play_Tune(TUNE_BOOT);
+ }else{
+ Buzzer_Play_Tune(TUNE_BOOT);
+ }
#if LUMASTIK_ENABLED
Init_Luma_Master();
#endif
- vTaskDelay(100);
- Lights_Control_Task_Resume();
}
void loop()
{
- // Button Scanning
- ON_EVERY_N_MILLISECONDS(10)
- {
- for (int i = 0; i < 3; i++)
- {
- if (boardButtons[i] != NULL)
- {
- boardButtons[i]->tick();
- }
- }
- }
-
- // Temperature Monitor
- static OnEveryMsVariable temperatureMonitorTimer;
- if (sys_settings.tSensorSettings.enabled)
- {
- if (temperatureMonitorTimer.ready(sys_settings.tSensorSettings.intervalMs))
- {
- static float boardTemperature;
- boardTemperature = tSensor->readTemperatureF();
- // ESP_LOGI(tag, "Board T: %F", boardTemperature);
-
- // Fan Control
- UpdateFanControl(boardTemperature, pwmOutputs[sys_settings.tSensorSettings.pwmIndex]);
-
- }
- }
-
-
- // Animation TestMode Timeout
+ // Animation TestMode Timeout (keep in loop as it's event-driven)
#if LEDS_ENABLED
if (animStatus.EventTestCountdown)
{
@@ -261,7 +296,7 @@ void loop()
};
#endif
- // Reboot requested
+ // Reboot requested (keep in loop as it's a state-based countdown)
#if WIFI_ENABLED
if (RebootSystem)
{
@@ -281,46 +316,8 @@ void loop()
}
#endif
- // Toggle Status LED L2
- ON_EVERY_N_MILLISECONDS(500)
- {
- if (sys_settings.boardPins.stat[1] >= 0)
- {
- static bool ledState = false;
- // digitalWrite(sys_settings.boardPins.stat[1], ledState = !ledState);
- setStatusPin2(ledState = !ledState);
- }
- }
-
- // Upgrade Mode Hearbeat tune
- if(UpgradeMode){
- ON_EVERY_N_MILLISECONDS(5000)
- {
- Buzzer_Play_Tune(TUNE_LOWBEEP);
- //ESP_LOGI(tag, "Upgrade Mode Heartbeat");
- }
- }
-
- #if FREERTOs_DIAGNOSTICS
- ON_EVERY_N_MILLISECONDS(60000)
- {
- print_task_watermarks();
- }
- #endif
-
- // Turn off white light after timeout
- ON_EVERY_N_MILLISECONDS(100)
- {
- // Only decrement if timeout is active
- if (whiteTimeout > 0) {
- whiteTimeout--;
- if (whiteTimeout == 0) {
- Lights_Set_White(0);
- ESP_LOGD(tag, "White light timeout triggered");
- }
- }
- }
-
+ // Small delay to prevent busy-waiting
+ vTaskDelay(pdMS_TO_TICKS(100));
}
void setupLogLevels(esp_log_level_t logLevel)
@@ -344,280 +341,5 @@ void setupLogLevels(esp_log_level_t logLevel)
esp_log_level_set("tsensor", logLevel);
}
-// TODO Restore original setOutput code..
-#define RELAY_RES 10
-void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4])
-{
- for (int i = 0; i < 4; i++)
- {
- int chIndex = findUnusedLedcChannel();
- if (chIndex < 0)
- {
- ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i);
- continue;
- }
- pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, pwmSettings[i].max, false);
- pwmOutputs[i]->setOutput(pwmSettings[i].def);
- // pwmOutputs[i]->setOutput(5.0);
- ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq, chIndex);
- }
-}
-void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4])
-{
- if (settings[0].enabled)
- {
- rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], 5.0, 100.0, 1.5);
- ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 1, settings[0].btnIndex, settings[0].pwmOutIndex);
- }
- if (settings[1].enabled)
- {
- rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], 5.0, 100.0, 1.5);
- ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 2, settings[1].btnIndex, settings[1].pwmOutIndex);
- }
-}
-
-// 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)
-{
- File file = LittleFS.open(sysPath);
-
- if (!file)
- {
- ESP_LOGE(tag, "Error opening %s...", sysPath);
- return;
- }
-
- JsonDocument doc;
- DeserializationError error = deserializeJson(doc, file);
- file.close();
-
- if (error)
- {
- ESP_LOGE(tag, "%s deserialize error!..", sysPath);
- return;
- }
-
- // get hardware version string
- boardPath = jsonConstrainString(tag, doc.as(), "boardfile", "/cfg/boards/board15.json");
- boothPath = jsonConstrainString(tag, doc.as(), "configfile", "/cfg/booths/custom.json");
-}
-
-void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
-{
- File file = LittleFS.open(boothPath);
- if (!file)
- {
- ESP_LOGE(tag, "Error opening %s...", boothPath.c_str());
- return;
- }
-
- JsonDocument doc;
- DeserializationError error = deserializeJson(doc, file);
- file.close();
-
- if (error)
- {
- ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str());
- return;
- }
-
- // ********** Mode ***********
- String modeStr = jsonConstrainString(tag, doc.as(), "mode", "booth");
- if (modeStr == "roamer")
- {
- sys_settings.mode = BOOTH_MODE_ROAMER;
- }
- else if (modeStr == "stik")
- {
- sys_settings.mode = BOOTH_MODE_STIK;
- }
- else
- {
- sys_settings.mode = BOOTH_MODE_NONE;
- }
-
- // ********** PWM Out ***********
- JsonArray pwmJsonArray = doc["pwmout"];
- if (!pwmJsonArray.isNull())
- {
- int pwmIndex = 0;
- for (JsonObject obj : pwmJsonArray)
- {
- if (pwmIndex >= 4)
- break;
- sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
- sys_settings.pwmOutSettings[pwmIndex].freq = jsonConstrain(tag, obj, "freq", 100, 5000, 250);
- sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0);
- sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0);
- sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain(tag, obj, "default", 0.0, 100.0, 0.0);
- sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
- // sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true);
- pwmIndex++;
- }
- ESP_LOGI(tag, "Loaded PWmOutput settings...");
- }
- else
- {
- ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath);
- }
-
- // ********** Ramp Lights ***********
- JsonArray rampJsonArray = doc["ramp-lights"];
- if (!rampJsonArray.isNull())
- {
- int rampIndex = 0;
- for (JsonObject obj : rampJsonArray)
- {
- if (rampIndex >= 2)
- break;
- sys_settings.rampLightSettings[rampIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
- sys_settings.rampLightSettings[rampIndex].vision = jsonConstrainBool(tag, obj, "vision", true);
- sys_settings.rampLightSettings[rampIndex].pwmOutIndex = jsonConstrain(tag, obj, "relay-index", 0, 1, 0);
- sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain(tag, obj, "button-index", 0, 1, 0);
- rampIndex++;
- }
- ESP_LOGI(tag, "Loaded Ramp Lights settings...");
- }
- else
- {
- ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath);
- }
-
- // ********** Fan ***********
- JsonObject sensorJson = doc["t-sensor"];
- if (!sensorJson.isNull())
- {
- sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true);
- sys_settings.tSensorSettings.pwmIndex = jsonConstrain(tag, sensorJson, "relay", 0, 3, 3);
- sys_settings.tSensorSettings.setpoint1 = jsonConstrain(tag, sensorJson, "sp1", 0.0, 100.0, 80.0);
- sys_settings.tSensorSettings.setpoint2 = jsonConstrain(tag, sensorJson, "sp2", 0.0, 100.0, 85.0);
- sys_settings.tSensorSettings.fanPower1 = jsonConstrain(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
- sys_settings.tSensorSettings.fanPower2 = jsonConstrain(tag, sensorJson, "fan-pwr2", 0.0, 100.0, 50.0);
- sys_settings.tSensorSettings.hyst = jsonConstrain(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
- sys_settings.tSensorSettings.intervalMs = jsonConstrain(tag, sensorJson, "interval", 1000, 30000, 5000);
- ESP_LOGI(tag, "Loaded TSensor settings...");
- ESP_LOGD(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1, sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
- }
- else
- {
- ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath);
- }
-
- // ********** RGB Strips ***********
- JsonArray stripsJsonArray = doc["strips"];
- if (!stripsJsonArray.isNull())
- {
- int stripIndex = 0;
- for (JsonObject obj : stripsJsonArray)
- {
- if (stripIndex >= 2)
- break;
- sys_settings.ledStripSettings[stripIndex]->enabled = jsonConstrainBool(tag, obj, "en", true);
- sys_settings.ledStripSettings[stripIndex]->size = jsonConstrain(tag, obj, "size", 1, 250, 25);
- sys_settings.ledStripSettings[stripIndex]->chip = jsonConstrainString(tag, obj, "chip", "WS2812B");
- sys_settings.ledStripSettings[stripIndex]->rgbOrder = jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
- sys_settings.ledStripSettings[stripIndex]->shift = jsonConstrain(tag, obj, "shift", -250, 250, 0);
- sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain(tag, obj, "offset", -250, 250, 0);
- sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain(tag, obj, "bright", 5, 255, 200);
- sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
- sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
- sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain(tag, obj, "core", 0, 1, 0);
- stripIndex++;
- }
-
- sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1;
- sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2;
- ESP_LOGI(tag, "Loaded LED Strip settings...");
- }
- else
- {
- ESP_LOGE(tag, "Error!, %s key: strips not found..");
- }
-
- // ********** BLE ***********
- JsonObject bleJson = doc["ble"];
- if (!bleJson.isNull())
- {
- sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true);
- sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS");
-
- ESP_LOGI(tag, "Loaded BLE settings...");
- }
- else
- {
- ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath);
- }
-
- // ********** RF Remote***********
- /*
- JsonObject rfJson = doc["wifi-ap"];
- if(!rfJson.isNull()){
- //sys_settings.rampLights[0].enabled = jsonConstrainBool(tag, wifiApJson, "en", true);
- ESP_LOGI(tag, "Loaded RF Remote settings...");
- }else{
- ESP_LOGE(tag, "Error!, %s key: wifi-ap not found..", boothPath);
- }
- */
-}
-
-void Init_ADC(void)
-{
- // Configure ADC
- analogReadResolution(12); // 12-bit ADC
- analogSetAttenuation(ADC_11db);
- if (sys_settings.boardPins.adc1 >= 0)
- {
- analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db);
- }
-
- // Enable ADC calibration
- esp_adc_cal_characteristics_t adc_chars;
- esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars);
-
- // Check calibration success
- if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK)
- {
- 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)
- {
- ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse");
- }
- else
- {
- ESP_LOGW(tag, "ADC calibration: Using default reference voltage");
- }
-}
-
-float readBoardInputVoltage(void)
-{
- const int SAMPLES = 64;
- uint32_t reading = 0;
-
- if (sys_settings.boardPins.adc1 < 0)
- {
- ESP_LOGE(tag, "ADC Pin not valid");
- return 0.0;
- }
-
- // Multiple readings for averaging
- for (int i = 0; i < SAMPLES; i++)
- {
- reading += analogRead(sys_settings.boardPins.adc1);
- delayMicroseconds(50); // Small delay between samples
- }
- reading /= SAMPLES;
-
- // Convert raw ADC to voltage using calibration
- uint32_t voltage_mv;
- esp_adc_cal_characteristics_t 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);
-
- // Scale to 12V range
- float voltage = (voltage_mv / 1000.0f) * (10470.0f / 470.0f);
-
- return voltage;
-}
\ No newline at end of file
diff --git a/src/my_device.cpp b/src/my_device.cpp
new file mode 100644
index 0000000..c2a477e
--- /dev/null
+++ b/src/my_device.cpp
@@ -0,0 +1,318 @@
+#include "my_device.h"
+#include "JsonConstrain.h"
+#include "global.h"
+#include "system.h"
+#include "my_buttons.h"
+#include "PWM_Output.h"
+#include "Ramp_Lights.h"
+#include "esp_log.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static const char *tag = "my_device";
+
+SYS_SETTINGS sys_settings;
+PWM_Output *pwmOutputs[4];
+RAMP_LIGHT *rampLight1;
+RAMP_LIGHT *rampLight2;
+
+// TODO Restore original setOutput code..
+#define RELAY_RES 10
+void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4])
+{
+ // Initialize all pointers to nullptr first
+ for (int i = 0; i < 4; i++) {
+ pwmOutputs[i] = nullptr;
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ int chIndex = findUnusedLedcChannel();
+ if (chIndex < 0)
+ {
+ ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i);
+ continue;
+ }
+ pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, pwmSettings[i].max, false);
+ pwmOutputs[i]->setOutput(pwmSettings[i].def);
+ // pwmOutputs[i]->setOutput(5.0);
+ ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq, chIndex);
+ }
+}
+
+
+void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4])
+{
+ if (settings[0].enabled)
+ {
+ rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], settings[0].min, settings[0].max, settings[0].step);
+ ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 1, settings[0].btnIndex, settings[0].pwmOutIndex);
+ }
+
+ if (settings[1].enabled)
+ {
+ rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], settings[1].min, settings[1].max, settings[1].step);
+ ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 2, settings[1].btnIndex, settings[1].pwmOutIndex);
+ }
+}
+
+
+// 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)
+{
+ File file = LittleFS.open(sysPath);
+
+ if (!file)
+ {
+ ESP_LOGE(tag, "Error opening %s...", sysPath);
+ return;
+ }
+
+ JsonDocument doc;
+ DeserializationError error = deserializeJson(doc, file);
+ file.close();
+
+ if (error)
+ {
+ ESP_LOGE(tag, "%s deserialize error!..", sysPath);
+ return;
+ }
+
+ // get hardware version string
+ boardPath = jsonConstrainString(tag, doc.as(), "boardfile", "/cfg/boards/board15.json");
+ boothPath = jsonConstrainString(tag, doc.as(), "configfile", "/cfg/booths/custom.json");
+}
+
+
+void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
+{
+ File file = LittleFS.open(boothPath);
+ if (!file)
+ {
+ ESP_LOGE(tag, "Error opening %s...", boothPath.c_str());
+ return;
+ }
+
+ JsonDocument doc;
+ DeserializationError error = deserializeJson(doc, file);
+ file.close();
+
+ if (error)
+ {
+ ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str());
+ return;
+ }
+
+ // ********** Mode ***********
+ String modeStr = jsonConstrainString(tag, doc.as(), "mode", "booth");
+ if (modeStr == "roamer")
+ {
+ sys_settings.mode = BOOTH_MODE_ROAMER;
+ }
+ else if (modeStr == "stik")
+ {
+ sys_settings.mode = BOOTH_MODE_STIK;
+ }
+ else
+ {
+ sys_settings.mode = BOOTH_MODE_NONE;
+ }
+
+ // ********** PWM Out ***********
+ JsonArray pwmJsonArray = doc["pwmout"];
+ if (!pwmJsonArray.isNull())
+ {
+ int pwmIndex = 0;
+ for (JsonObject obj : pwmJsonArray)
+ {
+ if (pwmIndex >= sizeof(sys_settings.pwmOutSettings) / sizeof(sys_settings.pwmOutSettings[0]))
+ break;
+ sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
+ sys_settings.pwmOutSettings[pwmIndex].freq = jsonConstrain(tag, obj, "freq", 100, 5000, 250);
+ sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0);
+ sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0);
+ sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain(tag, obj, "default", 0.0, 100.0, 0.0);
+ sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
+ // sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true);
+ pwmIndex++;
+ }
+ ESP_LOGI(tag, "Loaded PWmOutput settings...");
+ }
+ else
+ {
+ ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath);
+ }
+
+ // ********** Ramp Lights ***********
+ JsonArray rampJsonArray = doc["ramp-lights"];
+ if (!rampJsonArray.isNull())
+ {
+ int rampIndex = 0;
+ for (JsonObject obj : rampJsonArray)
+ {
+ if (rampIndex >= sizeof(sys_settings.rampLightSettings) / sizeof(sys_settings.rampLightSettings[0]))
+ break;
+ sys_settings.rampLightSettings[rampIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
+ sys_settings.rampLightSettings[rampIndex].vision = jsonConstrainBool(tag, obj, "vision", true);
+ sys_settings.rampLightSettings[rampIndex].pwmOutIndex = jsonConstrain(tag, obj, "relay-index", 0, 1, 0);
+ sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain(tag, obj, "button-index", 0, 1, 0);
+ sys_settings.rampLightSettings[rampIndex].min = jsonConstrain(tag, obj, "min", 0.0, 95.0, 0.0);
+ sys_settings.rampLightSettings[rampIndex].max = jsonConstrain(tag, obj, "max", 5.0, 100.0, 100.0);
+ sys_settings.rampLightSettings[rampIndex].step = jsonConstrain(tag, obj, "step", 0.1, 10.0, 1.5);
+ rampIndex++;
+ }
+ ESP_LOGI(tag, "Loaded Ramp Lights settings...");
+ }
+ else
+ {
+ ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath);
+ }
+
+ // ********** Fan ***********
+ JsonObject sensorJson = doc["t-sensor"];
+ if (!sensorJson.isNull())
+ {
+ sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true);
+ sys_settings.tSensorSettings.pwmIndex = jsonConstrain(tag, sensorJson, "relay", 0, 3, 3);
+ sys_settings.tSensorSettings.setpoint1 = jsonConstrain(tag, sensorJson, "sp1", 50.0, 100.0, 80.0);
+ sys_settings.tSensorSettings.setpoint2 = jsonConstrain(tag, sensorJson, "sp2", 60.0, 110.0, 90.0);
+ sys_settings.tSensorSettings.fanPower1 = jsonConstrain(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
+ sys_settings.tSensorSettings.fanPower2 = jsonConstrain(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0);
+ sys_settings.tSensorSettings.hyst = jsonConstrain(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
+ sys_settings.tSensorSettings.intervalMs = jsonConstrain(tag, sensorJson, "interval", 1000, 30000, 5000);
+ ESP_LOGI(tag, "Loaded TSensor settings...");
+ ESP_LOGI(tag, " SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1, sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
+ }
+ else
+ {
+ ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath);
+ }
+
+ // ********** RGB Strips ***********
+ JsonArray stripsJsonArray = doc["strips"];
+ if (!stripsJsonArray.isNull())
+ {
+ int stripIndex = 0;
+ for (JsonObject obj : stripsJsonArray)
+ {
+ if (stripIndex >= 2)
+ break;
+ sys_settings.ledStripSettings[stripIndex]->enabled = jsonConstrainBool(tag, obj, "en", true);
+ sys_settings.ledStripSettings[stripIndex]->size = jsonConstrain(tag, obj, "size", 1, 250, 25);
+ sys_settings.ledStripSettings[stripIndex]->chip = jsonConstrainString(tag, obj, "chip", "WS2812B");
+ sys_settings.ledStripSettings[stripIndex]->rgbOrder = jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
+ sys_settings.ledStripSettings[stripIndex]->shift = jsonConstrain(tag, obj, "shift", -250, 250, 0);
+ sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain(tag, obj, "offset", -250, 250, 0);
+ sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain(tag, obj, "bright", 5, 255, 200);
+ sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
+ sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
+ sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain(tag, obj, "core", 0, 1, 0);
+ stripIndex++;
+ }
+
+ sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1;
+ sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2;
+ ESP_LOGI(tag, "Loaded LED Strip settings...");
+ }
+ else
+ {
+ ESP_LOGE(tag, "Error!, %s key: strips not found..");
+ }
+
+ // ********** BLE ***********
+ JsonObject bleJson = doc["ble"];
+ if (!bleJson.isNull())
+ {
+ sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true);
+ sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS");
+
+ ESP_LOGI(tag, "Loaded BLE settings...");
+ }
+ else
+ {
+ ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath);
+ }
+
+ // ********** RF Remote***********
+ /*
+ JsonObject rfJson = doc["wifi-ap"];
+ if(!rfJson.isNull()){
+ //sys_settings.rampLights[0].enabled = jsonConstrainBool(tag, wifiApJson, "en", true);
+ ESP_LOGI(tag, "Loaded RF Remote settings...");
+ }else{
+ ESP_LOGE(tag, "Error!, %s key: wifi-ap not found..", boothPath);
+ }
+ */
+}
+
+
+void Init_ADC(void)
+{
+ // Configure ADC
+ analogReadResolution(12); // 12-bit ADC
+ analogSetAttenuation(ADC_11db);
+ if (sys_settings.boardPins.adc1 >= 0)
+ {
+ analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db);
+ }
+
+ // Enable ADC calibration
+ esp_adc_cal_characteristics_t adc_chars;
+ esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars);
+
+ // Check calibration success
+ if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK)
+ {
+ 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)
+ {
+ ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse");
+ }
+ else
+ {
+ ESP_LOGW(tag, "ADC calibration: Using default reference voltage");
+ }
+}
+
+
+float readBoardInputVoltage(void)
+{
+ const int SAMPLES = 64;
+ uint32_t reading = 0;
+
+ if (sys_settings.boardPins.adc1 < 0)
+ {
+ ESP_LOGE(tag, "ADC Pin not valid");
+ return 0.0;
+ }
+
+ // Multiple readings for averaging
+ for (int i = 0; i < SAMPLES; i++)
+ {
+ reading += analogRead(sys_settings.boardPins.adc1);
+ delayMicroseconds(50); // Small delay between samples
+ }
+ reading /= SAMPLES;
+
+ // Convert raw ADC to voltage using calibration
+ uint32_t voltage_mv;
+ esp_adc_cal_characteristics_t 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);
+
+ // Scale to 12V range
+ float voltage = (voltage_mv / 1000.0f) * (10470.0f / 470.0f);
+
+ return voltage;
+}
\ No newline at end of file
diff --git a/src/my_tsensor.cpp b/src/my_tsensor.cpp
index 15a3817..7cbf218 100644
--- a/src/my_tsensor.cpp
+++ b/src/my_tsensor.cpp
@@ -1,22 +1,31 @@
#include "my_tsensor.h"
#include
#include "global.h"
+#include "system.h"
static const char* tag = "tsensor";
-T_SENSOR tSensorSettings;
+TSENSOR_SETTINGS *t_settings = nullptr; // Global pointer reference
TI_TMP102_Compatible *tSensor = nullptr;
/******************* Temperature Control ********************/
- void Init_TSensor(uint8_t addr) {
+void Init_TSensor(uint8_t addr, TSENSOR_SETTINGS *tsettings) {
+
+ if(tsettings != nullptr) {
+ t_settings = tsettings; // Store pointer reference
+ ESP_LOGI(tag, "TSensor settings loaded: SP1=%.1f, SP2=%.1f, FP1=%.1f, FP2=%.1f, Hyst=%.1f",
+ t_settings->setpoint1, t_settings->setpoint2,
+ t_settings->fanPower1, t_settings->fanPower2, t_settings->hyst);
+ }
+
// Initialize the temperature sensor once with the provided I2C address
if (tSensor == nullptr) {
- tSensor = new TI_TMP102_Compatible(addr);
- ESP_LOGI(tag, "TSensor initialized at I2C addr 0x%02X", addr);
+ tSensor = new TI_TMP102_Compatible(addr);
+ ESP_LOGI(tag, "TSensor initialized at I2C addr 0x%02X", addr);
} 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);
}
- }
+}
static inline float clampf(float v, float lo, float hi) {
@@ -26,62 +35,149 @@ TI_TMP102_Compatible *tSensor = nullptr;
}
void UpdateFanControl(float temperature, PWM_Output* pwmOut) {
+
if (pwmOut == nullptr) {
- ESP_LOGW(tag, "UpdateFanControl called with null PWM output");
- return;
+ ESP_LOGW(tag, "UpdateFanControl called with null PWM output");
+ return;
}
if (isnan(temperature) || isinf(temperature)) {
- ESP_LOGW(tag, "Invalid temperature reading: %f", temperature);
- return;
+ ESP_LOGW(tag, "Invalid temperature reading: %f", temperature);
+ return;
}
- static uint8_t FanState = 0;
- tSensorSettings.temperature = temperature; // cache last temp
+ if (t_settings == nullptr || !t_settings->enabled) {
+ ESP_LOGV(tag, "Temperature control disabled or not initialized");
+ return;
+ }
+
+ static bool fanIsOn = false;
float currentDuty = pwmOut->currDuty;
float newDuty = currentDuty;
- // Sanitize settings locally (do not modify globals)
- float sp1 = tSensorSettings.Setpoint1;
- float sp2 = tSensorSettings.Setpoint2;
- float hyst = tSensorSettings.hyst;
- float fp1 = tSensorSettings.fanPower1;
- float fp2 = tSensorSettings.fanPower2;
+ // Use TSENSOR_SETTINGS field names with pointer dereferencing
+ float sp1 = t_settings->setpoint1;
+ float sp2 = t_settings->setpoint2;
+ float hyst = t_settings->hyst;
+ float fp1 = t_settings->fanPower1;
+ float fp2 = t_settings->fanPower2;
if (hyst < 0.0f) hyst = 0.0f;
if (sp2 < sp1) {
- // Ensure sp2 >= sp1
- float tmp = sp1; sp1 = sp2; sp2 = tmp;
+ // Ensure sp2 >= sp1
+ float tmp = sp1; sp1 = sp2; sp2 = tmp;
+ tmp = fp1; fp1 = fp2; fp2 = tmp; // Also swap corresponding fan powers
}
const float maxDuty = pwmOut->getMaxDuty();
fp1 = clampf(fp1, 0.0f, maxDuty);
fp2 = clampf(fp2, 0.0f, maxDuty);
- // Fan State Logic
- if ((FanState == 2) && (temperature < (sp2 - hyst))) {
- newDuty = fp1;
- FanState = 1;
- ESP_LOGD(tag, "Dropping down to FanPower1");
- }
- else if ((FanState == 1) && (temperature < (sp1 - hyst))) {
- newDuty = 0;
- FanState = 0;
- ESP_LOGD(tag, "Dropping down to FanPower0");
- }
- else if ((FanState <= 1) && (temperature > sp1)) {
- newDuty = fp1;
- if (temperature > sp2) {
- newDuty = fp2;
- FanState = 2;
- ESP_LOGD(tag, "Raising up to FanPower2");
- } else {
- ESP_LOGD(tag, "Raising up to FanPower1");
- }
+ // Fan control with linear scaling and hysteresis
+ if (!fanIsOn) {
+ // Fan is off - check if we should turn it on
+ if (temperature >= sp1) {
+ fanIsOn = true;
+ ESP_LOGI(tag, "Fan turning ON - temp %.2f >= setpoint1 %.2f", temperature, sp1);
+ } else {
+ newDuty = 0.0f; // Stay off
+ }
+ } else {
+ // Fan is on - check if we should turn it off
+ if (temperature < (sp1 - hyst)) {
+ fanIsOn = false;
+ newDuty = 0.0f;
+ ESP_LOGI(tag, "Fan turning OFF - temp %.2f < (setpoint1 - hyst) %.2f", temperature, sp1 - hyst);
+ }
}
- // Apply new duty cycle if changed
- if (currentDuty != newDuty) {
- pwmOut->setOutput(newDuty);
- ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f (state=%u)", temperature, newDuty, FanState);
+ // If fan should be on, calculate linear scaling
+ if (fanIsOn) {
+ if (temperature <= sp1) {
+ // At or just above sp1, use minimum fan power
+ newDuty = fp1;
+ } else if (temperature >= sp2) {
+ // At or above sp2, use maximum fan power
+ newDuty = fp2;
+ } else {
+ // Linear interpolation between sp1 and sp2
+ float tempRange = sp2 - sp1;
+ float powerRange = fp2 - fp1;
+ float tempRatio = (temperature - sp1) / tempRange;
+ newDuty = fp1 + (tempRatio * powerRange);
+
+ ESP_LOGV(tag, "Linear scaling: temp=%.2f, ratio=%.3f, duty=%.2f", temperature, tempRatio, newDuty);
+ }
+
+ // Ensure duty is within bounds
+ newDuty = clampf(newDuty, 0.0f, maxDuty);
}
-}
\ No newline at end of file
+
+ // Apply new duty cycle if changed (with small tolerance to avoid constant updates)
+ if (fabs(currentDuty - newDuty) > 0.1f) {
+ pwmOut->setOutput(newDuty);
+ ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f%% (on=%s)", temperature, newDuty, fanIsOn ? "true" : "false");
+ }
+}
+
+/*
+void UpdateFanControl(float temperature, PWM_Output* pwmOut) {
+ if (pwmOut == nullptr) {
+ ESP_LOGW(tag, "UpdateFanControl called with null PWM output");
+ return;
+ }
+
+ if (isnan(temperature) || isinf(temperature)) {
+ ESP_LOGW(tag, "Invalid temperature reading: %f", temperature);
+ return;
+ }
+
+ static uint8_t FanState = 0;
+ tSensorSettings.temperature = temperature; // cache last temp
+ float currentDuty = pwmOut->currDuty;
+ float newDuty = currentDuty;
+
+ // Sanitize settings locally (do not modify globals)
+ float sp1 = tSensorSettings.Setpoint1;
+ float sp2 = tSensorSettings.Setpoint2;
+ float hyst = tSensorSettings.hyst;
+ float fp1 = tSensorSettings.fanPower1;
+ float fp2 = tSensorSettings.fanPower2;
+
+ if (hyst < 0.0f) hyst = 0.0f;
+ if (sp2 < sp1) {
+ // Ensure sp2 >= sp1
+ float tmp = sp1; sp1 = sp2; sp2 = tmp;
+ }
+ const float maxDuty = pwmOut->getMaxDuty();
+ fp1 = clampf(fp1, 0.0f, maxDuty);
+ fp2 = clampf(fp2, 0.0f, maxDuty);
+
+ // Fan State Logic
+ if ((FanState == 2) && (temperature < (sp2 - hyst))) {
+ newDuty = fp1;
+ FanState = 1;
+ ESP_LOGD(tag, "Dropping down to FanPower1");
+ }
+ else if ((FanState == 1) && (temperature < (sp1 - hyst))) {
+ newDuty = 0;
+ FanState = 0;
+ ESP_LOGD(tag, "Dropping down to FanPower0");
+ }
+ else if ((FanState <= 1) && (temperature > sp1)) {
+ newDuty = fp1;
+ if (temperature > sp2) {
+ newDuty = fp2;
+ FanState = 2;
+ ESP_LOGD(tag, "Raising up to FanPower2");
+ } else {
+ ESP_LOGD(tag, "Raising up to FanPower1");
+ }
+ }
+
+ // Apply new duty cycle if changed
+ if (currentDuty != newDuty) {
+ pwmOut->setOutput(newDuty);
+ ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f (state=%u)", temperature, newDuty, FanState);
+ }
+}
+*/
\ No newline at end of file
diff --git a/src/my_wifi.cpp b/src/my_wifi.cpp
index c436a14..ec00a04 100644
--- a/src/my_wifi.cpp
+++ b/src/my_wifi.cpp
@@ -145,6 +145,12 @@ void Wifi_Load_Settings(String path)
local_IP.fromString(jsonConstrainString(tag, apJson, "ip", "192.168.10.1"));
gateway.fromString(jsonConstrainString(tag, apJson, "gateway", "192.168.10.1"));
subnet.fromString(jsonConstrainString(tag, apJson, "subnet", "255.255.255.0"));
+
+ char macSuffix[13] = {0}; // Just need 2 chars + null terminator
+ get_chip_mac(macSuffix, sizeof(macSuffix)); // Only get first 2 chars of MAC
+ ap_ssid += "-"; // Add a separator
+ ap_ssid += macSuffix[0]; // Add first character
+ ap_ssid += macSuffix[1]; // Add second character
}
// Load Client settings
diff --git a/temporary/AppUpgrade copy.cpp b/temporary/AppUpgrade copy.cpp
new file mode 100644
index 0000000..e69de29