commit-9-16-25

This commit is contained in:
admin 2025-09-16 16:25:08 -07:00
parent 084de5cd44
commit 0040c64da8
28 changed files with 1931 additions and 773 deletions

14
.vscode/tasks.json vendored Normal file
View File

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

View File

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

View File

@ -1,5 +1,6 @@
{
"name": "ATALIGHTS",
"unique": true,
"lights-service": "FFE0",
"lights-char": "FFE1",
"stick-char": "FFE2",

View File

@ -256,6 +256,8 @@
<div class="status-label" id="status-wifi-client">WiFi Client: ...</div>
<div class="status-indicator" id="indicator-internet"></div>
<div class="status-label" id="status-internet">Internet: ...</div>
<div class="status-indicator" id="indicator-temperature"></div>
<div class="status-label" id="status-temperature">Temperature: ...</div>
<span></span> <div class="status-label" id="status-current-version">Current Version: ...</div>
<span></span> <div class="status-label" id="status-new-version">New Version: ...</div>
</div>
@ -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 }],
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();
}

4
docs/links.txt Normal file
View File

@ -0,0 +1,4 @@
SP110E Protocol:
https://gist.github.com/mbullington/37957501a07ad065b67d4e8d39bfe012

View File

@ -3,11 +3,15 @@
#include <Arduino.h>
#include <FastLED.h>
#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);

View File

@ -4,6 +4,8 @@
#include <FastLED.h>
#include <functional>
#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<typename TPalette>
//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);

View File

@ -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
*/
@ -242,3 +242,4 @@ UpdateMode getGlobalUpdateMode();
void setUpdateModeFilesOnly();
void setUpdateModeFirmwareOnly();
void setUpdateModeBoth();

View File

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

View File

@ -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){
return maxDuty;
}
float getDuty(void) {
return currDuty;
}
int getOutVal(void);
void setResolution(uint8_t res);
int currOutVal;
private:

View File

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

View File

@ -4,16 +4,13 @@
#include <ArduinoJson.h>
#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);

18
include/my_device.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <Arduino.h>
#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]);

View File

@ -2,21 +2,12 @@
#include <Temperature_LM75_Derived.h>
#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);

View File

@ -10,6 +10,10 @@
#include "ColorPalettes.h"
#include "global.h"
//#include <crgb.h>
#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,62 +46,160 @@ 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 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;
}
void RampUpLights(int level)
{
// 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...");
}
}
*/
void Ramp_Lights_Control_Task(void *parameters)
/*
#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)
{
static *OutputPWM* pwmOut = NULL;
pwmOut = pwmOut[sys_settings.rampLightSettings[0].pwmOutIndex];
float currPwmValue = 0.0;
float outValue = 0.0;
int timeCounter = 0;
while(1) {
sys_settings.rampLightSettings[rampIndex].pwmOutIndex
while()
vTaskDelay(100 / portTICK_PERIOD_MS);
vTaskSuspend(NULL);
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);
}
*/
@ -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;

View File

@ -10,7 +10,9 @@
#include "esp_log.h"
#include <functional>
#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<in
loop_active_flag = false;
}
void Animation_Loop_Variable(bool volatile& loop_active_flag, std::function<int()> callback) {
if (!callback) {
ESP_LOGE("Animation_Loop", "Invalid callback function");
return;
}
loop_active_flag = true;
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<int()> 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,17 +294,41 @@ 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++) {
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;
@ -271,12 +337,17 @@ void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const C
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) {
@ -439,15 +532,37 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
}
}
// 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,69 +570,9 @@ 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) {
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;
// Initialize PWM output to 0 if provided
if (pwmOutput) {
pwmOutput->setOutput(0.0f);
}
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;
});
}
*/
void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0) {
void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0)
{
if (!leds || size <= 1 || totalDurationMs <= 0) return;
const int halfSize = size / 2;
@ -530,10 +585,14 @@ void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCo
unsigned long currentTime;
unsigned long elapsedTime;
int ledsToLight, pos;
Animation_Loop(activeFlag, 90, [&]() -> int {
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;
@ -551,12 +610,156 @@ void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCo
}
prevLedsToLight = ledsToLight;
// Return 1 when complete
return (ledsToLight >= halfSize) ? 1 : 0;
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);
// 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
}
FastLED.setBrightness(origBright); // Restore original brightness
}
#define MIN_BRIGHTNESS 2
void Anim_ColorBreath(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, uint32_t timeMs, int speed) {
if (!leds || size <= 0 || colors.size <= 0 || timeMs <= 0) return;
@ -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<uint8_t>(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<uint8_t>(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;

View File

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

View File

@ -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<const uint8_t*>(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);
}
sendToAllClients(value, length);
process_BLE_SP110E_Command(value, length, pCharacteristic);
}
};
*/
// Class for handling characteristic events
class SP110ECallbacks : public NimBLECharacteristicCallbacks {
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
}
// 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);
}
public:
void onWrite(NimBLECharacteristic* pCharacteristic) override {
if (!pCharacteristic) return;
@ -94,6 +111,15 @@ public:
const uint8_t* data = reinterpret_cast<const uint8_t*>(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");

View File

@ -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<uint8_t*>(&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<uint8_t*>(&updatePacket), sizeof(updatePacket));
ESP_LOGI(tag, "Upgrade status read");
}
}
};

View File

@ -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...");

View File

