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