Refactor code structure and clean up unused files

This commit is contained in:
admin 2025-08-14 23:35:33 -07:00
parent f7950c417f
commit 88c2ff3def
19 changed files with 475 additions and 239 deletions

View File

@ -104,7 +104,7 @@
"size": 168, "size": 168,
"chip": "SK6812", "chip": "SK6812",
"rgb-order": "rgb", "rgb-order": "rgb",
"shift":-52, "shift":-5,
"offset": 0, "offset": 0,
"power-div": 0, "power-div": 0,
"i2s-ch": 0, "i2s-ch": 0,

View File

@ -27,10 +27,11 @@ typedef struct{
}BOARD_PINS; }BOARD_PINS;
extern BOARD_PINS* thisBoardPins; extern BOARD_PINS* thisBoardPins;
#define setStatusPin1(state) digitalWrite(thisBoardPins->stat[0], state); // Safe status pin macros: only write when configured (>=0)
#define setStatusPin2(state) digitalWrite(thisBoardPins->stat[1], state); #define setStatusPin1(state) do { if (thisBoardPins && thisBoardPins->stat[0] >= 0) digitalWrite(thisBoardPins->stat[0], state); } while(0)
#define setStatusPin2(state) do { if (thisBoardPins && thisBoardPins->stat[1] >= 0) digitalWrite(thisBoardPins->stat[1], state); } while(0)
void Load_Board_Pins(BOARD_PINS& boardPins, String& path); bool Load_Board_Pins(BOARD_PINS& boardPins, const String& path);
void Init_Board_Basic(BOARD_PINS& boardPins); void Init_Board_Basic(BOARD_PINS& boardPins);
void updateFanControl(float temperature); void updateFanControl(float temperature);
void Initialize_Rear_Control(int relayIndex, int buttonIndex, int rampTime, int steps, float min, float max); void Initialize_Rear_Control(int relayIndex, int buttonIndex, int rampTime, int steps, float min, float max);

View File

@ -4,11 +4,11 @@
#include "OneButton.h" #include "OneButton.h"
extern OneButton *boardButtons[3]; extern OneButton *boardButtons[3];
#define Update_Buttons() boardButtons[1]->tick(); boardButtons[2]->tick(); boardButtons[3]->tick();
void Init_ButtonEvents(int8_t (&pin)[3]); void Init_ButtonEvents(int8_t (&pin)[3]);
// Safely tick any initialized buttons (nullptr-aware)
void Update_Buttons();
void btn1_click(); void btn1_click();
void btn1_doubleClick(); void btn1_doubleClick();
void btn1_LongPressStart(); void btn1_LongPressStart();

View File

@ -34,6 +34,6 @@ build_flags =
-D CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=1 -D CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=1
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
-D CONFIG_ARDUHAL_LOG_COLORS=1 -D CONFIG_ARDUHAL_LOG_COLORS=1
upload_port = COM11 upload_port = COM5
debug_init_break = tbreak setup debug_init_break = tbreak setup
monitor_port = COM11 monitor_port = COM5

View File

@ -19,7 +19,7 @@ TaskHandle_t Animation_Task_Handle;
LEDSTRIP_SETTINGS ledSettings[2]; LEDSTRIP_SETTINGS ledSettings[2];
volatile bool AnimationLooping = false; volatile bool AnimationLooping = false;
ANIM_EVENT prevAnimEvent = {0}; ANIM_EVENT prevAnimEvent = {0};
QueueHandle_t animationQueue = xQueueCreate( 1, sizeof( ANIM_EVENT ) ); QueueHandle_t animationQueue = xQueueCreate( 4, sizeof( ANIM_EVENT ) );
void Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){ void Lights_Set_Animation(int animIndex, uint8_t red, uint8_t grn, uint8_t blu){
@ -352,8 +352,12 @@ void Lights_Control_Task(void *parameters){
ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex); ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
switch (AnimEvent.AnimationIndex) { switch (AnimEvent.AnimationIndex) {
case -3: // Set Pixel by index case -3: // Set Pixel by index
if (AnimEvent.data.data[7] >= 0 && AnimEvent.data.data[7] < ledSettings[0].size) {
ledSettings[0].leds[AnimEvent.data.data[7]] = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu); ledSettings[0].leds[AnimEvent.data.data[7]] = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu);
FastLED.show(); FastLED.show();
} else {
ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[7]);
}
break; break;
case -2: // Fill Static Color case -2: // Fill Static Color
col = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu); col = CRGB(AnimEvent.data.red, AnimEvent.data.grn, AnimEvent.data.blu);

View File