@ -3,6 +3,7 @@
#include <LittleFS.h>
#include <ArduinoJson.h>
#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(),

View File

@ -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<RAMP_LIGHT*>(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;
}
}

View File

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

View File

@ -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);
// TODO... Test if this is still necessary need to configure pin 0 for some reason
// pinMode(0, INPUT); // button0/boot pin
#if FREERTOs_DIAGNOSTICS
diagnosticsTimer = xTimerCreate("Diagnostics", pdMS_TO_TICKS(60000), pdTRUE, NULL, DiagnosticsCallback);
#endif
// 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<JsonObject>(), "boardfile", "/cfg/boards/board15.json");
boothPath = jsonConstrainString(tag, doc.as<JsonObject>(), "configfile", "/cfg/booths/custom.json");
}
void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
{
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<JsonObject>(), "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<int>(tag, obj, "freq", 100, 5000, 250);
sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain<float>(tag, obj, "default", 0.0, 100.0, 0.0);
sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
// sys_settings.pwmOutSettings[pwmIndex]. = 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<int>(tag, obj, "relay-index", 0, 1, 0);
sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain<int>(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<int>(tag, sensorJson, "relay", 0, 3, 3);
sys_settings.tSensorSettings.setpoint1 = jsonConstrain<float>(tag, sensorJson, "sp1", 0.0, 100.0, 80.0);
sys_settings.tSensorSettings.setpoint2 = jsonConstrain<float>(tag, sensorJson, "sp2", 0.0, 100.0, 85.0);
sys_settings.tSensorSettings.fanPower1 = jsonConstrain<float>(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
sys_settings.tSensorSettings.fanPower2 = jsonConstrain<float>(tag, sensorJson, "fan-pwr2", 0.0, 100.0, 50.0);
sys_settings.tSensorSettings.hyst = jsonConstrain<float>(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
sys_settings.tSensorSettings.intervalMs = jsonConstrain<int>(tag, sensorJson, "interval", 1000, 30000, 5000);
ESP_LOGI(tag, "Loaded TSensor settings...");
ESP_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<int>(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<int>(tag, obj, "shift", -250, 250, 0);
sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain<int>(tag, obj, "offset", -250, 250, 0);
sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain<int>(tag, obj, "bright", 5, 255, 200);
sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain<int>(tag, obj, "core", 0, 1, 0);
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;
}

318
src/my_device.cpp Normal file
View File

@ -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 <FS.h>
#include <LittleFS.h>
#include <Arduino.h>
#include <ArduinoJson.h>
#include <esp_system.h>
#include <esp_log.h>
#include <esp_adc_cal.h>
#include <driver/adc.h>
#include <driver/ledc.h>
#include <OneButton.h>
#include <functional>
#include <memory>
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<JsonObject>(), "boardfile", "/cfg/boards/board15.json");
boothPath = jsonConstrainString(tag, doc.as<JsonObject>(), "configfile", "/cfg/booths/custom.json");
}
void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
{
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<JsonObject>(), "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<int>(tag, obj, "freq", 100, 5000, 250);
sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain<float>(tag, obj, "default", 0.0, 100.0, 0.0);
sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
// sys_settings.pwmOutSettings[pwmIndex]. = 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<int>(tag, obj, "relay-index", 0, 1, 0);
sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain<int>(tag, obj, "button-index", 0, 1, 0);
sys_settings.rampLightSettings[rampIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
sys_settings.rampLightSettings[rampIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
sys_settings.rampLightSettings[rampIndex].step = jsonConstrain<float>(tag, obj, "step", 0.1, 10.0, 1.5);
rampIndex++;
}
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<int>(tag, sensorJson, "relay", 0, 3, 3);
sys_settings.tSensorSettings.setpoint1 = jsonConstrain<float>(tag, sensorJson, "sp1", 50.0, 100.0, 80.0);
sys_settings.tSensorSettings.setpoint2 = jsonConstrain<float>(tag, sensorJson, "sp2", 60.0, 110.0, 90.0);
sys_settings.tSensorSettings.fanPower1 = jsonConstrain<float>(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
sys_settings.tSensorSettings.fanPower2 = jsonConstrain<float>(tag, sensorJson, "fan-pwr2", 50.0, 100.0, 50.0);
sys_settings.tSensorSettings.hyst = jsonConstrain<float>(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
sys_settings.tSensorSettings.intervalMs = jsonConstrain<int>(tag, sensorJson, "interval", 1000, 30000, 5000);
ESP_LOGI(tag, "Loaded TSensor settings...");
ESP_LOGI(tag, " 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<int>(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<int>(tag, obj, "shift", -250, 250, 0);
sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain<int>(tag, obj, "offset", -250, 250, 0);
sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain<int>(tag, obj, "bright", 5, 255, 200);
sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain<int>(tag, obj, "core", 0, 1, 0);
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;
}

View File

@ -1,14 +1,23 @@
#include "my_tsensor.h"
#include <Temperature_LM75_Derived.h>
#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);
@ -25,6 +34,92 @@ TI_TMP102_Compatible *tSensor = nullptr;
return v;
}
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;
}
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;
// 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;
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 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);
}
}
// 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);
}
// 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");
@ -85,3 +180,4 @@ TI_TMP102_Compatible *tSensor = nullptr;
ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f (state=%u)", temperature, newDuty, FanState);
}
}
*/

View File

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

View File