@ -71,7 +71,16 @@ void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickTyp
xLastWakeTime = xTaskGetTickCount(); xLastWakeTime = xTaskGetTickCount();
// Call animation function // Call animation function
int speedIncrease = callback(); // Call animation function int speedIncrease = 0;
try {
speedIncrease = callback(); // Call animation function
} catch (const std::exception& e) {
ESP_LOGE("Animation_Loop_Duration", "Callback exception: %s", e.what());
break;
} catch (...) {
ESP_LOGE("Animation_Loop_Duration", "Callback unknown exception");
break;
}
if(!loop_active_flag) return; if(!loop_active_flag) return;
@ -94,11 +103,7 @@ void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickTyp
TickType_t totalElapsed = xTaskGetTickCount() - startTicks; TickType_t totalElapsed = xTaskGetTickCount() - startTicks;
if (totalElapsed >= durationMs) { if (totalElapsed >= durationMs) {
while(loop_active_flag){ // Auto-terminate the loop when duration reached
if (ulTaskNotifyTake(pdTRUE, 50)) {
return;
}
}
break; break;
} }
} }
@ -121,7 +126,16 @@ void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t
for(;;) { for(;;) {
xLastWakeTime = xTaskGetTickCount(); xLastWakeTime = xTaskGetTickCount();
int speedIncrease = callback(); // Call animation function int speedIncrease = 0;
try {
speedIncrease = callback(); // Call animation function
} catch (const std::exception& e) {
ESP_LOGE("Animation_Loop_Cycles", "Callback exception: %s", e.what());
break;
} catch (...) {
ESP_LOGE("Animation_Loop_Cycles", "Callback unknown exception");
break;
}
if(!loop_active_flag) return; if(!loop_active_flag) return;
@ -139,14 +153,8 @@ void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t
// Delay and Check for termination request // Delay and Check for termination request
if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; } if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; }
// Check if cycles reached and wait for loop_active_flag // Check if cycles reached and exit
if (loop_cycle_count >= loop_cycles) { if (loop_cycle_count >= loop_cycles) {
while(loop_active_flag){
if (ulTaskNotifyTake(pdTRUE, 50)) {
//loop_active_flag = false;
break;
}
}
break; break;
} }
@ -189,6 +197,7 @@ void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const
// Calculate half size for mirroring // Calculate half size for mirroring
const int halfSize = size / 2; const int halfSize = size / 2;
if (halfSize <= 0) return;
// Create heat array for half the size // Create heat array for half the size
uint8_t* heat = new (std::nothrow) uint8_t[halfSize]; uint8_t* heat = new (std::nothrow) uint8_t[halfSize];
@ -211,7 +220,8 @@ void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const
// Randomly ignite new sparks at bottom // Randomly ignite new sparks at bottom
if(random8() < FIRE_SPARKING) { if(random8() < FIRE_SPARKING) {
y = random8(7); // ensure y is in-bounds for small strips
y = min<int>(random8(7), halfSize - 1);
heat[y] = qadd8(heat[y], random8(160, 240)); heat[y] = qadd8(heat[y], random8(160, 240));
} }
@ -382,11 +392,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
// Set fade factor // Set fade factor
uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1; uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1;
// Initialize comet positions with fixed array // Initialize comet positions with fixed array, evenly distributed
int cometPositions[MAX_COMETS] = {0}; int cometPositions[MAX_COMETS] = {0};
int spacing = size / totalComets;
for (int i = 0; i < totalComets; i++) { for (int i = 0; i < totalComets; i++) {
cometPositions[i] = i * spacing; // Even distribution even when size not divisible by totalComets
cometPositions[i] = (i * size) / totalComets;
} }
// Animation loop // Animation loop
@ -414,9 +424,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
} }
// Draw comet with solid color // Draw comet with solid color
colorPack.col[i % colorPack.size]; color = colorPack.col[i % colorPack.size];
for (int j = 0; j < cometSize; j++) { for (int j = 0; j < cometSize; j++) {
pos = (cometPositions[i] - j + size) % size; // Tail follows the direction of movement
pos = direction ? (cometPositions[i] - j) : (cometPositions[i] + j);
pos = (pos % size + size) % size; // safe modulus
leds[pos] += color; leds[pos] += color;
} }
} }
@ -439,7 +451,7 @@ 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) { void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0) {
if (!leds || size <= 0 || totalDurationMs <= 0) return; if (!leds || size <= 1 || totalDurationMs <= 0) return;
const int halfSize = size / 2; const int halfSize = size / 2;
const float msPerLed = totalDurationMs / (float)halfSize; const float msPerLed = totalDurationMs / (float)halfSize;
@ -486,35 +498,35 @@ void Anim_ColorBreath(bool volatile& activeFlag, CRGB* leds, int size, const COL
unsigned long startTime = millis(); unsigned long startTime = millis();
const uint32_t halfTime = timeMs / 2; const uint32_t halfTime = timeMs / 2;
uint8_t origBright = FastLED.getBrightness(); const uint8_t origBright = FastLED.getBrightness();
FastLED.setBrightness(255); FastLED.setBrightness(255);
uint8_t brightness = MIN_BRIGHTNESS; uint8_t breath = MIN_BRIGHTNESS;
uint32_t elapsedTime; uint32_t elapsedTime;
CRGB scaledColor, correctedColor;
unsigned long currentTime; unsigned long currentTime;
CRGB outColor;
Animation_Loop(activeFlag, speed, [&]() -> int { Animation_Loop(activeFlag, speed, [&]() -> int {
// Calculate elapsed time in current breath cycle // Elapsed time in the current breath cycle
currentTime = millis(); currentTime = millis();
elapsedTime = currentTime - startTime; elapsedTime = currentTime - startTime;
// Calculate brightness using a linear approach // Triangle wave brightness: up for half, down for half
if (elapsedTime < halfTime) { if (elapsedTime < halfTime) {
brightness = map(elapsedTime, 0, halfTime, MIN_BRIGHTNESS, 255); // Brighten breath = map(elapsedTime, 0, halfTime, MIN_BRIGHTNESS, 255); // Brighten
} else { } else {
brightness = map(elapsedTime, halfTime, timeMs, 255, MIN_BRIGHTNESS); // Dim breath = map(elapsedTime, halfTime, timeMs, 255, MIN_BRIGHTNESS); // Dim
} }
// Scale the color directly // Combine breath with original global brightness (rounded)
scaledColor = colors.col[colorIndex]; uint16_t prod = static_cast<uint16_t>(breath) * static_cast<uint16_t>(origBright);
scaledColor.nscale8(brightness * origBright / 255); uint8_t finalBright = static_cast<uint8_t>((prod + 127) / 255);
// Correct the color scale for vision // Apply perceptual scaling once with combined brightness
correctedColor = scaledColor; outColor = colors.col[colorIndex];
correctedColor.nscale8_video(brightness); outColor.nscale8_video(finalBright);
// Fill all LEDs with scaled color // Fill all LEDs with scaled color
fill_solid(leds, size, correctedColor); fill_solid(leds, size, outColor);
FastLED.show(); FastLED.show();
if (elapsedTime >= timeMs) { if (elapsedTime >= timeMs) {
@ -534,21 +546,17 @@ void Anim_GradientRotate(bool volatile& activeFlag, CRGB* leds, int size, const
CRGB color1, color2; CRGB color1, color2;
// Create initial gradient // Create initial gradient: evenly distribute blends across the strip
int segmentLength = size / colors.size; // For i in [0..size-1], compute position in color space [0..colors.size)
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
// Determine which color segment we're in uint32_t pos256 = (uint32_t)i * (uint32_t)colors.size * 256u / (uint32_t)size; // 8.8 fixed point
int segment = i / segmentLength; int segment = (int)(pos256 >> 8); // 0..colors.size-1
uint8_t blendPos = (uint8_t)(pos256 & 0xFF); // 0..255
int nextSegment = (segment + 1) % colors.size; int nextSegment = (segment + 1) % colors.size;
// Calculate blend amount within segment // Local copies for blending
uint8_t blendPos = map(i % segmentLength, 0, segmentLength - 1, 0, 255);
// Create local copies for blending
color1 = colors.col[segment]; color1 = colors.col[segment];
color2 = colors.col[nextSegment]; color2 = colors.col[nextSegment];
// Set initial gradient
leds[i] = blend(color1, color2, blendPos); leds[i] = blend(color1, color2, blendPos);
} }

View File

@ -4,8 +4,10 @@
#include <LittleFS.h> #include <LittleFS.h>
#include <memory> #include <memory>
#include "global.h" #include "global.h"
#include "jsonConstrain.h" #include "JsonConstrain.h"
#include "BLE_UpdateService.h" #include "BLE_UpdateService.h"
#include <HTTPClient.h>
#include <Update.h>
static const char* TAG = "AppUpdater"; static const char* TAG = "AppUpdater";
TaskHandle_t Update_Task_Handle = NULL; TaskHandle_t Update_Task_Handle = NULL;
@ -90,7 +92,8 @@ bool AppUpdater::checkManifest() {
// Check if an update is available // Check if an update is available
updateAvailable = false; updateAvailable = false;
if (otaVersion < localVersion) { // Only mark update available if remote is strictly newer than local
if (otaVersion <= localVersion) {
ESP_LOGI(TAG, "No updates available"); ESP_LOGI(TAG, "No updates available");
return false; return false;
}else{ }else{
@ -163,7 +166,8 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
//updateProgress(UpdateStatus::DOWNLOADING, 0, localPath); //updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
// Single pass: Save file and calculate MD5 if (contentLength > 0) {
// Single pass with known content length
while (totalRead < contentLength) { while (totalRead < contentLength) {
size_t available = stream->available(); size_t available = stream->available();
if (available) { if (available) {
@ -184,6 +188,26 @@ bool AppUpdater::verifyAndSaveFile(WiFiClient* stream, size_t contentLength, con
} }
yield(); yield();
} }
} else {
// Unknown content length: read until stream ends
for (;;) {
size_t readLen = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
if (readLen == 0) {
break;
}
if (file.write(downloadBuffer.get(), readLen) != readLen) {
ESP_LOGE(TAG, "Failed to write to temporary file");
file.close();
fileSystem.remove(tempPath.c_str());
return false;
}
md5.add(downloadBuffer.get(), readLen);
totalRead += readLen;
// Progress unknown; emit periodic heartbeats at 0%
updateProgress(UpdateStatus::DOWNLOADING, 0, localPath);
yield();
}
}
file.close(); file.close();
md5.calculate(); md5.calculate();
@ -286,7 +310,7 @@ bool AppUpdater::updateApp() {
// Check available space // Check available space
size_t firmwareSize = http.getSize(); size_t firmwareSize = http.getSize();
if (!Update.begin(firmwareSize)) { if (!Update.begin(firmwareSize > 0 ? firmwareSize : UPDATE_SIZE_UNKNOWN)) {
ESP_LOGE(TAG, "Firmware: Not enough space for update"); ESP_LOGE(TAG, "Firmware: Not enough space for update");
updateProgress(UpdateStatus::ERROR, 0, "Firmware: Not enough space for update"); updateProgress(UpdateStatus::ERROR, 0, "Firmware: Not enough space for update");
http.end(); http.end();
@ -299,6 +323,7 @@ bool AppUpdater::updateApp() {
// Download and verify firmware // Download and verify firmware
WiFiClient* stream = http.getStreamPtr(); WiFiClient* stream = http.getStreamPtr();
if (firmwareSize > 0) {
size_t remaining = firmwareSize; size_t remaining = firmwareSize;
while (remaining > 0) { while (remaining > 0) {
size_t chunk = std::min(remaining, size_t(BUFFER_SIZE)); size_t chunk = std::min(remaining, size_t(BUFFER_SIZE));
@ -307,7 +332,6 @@ bool AppUpdater::updateApp() {
// Check for timeout // Check for timeout
if (read == 0) { if (read == 0) {
ESP_LOGE(TAG, "Read timeout"); ESP_LOGE(TAG, "Read timeout");
Update.abort(); Update.abort();
http.end(); http.end();
return false; return false;
@ -325,6 +349,21 @@ bool AppUpdater::updateApp() {
remaining -= read; remaining -= read;
updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware"); updateProgress(UpdateStatus::DOWNLOADING, (firmwareSize - remaining) * 100 / firmwareSize, "firmware");
} }
} else {
// Unknown size: stream until end
for (;;) {
size_t read = stream->readBytes(downloadBuffer.get(), BUFFER_SIZE);
if (read == 0) break;
md5.add(downloadBuffer.get(), read);
if (Update.write(downloadBuffer.get(), read) != read) {
ESP_LOGE(TAG, "Write failed");
Update.abort();
http.end();
return false;
}
updateProgress(UpdateStatus::DOWNLOADING, 0, "firmware");
}
}
// Verify MD5 // Verify MD5
md5.calculate(); md5.calculate();
@ -469,6 +508,7 @@ void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const ch
const char* msg; const char* msg;
bool isComplete = false; bool isComplete = false;
const char* safeMsg = message ? message : "";
switch (newStatus) { switch (newStatus) {
case AppUpdater::UpdateStatus::IDLE: case AppUpdater::UpdateStatus::IDLE:
snprintf(buffer, sizeof(buffer), "Update idle"); snprintf(buffer, sizeof(buffer), "Update idle");
@ -478,23 +518,23 @@ void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const ch
msg = message ? message : ""; msg = message ? message : "";
break; break;
case AppUpdater::UpdateStatus::DOWNLOADING: case AppUpdater::UpdateStatus::DOWNLOADING:
snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", message, percentage); snprintf(buffer, sizeof(buffer), "%s: Download progress: %d%%", safeMsg, percentage);
msg = buffer; msg = buffer;
break; break;
case AppUpdater::UpdateStatus::VERIFYING: case AppUpdater::UpdateStatus::VERIFYING:
snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", message, percentage); snprintf(buffer, sizeof(buffer), "%s: Verifying update: %d%%", safeMsg, percentage);
msg = buffer; msg = buffer;
break; break;
case AppUpdater::UpdateStatus::FILE_SKIPPED: case AppUpdater::UpdateStatus::FILE_SKIPPED:
snprintf(buffer, sizeof(buffer), "%s: Skipping file update, already up to date", message); snprintf(buffer, sizeof(buffer), "%s: Skipping file update, already up to date", safeMsg);
msg = buffer; msg = buffer;
break; break;
case AppUpdater::UpdateStatus::FILE_SAVED: case AppUpdater::UpdateStatus::FILE_SAVED:
snprintf(buffer, sizeof(buffer), "%s: File Saved", message); snprintf(buffer, sizeof(buffer), "%s: File Saved", safeMsg);
msg = buffer; msg = buffer;
break; break;
case AppUpdater::UpdateStatus::MD5_FAILED: case AppUpdater::UpdateStatus::MD5_FAILED:
snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", message); snprintf(buffer, sizeof(buffer), "%s: MD5 Verification Failed", safeMsg);
msg = buffer; msg = buffer;
break; break;
case AppUpdater::UpdateStatus::COMPLETE: case AppUpdater::UpdateStatus::COMPLETE:
@ -503,7 +543,7 @@ void updateProgress(AppUpdater::UpdateStatus newStatus, int percentage, const ch
isComplete = true; isComplete = true;
break; break;
case AppUpdater::UpdateStatus::ERROR: case AppUpdater::UpdateStatus::ERROR:
snprintf(buffer, sizeof(buffer), "Error!: %s", message); snprintf(buffer, sizeof(buffer), "Error!: %s", safeMsg);
msg = buffer; msg = buffer;
break; break;
default: default:

View File

@ -55,7 +55,11 @@ class SP110ECallbacks : public NimBLECharacteristicCallbacks {
std::string rawValue = pCharacteristic->getValue(); std::string rawValue = pCharacteristic->getValue();
const uint8_t* value = reinterpret_cast<const uint8_t*>(rawValue.data()); const uint8_t* value = reinterpret_cast<const uint8_t*>(rawValue.data());
size_t length = rawValue.length(); 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); 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); sendToAllClients(value, length);
process_BLE_SP110E_Command(value, length, pCharacteristic); process_BLE_SP110E_Command(value, length, pCharacteristic);

View File

@ -35,20 +35,36 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str()); ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str());
if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials
JsonDocument doc; // Expecting: "wifi-connect:{\"ssid\":...,\"pass\":...}"
deserializeJson(doc, value.substr(13)); size_t jsonStart = (value.size() > 12 && value[12] == ':') ? 13 : 12;
if (value.size() <= jsonStart) {
ESP_LOGW(tag, "wifi-connect command missing JSON payload");
return;
}
DynamicJsonDocument doc(512);
DeserializationError err = deserializeJson(doc, value.substr(jsonStart));
if (err) {
ESP_LOGW(tag, "JSON parse error for wifi-connect: %s", err.c_str());
return;
}
JsonObject wifiJson = doc.as<JsonObject>(); JsonObject wifiJson = doc.as<JsonObject>();
String ssid = wifiJson["ssid"].as<String>(); String ssid = wifiJson["ssid"].as<String>();
String pass = wifiJson["pass"].as<String>(); String pass = wifiJson["pass"].as<String>();
ESP_LOGI(tag, "Wifi Credentials: %s, %s", ssid.c_str(), pass.c_str()); if (ssid.length() == 0) {
ESP_LOGW(tag, "wifi-connect missing ssid");
return;
}
ESP_LOGI(tag, "WiFi connect requested: ssid='%s', pass len=%u", ssid.c_str(), (unsigned)pass.length());
bool status = StartWifiConnectTask(ssid, pass); bool started = StartWifiConnectTask(ssid, pass);
if(status == true){ if (started) {
updatePacket.wifiStatus = WIFI_DISCONNECTED; updatePacket.wifiStatus = WIFI_DISCONNECTED;
updatePacket.wifiOnline = false; updatePacket.wifiOnline = false;
updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0; updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
} else { } else {
ESP_LOGI(tag, "Failed to start WiFi connection task"); ESP_LOGW(tag, "Failed to start WiFi connection task");
} }
} }
else if (value.compare("version-check") == 0) { // Check if new version is available else if (value.compare("version-check") == 0) { // Check if new version is available
@ -75,21 +91,18 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
updatePacket.wifiOnline = InternetAvailable; updatePacket.wifiOnline = InternetAvailable;
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
updatePacket.wifiStatus = WIFI_CONNECTED; updatePacket.wifiStatus = WIFI_CONNECTED;
if(updatePacket.wifiIP[0] == 0){ IPAddress ip = WiFi.localIP();
updatePacket.wifiIP[0] = WiFi.localIP()[0]; updatePacket.wifiIP[0] = ip[0];
updatePacket.wifiIP[1] = WiFi.localIP()[1]; updatePacket.wifiIP[1] = ip[1];
updatePacket.wifiIP[2] = WiFi.localIP()[2]; updatePacket.wifiIP[2] = ip[2];
updatePacket.wifiIP[3] = WiFi.localIP()[3]; updatePacket.wifiIP[3] = ip[3];
}
} else { } else {
updatePacket.wifiStatus = WIFI_DISCONNECTED; updatePacket.wifiStatus = WIFI_DISCONNECTED;
if(updatePacket.wifiIP[0] > 0){
updatePacket.wifiIP[0] = 0; updatePacket.wifiIP[0] = 0;
updatePacket.wifiIP[1] = 0; updatePacket.wifiIP[1] = 0;
updatePacket.wifiIP[2] = 0; updatePacket.wifiIP[2] = 0;
updatePacket.wifiIP[3] = 0; updatePacket.wifiIP[3] = 0;
} }
}
//update version //update version
if(otaVersion.major() != 0){ if(otaVersion.major() != 0){
@ -99,19 +112,24 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
updatePacket.newVersion[2] = otaVersion.patch(); updatePacket.newVersion[2] = otaVersion.patch();
} }
// Only populate the control characteristic with the status packet
if (pCharacteristic == pUpgradeCharacteristic1) {
pCharacteristic->setValue(reinterpret_cast<uint8_t*>(&updatePacket), sizeof(updatePacket)); pCharacteristic->setValue(reinterpret_cast<uint8_t*>(&updatePacket), sizeof(updatePacket));
ESP_LOGI(tag, "Upgrade Char read"); ESP_LOGI(tag, "Upgrade status read");
}
} }
}; };
void bleUpgrade_send_message(String s){ void bleUpgrade_send_message(String s){
if(pUpgradeCharacteristic2){ if(pUpgradeCharacteristic2){
if (s != nullptr) { if (s.length() == 0) {
pUpgradeCharacteristic2->setValue(s); return;
}
// Set value and notify only if there are subscribers to avoid unnecessary work
pUpgradeCharacteristic2->setValue(s.c_str());
if (pUpgradeCharacteristic2->getSubscribedCount() > 0) {
pUpgradeCharacteristic2->notify(); pUpgradeCharacteristic2->notify();
} else {
ESP_LOGW(tag, "Null string passed to bleUpgrade_send_message");
} }
} }
} }

View File

@ -13,18 +13,22 @@ class ServerCallbacks : public NimBLEServerCallbacks {
// Ensure advertising remains active even after a client connects // Ensure advertising remains active even after a client connects
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
if (pAdvertising != nullptr) { if (pAdvertising != nullptr) {
if (!pAdvertising->isAdvertising()) {
pAdvertising->start(); pAdvertising->start();
} }
} }
}
void onDisconnect(NimBLEServer* pServer) override { void onDisconnect(NimBLEServer* pServer) override {
ESP_LOGI(tag, "Client disconnected"); ESP_LOGI(tag, "Client disconnected");
// Restart advertising on disconnect to keep it active always // Restart advertising on disconnect to keep it active always
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
if (pAdvertising != nullptr) { if (pAdvertising != nullptr) {
if (!pAdvertising->isAdvertising()) {
pAdvertising->start(); pAdvertising->start();
} }
} }
}
}; };
void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) { void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
@ -35,6 +39,7 @@ void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
//NimBLEDevice::init("ATALIGHTS"); //NimBLEDevice::init("ATALIGHTS");
NimBLEDevice::init("SP110E-ATA"); NimBLEDevice::init("SP110E-ATA");
//NimBLEDevice::setMTU(247); // Set preferred MTU size (max 247 for BLE) //NimBLEDevice::setMTU(247); // Set preferred MTU size (max 247 for BLE)
isInitialized = true;
} }
NimBLEServer *pServer = NimBLEDevice::createServer(); NimBLEServer *pServer = NimBLEDevice::createServer();
@ -59,7 +64,7 @@ void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
// Start BLE advertising // Start BLE advertising
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
if (pAdvertising != nullptr) { if (pAdvertising != nullptr) {
if (!pAdvertising->start()) { if (!pAdvertising->isAdvertising() && !pAdvertising->start()) {
ESP_LOGE(tag, "Failed to start advertising"); ESP_LOGE(tag, "Failed to start advertising");
return; return;
} }

View File

@ -3,7 +3,10 @@
#include <esp_log.h> #include <esp_log.h>
template <typename T> template <typename T>
void logConstrainedValue(const char* tag, const char* key, T value); void logConstrainedValue(const char* tag, const char* key, T) {
// Generic fallback when no specialization provided
ESP_LOGD(tag, "Key [%s] value set", key);
}
template <> template <>
void logConstrainedValue<int>(const char* tag, const char* key, int value) { void logConstrainedValue<int>(const char* tag, const char* key, int value) {
@ -16,7 +19,10 @@ void logConstrainedValue<float>(const char* tag, const char* key, float value) {
} }
template <typename T> template <typename T>
void logClamping(const char* tag, const char* key, T value, T limit, const char* condition); void logClamping(const char* tag, const char* key, T, T, const char* condition) {
// Generic fallback when no specialization provided
ESP_LOGW(tag, "Key [%s] value too %s. Clamping.", key, condition);
}
template <> template <>
void logClamping<int>(const char* tag, const char* key, int value, int limit, const char* condition) { void logClamping<int>(const char* tag, const char* key, int value, int limit, const char* condition) {
@ -72,12 +78,12 @@ const char* jsonConstrainChar(const char *tag, const JsonObject &jsonObject, con
} }
String value = jsonObject[key].as<String>(); String value = jsonObject[key].as<String>();
if (value.isEmpty()) { if (value.length() == 0) {
ESP_LOGW(tag, "Key [%s] value is empty. Using default value.", key); ESP_LOGW(tag, "Key [%s] value is empty. Using default value.", key);
return strdup(def); return strdup(def);
} }
ESP_LOGD(tag, "Key [%s] value: %s", key, value); ESP_LOGD(tag, "Key [%s] value: %s", key, value.c_str());
return strdup(value.c_str()); return strdup(value.c_str());
} }
@ -93,7 +99,7 @@ String jsonConstrainString(const char *tag, const JsonObject &jsonObject, const
String value = jsonObject[key].as<String>(); String value = jsonObject[key].as<String>();
// Check if the value is empty // Check if the value is empty
if (value.isEmpty()) { if (value.length() == 0) {
ESP_LOGW(tag, "Key [%s] value is empty. Using default value [%s].", key, def.c_str()); ESP_LOGW(tag, "Key [%s] value is empty. Using default value [%s].", key, def.c_str());
return def; return def;
} }

View File

@ -1,5 +1,6 @@
#include "PWM_Output.h" #include "PWM_Output.h"
#include <Arduino.h> #include <Arduino.h>
#include "global.h"
const char* tag = "pwmout"; const char* tag = "pwmout";
@ -56,6 +57,11 @@ void PWM_Output::setOutput(float duty){
outDutyVal = static_cast<int>(duty * this->standardFactor); outDutyVal = static_cast<int>(duty * this->standardFactor);
} }
// Clamp to valid resolution range [0, 2^res - 1]
int maxVal = static_cast<int>(binaryPow[this->res]);
if (outDutyVal < 0) outDutyVal = 0;
if (outDutyVal > maxVal) outDutyVal = maxVal;
ledcWrite(this->ch, outDutyVal); ledcWrite(this->ch, outDutyVal);
this->currOutVal = outDutyVal; this->currOutVal = outDutyVal;
this->currDuty = duty; this->currDuty = duty;
@ -77,8 +83,9 @@ void PWM_Output::setResolution(uint8_t res){
if(this->res < 4) this->res = 4; if(this->res < 4) this->res = 4;
if(this->res > 16) this->res = 16; if(this->res > 16) this->res = 16;
this->standardFactor = binaryPow[res] * 0.01; // Use the clamped resolution when computing factors
this->visionFactor = binaryPow[res] * 0.0001; this->standardFactor = binaryPow[this->res] * 0.01f;
this->visionFactor = binaryPow[this->res] * 0.0001f;
ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor); ESP_LOGD(tag, "factor=%f, vision=%f", this->standardFactor, this->visionFactor);
} }

View File

@ -3,6 +3,14 @@
#define MAX_HUE 360.0 #define MAX_HUE 360.0
// Normalize a hue into [0, MAX_HUE)
static inline float wrapHue(float h)
{
while (h >= MAX_HUE) h -= MAX_HUE;
while (h < 0.0f) h += MAX_HUE;
return h;
}
/************************* CLASS - HUE PALLET DISPENSER ************************/ /************************* CLASS - HUE PALLET DISPENSER ************************/
@ -11,59 +19,51 @@ HUE_PALLET_DISPENSER::HUE_PALLET_DISPENSER(void){
} }
void HUE_PALLET_DISPENSER::Initialize(float hue, float range, int colSteps){ void HUE_PALLET_DISPENSER::Initialize(float hue, float range, int colSteps){
this->range = range; // Clamp and normalize inputs
this->hueSteps = colSteps; this->range = constrain(range, 0.0f, (float)MAX_HUE);
this->hueSteps = (colSteps < 0) ? 0 : colSteps;
this->startHue = hue - this->range / 2;
if(this->startHue < 0.0){
this->startHue += MAX_HUE;
}else if(this->startHue > MAX_HUE){
this->startHue -= MAX_HUE;
}
this->startHue = wrapHue(hue - (this->range * 0.5f));
this->hueIndex = 0; this->hueIndex = 0;
if (this->hueSteps <= 1) { if (this->hueSteps <= 1) {
this->hueIncrement = 0.0; this->hueIncrement = 0.0f;
this->currHue = hue; this->currHue = wrapHue(hue);
} else { } else {
this->hueIncrement = this->range / (this->hueSteps - 1); this->hueIncrement = (this->range / (this->hueSteps - 1));
this->currHue = this->startHue;
} }
} }
int HUE_PALLET_DISPENSER::GetNextPalletHue(void){ int HUE_PALLET_DISPENSER::GetNextPalletHue(void){
if (this->hueSteps > 1) { if (this->hueSteps > 1) {
this->currHue = this->startHue + this->hueIndex * this->hueIncrement; this->currHue = wrapHue(this->startHue + (this->hueIndex * this->hueIncrement));
if(this->currHue < 0){ this->hueIndex = (this->hueIndex + 1) % this->hueSteps;
this->currHue += MAX_HUE;
}else if(this->currHue > MAX_HUE){
this->currHue -= MAX_HUE;
} }
// TODO Remove later // Convert to integer hue in [0, 359]
//rgbpixel_t p = HUEtoRGB(huePallet.currHue); int out = (int)(this->currHue + 0.5f);
//Log.traceln("<anim> index: %d, hue= %F, col: %d, %d, %d", huePallet.hueIndex, huePallet.currHue, p.red, p.grn, p.blu); if (out >= (int)MAX_HUE) out -= (int)MAX_HUE;
if (out < 0) out = 0; // safety
this->hueIndex = ++this->hueIndex % this->hueSteps; return out;
return round(this->currHue);
}else{
return round(this->currHue);
}
} }
float HUE_PALLET_DISPENSER::PeekNextPalletHue(int hueOffset){ float HUE_PALLET_DISPENSER::PeekNextPalletHue(int hueOffset){
float tempHue = this->startHue + (this->hueIndex + hueOffset) * this->hueIncrement; float tempHue = wrapHue(this->startHue + ((this->hueIndex + hueOffset) * this->hueIncrement));
if(tempHue < 0){
tempHue += MAX_HUE;
}else if(tempHue > MAX_HUE){
tempHue -= MAX_HUE;
}
return tempHue; return tempHue;
} }
void HUE_PALLET_DISPENSER::SetHueIndex(int hueIndex){ void HUE_PALLET_DISPENSER::SetHueIndex(int hueIndex){
this->currHue = hueIndex; if (this->hueSteps > 0) {
int n = this->hueSteps;
int idx = hueIndex % n;
if (idx < 0) idx += n;
this->hueIndex = idx;
if (this->hueSteps > 1) {
this->currHue = wrapHue(this->startHue + (this->hueIndex * this->hueIncrement));
}
} else {
this->hueIndex = 0;
}
} }

View File

@ -1,5 +1,5 @@
#include "global.h" #include "global.h"
#include <Wifi.h> #include <WiFi.h>
#include <FS.h> #include <FS.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
@ -257,6 +257,12 @@ void Log_CPU_Load(void) {
// Get task stats // Get task stats
task_count = uxTaskGetSystemState(task_array, task_count, &total_runtime); task_count = uxTaskGetSystemState(task_array, task_count, &total_runtime);
if (total_runtime == 0) {
ESP_LOGW(tag, "Runtime stats not ready yet (total_runtime==0)");
free(stats);
free(task_array);
return;
}
// Find IDLE tasks // Find IDLE tasks
for (UBaseType_t i = 0; i < task_count; i++) { for (UBaseType_t i = 0; i < task_count; i++) {

View File

@ -93,8 +93,7 @@ void setup()
{ {
// Serial Port // Serial Port
Serial.begin(115200); Serial.begin(115200);
while (!Serial) while (!Serial);
;
// Initialize I2C Port for TSensor, ... // Initialize I2C Port for TSensor, ...
Wire.begin(I2C_SDA1_Pin, I2C_SCL1_Pin); Wire.begin(I2C_SDA1_Pin, I2C_SCL1_Pin);
@ -281,7 +280,7 @@ void loop()
{ {
static bool ledState = false; static bool ledState = false;
// digitalWrite(sys_settings.boardPins.stat[1], ledState = !ledState); // digitalWrite(sys_settings.boardPins.stat[1], ledState = !ledState);
setStatusPin2(ledState = !ledState) setStatusPin2(ledState = !ledState);
} }
} }

View File

@ -12,13 +12,29 @@ static const char* tag = "board";
BOARD_PINS* thisBoardPins; BOARD_PINS* thisBoardPins;
void Load_Board_Pins(BOARD_PINS& boardPins, String& path){ // Basic validator for ESP32-S3 GPIOs; rejects common reserved/USB/strap pins
static bool isValidGpio(int pin) {
if (pin < 0 || pin > 48) return false;
switch (pin) {
case 19: // USB D-
case 20: // USB D+
case 45: // strapping
case 46: // strapping
return false;
default:
return true;
}
}
bool Load_Board_Pins(BOARD_PINS& boardPins, const String& path){
// Default initialize to -1 to avoid stale values on partial loads
memset(&boardPins, -1, sizeof(boardPins));
thisBoardPins = &boardPins; thisBoardPins = &boardPins;
File file = LittleFS.open(path); File file = LittleFS.open(path);
if (!file) { if (!file) {
ESP_LOGE(tag, "Error opening %s...", path.c_str()); ESP_LOGE(tag, "Error opening %s...", path.c_str());
return; return false;
} }
JsonDocument doc; JsonDocument doc;
@ -27,7 +43,7 @@ void Load_Board_Pins(BOARD_PINS& boardPins, String& path){
if (error) { if (error) {
ESP_LOGE(tag, "%s deserialize error!..", path.c_str()); ESP_LOGE(tag, "%s deserialize error!..", path.c_str());
return; return false;
} }
JsonObject boardJson = doc.as<JsonObject>(); JsonObject boardJson = doc.as<JsonObject>();
@ -60,9 +76,28 @@ void Load_Board_Pins(BOARD_PINS& boardPins, String& path){
boardPins.rf433tx = jsonConstrain<int>(tag, boardJson, "rf433tx", -1, 48, -1); boardPins.rf433tx = jsonConstrain<int>(tag, boardJson, "rf433tx", -1, 48, -1);
boardPins.rf433rx = jsonConstrain<int>(tag, boardJson, "rf433rx", -1, 48, -1); boardPins.rf433rx = jsonConstrain<int>(tag, boardJson, "rf433rx", -1, 48, -1);
// TODO Add hardware version to log // Validate pins against reserved GPIOs
ESP_LOGI(tag, "loaded Pins from %s", path.c_str()); auto clampPin = [](int v){ return isValidGpio(v) ? v : -1; };
boardPins.rgb1 = clampPin(boardPins.rgb1);
boardPins.rgb2 = clampPin(boardPins.rgb2);
for (int i=0;i<3;i++) boardPins.btn[i] = clampPin(boardPins.btn[i]);
boardPins.buzzer = clampPin(boardPins.buzzer);
for (int i=0;i<5;i++) boardPins.touch[i] = clampPin(boardPins.touch[i]);
boardPins.shield = clampPin(boardPins.shield);
for (int i=0;i<4;i++) boardPins.relay[i] = clampPin(boardPins.relay[i]);
for (int i=0;i<2;i++) boardPins.stat[i] = clampPin(boardPins.stat[i]);
boardPins.adc1 = clampPin(boardPins.adc1);
boardPins.oled_dc = clampPin(boardPins.oled_dc);
boardPins.oled_rst = clampPin(boardPins.oled_rst);
boardPins.oled_mosi = clampPin(boardPins.oled_mosi);
boardPins.oled_sck = clampPin(boardPins.oled_sck);
boardPins.oled_cs = clampPin(boardPins.oled_cs);
for (int i=0;i<2;i++) boardPins.ext[i] = clampPin(boardPins.ext[i]);
boardPins.rf433tx = clampPin(boardPins.rf433tx);
boardPins.rf433rx = clampPin(boardPins.rf433rx);
ESP_LOGI(tag, "loaded Pins from %s", path.c_str());
return true;
} }

View File

@ -9,16 +9,28 @@ void Init_ButtonEvents(int8_t (&pin)[3]){
if (pin[0] >= 0) { if (pin[0] >= 0) {
if (boardButtons[0] == nullptr) {
boardButtons[0] = new OneButton(pin[0], true, true); boardButtons[0] = new OneButton(pin[0], true, true);
ESP_LOGD(tag, "Button1 Events, pin=%d", pin[0]); ESP_LOGD(tag, "Button1 Events, pin=%d", pin[0]);
} else {
ESP_LOGD(tag, "Button1 already initialized (pin=%d)", pin[0]);
}
} }
if (pin[1] >= 0) { if (pin[1] >= 0) {
if (boardButtons[1] == nullptr) {
boardButtons[1] = new OneButton(pin[1], true, true); boardButtons[1] = new OneButton(pin[1], true, true);
ESP_LOGD(tag, "Button2 Events, pin=%d", pin[1]); ESP_LOGD(tag, "Button2 Events, pin=%d", pin[1]);
} else {
ESP_LOGD(tag, "Button2 already initialized (pin=%d)", pin[1]);
}
} }
if (pin[2] >= 0) { if (pin[2] >= 0) {
if (boardButtons[2] == nullptr) {
boardButtons[2] = new OneButton(pin[2], true, false); boardButtons[2] = new OneButton(pin[2], true, false);
ESP_LOGD(tag, "Button3 Events, pin=%d", pin[2]); ESP_LOGD(tag, "Button3 Events, pin=%d", pin[2]);
} else {
ESP_LOGD(tag, "Button3 already initialized (pin=%d)", pin[2]);
}
} }
/* /*
@ -62,6 +74,14 @@ void Init_ButtonEvents(int8_t (&pin)[3]){
} }
void Update_Buttons() {
for (int i = 0; i < 3; ++i) {
if (boardButtons[i] != nullptr) {
boardButtons[i]->tick();
}
}
}
void btn1_click() { void btn1_click() {
//IncrementEventIndex(); //IncrementEventIndex();
//Pulse_LED_Status(150); //Pulse_LED_Status(150);

View File

@ -1,38 +1,77 @@
#include "my_tsensor.h" #include "my_tsensor.h"
#include <Temperature_LM75_Derived.h> #include <Temperature_LM75_Derived.h>
#include "global.h"
static const char* tag = "tsensor"; static const char* tag = "tsensor";
T_SENSOR tSensorSettings; T_SENSOR tSensorSettings;
TI_TMP102_Compatible *tSensor; TI_TMP102_Compatible *tSensor = nullptr;
/******************* Temperature Control ********************/ /******************* Temperature Control ********************/
void Init_TSensor(uint8_t addr) { void Init_TSensor(uint8_t addr) {
//tSensor = new TI_TMP102_Compatible(72); // Initialize the temperature sensor once with the provided I2C address
if (tSensor == nullptr) {
tSensor = new TI_TMP102_Compatible(addr); tSensor = new TI_TMP102_Compatible(addr);
ESP_LOGI(tag, "TSensor initialized at I2C addr 0x%02X", addr);
} else {
ESP_LOGW(tag, "TSensor already initialized; ignoring re-init request (addr 0x%02X)", addr);
}
} }
static inline float clampf(float v, float lo, float hi) {
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}
void UpdateFanControl(float temperature, PWM_Output* pwmOut) { void UpdateFanControl(float temperature, PWM_Output* pwmOut) {
if (pwmOut == nullptr) {
ESP_LOGW(tag, "UpdateFanControl called with null PWM output");
return;
}
if (isnan(temperature) || isinf(temperature)) {
ESP_LOGW(tag, "Invalid temperature reading: %f", temperature);
return;
}
static uint8_t FanState = 0; static uint8_t FanState = 0;
tSensorSettings.temperature = temperature; // cache last temp
float currentDuty = pwmOut->currDuty; float currentDuty = pwmOut->currDuty;
float newDuty = currentDuty; float newDuty = currentDuty;
// Sanitize settings locally (do not modify globals)
float sp1 = tSensorSettings.Setpoint1;
float sp2 = tSensorSettings.Setpoint2;
float hyst = tSensorSettings.hyst;
float fp1 = tSensorSettings.fanPower1;
float fp2 = tSensorSettings.fanPower2;
if (hyst < 0.0f) hyst = 0.0f;
if (sp2 < sp1) {
// Ensure sp2 >= sp1
float tmp = sp1; sp1 = sp2; sp2 = tmp;
}
const float maxDuty = pwmOut->getMaxDuty();
fp1 = clampf(fp1, 0.0f, maxDuty);
fp2 = clampf(fp2, 0.0f, maxDuty);
// Fan State Logic // Fan State Logic
if ((FanState == 2) && (temperature < (tSensorSettings.Setpoint2 - tSensorSettings.hyst))) { if ((FanState == 2) && (temperature < (sp2 - hyst))) {
newDuty = tSensorSettings.fanPower1; newDuty = fp1;
FanState = 1; FanState = 1;
//ESP_LOGD(tag, "Dropping down to FanPower1"); //ESP_LOGD(tag, "Dropping down to FanPower1");
} }
else if ((FanState == 1) && (temperature < (tSensorSettings.Setpoint1 - tSensorSettings.hyst))) { else if ((FanState == 1) && (temperature < (sp1 - hyst))) {
newDuty = 0; newDuty = 0;
FanState = 0; FanState = 0;
//ESP_LOGD(tag, "Dropping down to FanPower0"); //ESP_LOGD(tag, "Dropping down to FanPower0");
} }
else if ((FanState <= 1) && (temperature > tSensorSettings.Setpoint1)) { else if ((FanState <= 1) && (temperature > sp1)) {
newDuty = tSensorSettings.fanPower1; newDuty = fp1;
if (temperature > tSensorSettings.Setpoint2) { if (temperature > sp2) {
newDuty = tSensorSettings.fanPower2; newDuty = fp2;
FanState = 2; FanState = 2;
//ESP_LOGD(tag, "Raising up to FanPower2"); //ESP_LOGD(tag, "Raising up to FanPower2");
} //else { } //else {
@ -43,6 +82,6 @@ TI_TMP102_Compatible *tSensor;
// Apply new duty cycle if changed // Apply new duty cycle if changed
if (currentDuty != newDuty) { if (currentDuty != newDuty) {
pwmOut->setOutput(newDuty); pwmOut->setOutput(newDuty);
ESP_LOGD(tag, "Board T: %.2f, Fan -> %.2f", temperature, newDuty); ESP_LOGD(tag, "Board T: %.2f F, Fan -> %.2f (state=%u)", temperature, newDuty, FanState);
} }
} }

View File

@ -1036,38 +1036,82 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
return false; return false;
} }
// Open file with error checking // Normalize and validate path
File file = fs.open(path, "w"); String finalPath(path);
if (!file) if (!finalPath.startsWith("/"))
{ {
ESP_LOGE(tag, "Failed to open file: %s", path); finalPath = String("/") + finalPath;
}
// Prevent directory traversal
if (finalPath.indexOf("..") >= 0)
{
ESP_LOGE(tag, "Rejected unsafe path: %s", finalPath.c_str());
return false;
}
// Collapse duplicate slashes (optional hardening)
while (finalPath.indexOf("//") >= 0)
{
finalPath.replace("//", "/");
}
// Size checks
const size_t MAX_FILE_SIZE = 1024 * 1024; // 1MB cap (aligned with readFile)
const size_t totalSize = strlen(message);
if (totalSize > MAX_FILE_SIZE)
{
ESP_LOGE(tag, "Write too large: %u bytes for %s", (unsigned)totalSize, finalPath.c_str());
return false; return false;
} }
// Write with error handling // Write to a temporary file first for atomicity
try String tmpPath = finalPath + ".tmp";
File tmp = fs.open(tmpPath.c_str(), "w");
if (!tmp)
{ {
size_t bytesWritten = file.print(message); ESP_LOGE(tag, "Failed to open temp file: %s", tmpPath.c_str());
if (bytesWritten == 0)
{
ESP_LOGE(tag, "Failed to write to file: %s", path);
file.close();
return false; return false;
} }
// Ensure all data is written // Write in a loop to ensure all bytes are written
file.flush(); size_t written = 0;
file.close(); const uint8_t *buf = reinterpret_cast<const uint8_t *>(message);
ESP_LOGD(tag, "Successfully wrote %u bytes to %s", bytesWritten, path); while (written < totalSize)
{
size_t n = tmp.write(buf + written, totalSize - written);
if (n == 0)
{
ESP_LOGE(tag, "Write failed to temp file: %s at %u/%u bytes", tmpPath.c_str(), (unsigned)written, (unsigned)totalSize);
tmp.close();
fs.remove(tmpPath.c_str());
return false;
}
written += n;
}
// Flush and close temp file
tmp.flush();
tmp.close();
// Replace the target file atomically: remove existing then rename
if (fs.exists(finalPath.c_str()))
{
if (!fs.remove(finalPath.c_str()))
{
ESP_LOGE(tag, "Failed to remove existing file: %s", finalPath.c_str());
fs.remove(tmpPath.c_str());
return false;
}
}
if (!fs.rename(tmpPath.c_str(), finalPath.c_str()))
{
ESP_LOGE(tag, "Failed to rename %s to %s", tmpPath.c_str(), finalPath.c_str());
fs.remove(tmpPath.c_str());
return false;
}
ESP_LOGD(tag, "Successfully wrote %u bytes to %s", (unsigned)totalSize, finalPath.c_str());
return true; return true;
} }
catch (const std::exception &e)
{
ESP_LOGE(tag, "Exception while writing file %s: %s", path, e.what());
file.close();
return false;
}
}
char *readFile(fs::FS &fs, const char *path) char *readFile(fs::FS &fs, const char *path)
{ {