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,
"chip": "SK6812",
"rgb-order": "rgb",
"shift":-52,
"shift":-5,
"offset": 0,
"power-div": 0,
"i2s-ch": 0,

View File

@ -27,10 +27,11 @@ typedef struct{
}BOARD_PINS;
extern BOARD_PINS* thisBoardPins;
#define setStatusPin1(state) digitalWrite(thisBoardPins->stat[0], state);
#define setStatusPin2(state) digitalWrite(thisBoardPins->stat[1], state);
// Safe status pin macros: only write when configured (>=0)
#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 updateFanControl(float temperature);
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"
extern OneButton *boardButtons[3];
#define Update_Buttons() boardButtons[1]->tick(); boardButtons[2]->tick(); boardButtons[3]->tick();
void Init_ButtonEvents(int8_t (&pin)[3]);
// Safely tick any initialized buttons (nullptr-aware)
void Update_Buttons();
void btn1_click();
void btn1_doubleClick();
void btn1_LongPressStart();

View File

@ -34,6 +34,6 @@ build_flags =
-D CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=1
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
-D CONFIG_ARDUHAL_LOG_COLORS=1
upload_port = COM11
upload_port = COM5
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];
volatile bool AnimationLooping = false;
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){
@ -352,8 +352,12 @@ void Lights_Control_Task(void *parameters){
ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
switch (AnimEvent.AnimationIndex) {
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);
FastLED.show();
} else {
ESP_LOGW(tag, "Pixel index out of range: %d", AnimEvent.data.data[7]);
}
break;
case -2: // Fill Static Color
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();
// 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;
@ -94,11 +103,7 @@ void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickTyp
TickType_t totalElapsed = xTaskGetTickCount() - startTicks;
if (totalElapsed >= durationMs) {
while(loop_active_flag){
if (ulTaskNotifyTake(pdTRUE, 50)) {
return;
}
}
// Auto-terminate the loop when duration reached
break;
}
}
@ -121,7 +126,16 @@ void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t
for(;;) {
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;
@ -139,14 +153,8 @@ void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t
// Delay and Check for termination request
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) {
while(loop_active_flag){
if (ulTaskNotifyTake(pdTRUE, 50)) {
//loop_active_flag = false;
break;
}
}
break;
}
@ -189,6 +197,7 @@ void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const
// Calculate half size for mirroring
const int halfSize = size / 2;
if (halfSize <= 0) return;
// Create heat array for half the size
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
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));
}
@ -382,11 +392,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
// Set fade factor
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 spacing = size / totalComets;
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
@ -414,9 +424,11 @@ void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PA
}
// Draw comet with solid color
colorPack.col[i % colorPack.size];
color = colorPack.col[i % colorPack.size];
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;
}
}
@ -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) {
if (!leds || size <= 0 || totalDurationMs <= 0) return;
if (!leds || size <= 1 || totalDurationMs <= 0) return;
const int halfSize = size / 2;
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();
const uint32_t halfTime = timeMs / 2;
uint8_t origBright = FastLED.getBrightness();
const uint8_t origBright = FastLED.getBrightness();
FastLED.setBrightness(255);
uint8_t brightness = MIN_BRIGHTNESS;
uint8_t breath = MIN_BRIGHTNESS;
uint32_t elapsedTime;
CRGB scaledColor, correctedColor;
unsigned long currentTime;
CRGB outColor;
Animation_Loop(activeFlag, speed, [&]() -> int {
// Calculate elapsed time in current breath cycle
// Elapsed time in the current breath cycle
currentTime = millis();
elapsedTime = currentTime - startTime;
// Calculate brightness using a linear approach
// Triangle wave brightness: up for half, down for half
if (elapsedTime < halfTime) {
brightness = map(elapsedTime, 0, halfTime, MIN_BRIGHTNESS, 255); // Brighten
breath = map(elapsedTime, 0, halfTime, MIN_BRIGHTNESS, 255); // Brighten
} else {
brightness = map(elapsedTime, halfTime, timeMs, 255, MIN_BRIGHTNESS); // Dim
breath = map(elapsedTime, halfTime, timeMs, 255, MIN_BRIGHTNESS); // Dim
}
// Scale the color directly
scaledColor = colors.col[colorIndex];
scaledColor.nscale8(brightness * origBright / 255);
// Combine breath with original global brightness (rounded)
uint16_t prod = static_cast<uint16_t>(breath) * static_cast<uint16_t>(origBright);
uint8_t finalBright = static_cast<uint8_t>((prod + 127) / 255);
// Correct the color scale for vision
correctedColor = scaledColor;
correctedColor.nscale8_video(brightness);
// Apply perceptual scaling once with combined brightness
outColor = colors.col[colorIndex];
outColor.nscale8_video(finalBright);
// Fill all LEDs with scaled color
fill_solid(leds, size, correctedColor);
fill_solid(leds, size, outColor);
FastLED.show();
if (elapsedTime >= timeMs) {
@ -534,21 +546,17 @@ void Anim_GradientRotate(bool volatile& activeFlag, CRGB* leds, int size, const
CRGB color1, color2;
// Create initial gradient
int segmentLength = size / colors.size;
for(int i = 0; i < size; i++) {
// Determine which color segment we're in
int segment = i / segmentLength;
// Create initial gradient: evenly distribute blends across the strip
// For i in [0..size-1], compute position in color space [0..colors.size)
for (int i = 0; i < size; i++) {
uint32_t pos256 = (uint32_t)i * (uint32_t)colors.size * 256u / (uint32_t)size; // 8.8 fixed point
int segment = (int)(pos256 >> 8); // 0..colors.size-1
uint8_t blendPos = (uint8_t)(pos256 & 0xFF); // 0..255
int nextSegment = (segment + 1) % colors.size;
// Calculate blend amount within segment
uint8_t blendPos = map(i % segmentLength, 0, segmentLength - 1, 0, 255);
// Create local copies for blending
// Local copies for blending
color1 = colors.col[segment];
color2 = colors.col[nextSegment];
// Set initial gradient
leds[i] = blend(color1, color2, blendPos);
}

View File

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

View File

@ -55,7 +55,11 @@ class SP110ECallbacks : public NimBLECharacteristicCallbacks {
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);

View File

@ -35,20 +35,36 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str());
if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials
JsonDocument doc;
deserializeJson(doc, value.substr(13));
// Expecting: "wifi-connect:{\"ssid\":...,\"pass\":...}"
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>();
String ssid = wifiJson["ssid"].as<String>();
String pass = wifiJson["pass"].as<String>();
ESP_LOGI(tag, "Wifi Credentials: %s, %s", ssid.c_str(), pass.c_str());
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);
if(status == true){
bool started = StartWifiConnectTask(ssid, pass);
if (started) {
updatePacket.wifiStatus = WIFI_DISCONNECTED;
updatePacket.wifiOnline = false;
updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
}else{
ESP_LOGI(tag, "Failed to start WiFi connection task");
} else {
ESP_LOGW(tag, "Failed to start WiFi connection task");
}
}
else if (value.compare("version-check") == 0) { // Check if new version is available
@ -73,23 +89,20 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic *pCharacteristic) override {
updatePacket.wifiOnline = InternetAvailable;
if(WiFi.status() == WL_CONNECTED){
if (WiFi.status() == WL_CONNECTED) {
updatePacket.wifiStatus = WIFI_CONNECTED;
if(updatePacket.wifiIP[0] == 0){
updatePacket.wifiIP[0] = WiFi.localIP()[0];
updatePacket.wifiIP[1] = WiFi.localIP()[1];
updatePacket.wifiIP[2] = WiFi.localIP()[2];
updatePacket.wifiIP[3] = WiFi.localIP()[3];
}
}else{
IPAddress ip = WiFi.localIP();
updatePacket.wifiIP[0] = ip[0];
updatePacket.wifiIP[1] = ip[1];
updatePacket.wifiIP[2] = ip[2];
updatePacket.wifiIP[3] = ip[3];
} else {
updatePacket.wifiStatus = WIFI_DISCONNECTED;
if(updatePacket.wifiIP[0] > 0){
updatePacket.wifiIP[0] = 0;
updatePacket.wifiIP[1] = 0;
updatePacket.wifiIP[2] = 0;
updatePacket.wifiIP[3] = 0;
}
}
//update version
if(otaVersion.major() != 0){
@ -99,19 +112,24 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
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));
ESP_LOGI(tag, "Upgrade Char read");
ESP_LOGI(tag, "Upgrade status read");
}
}
};
void bleUpgrade_send_message(String s){
if(pUpgradeCharacteristic2){
if (s != nullptr) {
pUpgradeCharacteristic2->setValue(s);
if (s.length() == 0) {
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();
} 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
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
if (pAdvertising != nullptr) {
if (!pAdvertising->isAdvertising()) {
pAdvertising->start();
}
}
}
void onDisconnect(NimBLEServer* pServer) override {
ESP_LOGI(tag, "Client disconnected");
// Restart advertising on disconnect to keep it active always
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
if (pAdvertising != nullptr) {
if (!pAdvertising->isAdvertising()) {
pAdvertising->start();
}
}
}
};
void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
@ -35,6 +39,7 @@ void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
//NimBLEDevice::init("ATALIGHTS");
NimBLEDevice::init("SP110E-ATA");
//NimBLEDevice::setMTU(247); // Set preferred MTU size (max 247 for BLE)
isInitialized = true;
}
NimBLEServer *pServer = NimBLEDevice::createServer();
@ -59,7 +64,7 @@ void Init_BleServer( bool isSP110EActive, bool isUpgradeActive) {
// Start BLE advertising
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
if (pAdvertising != nullptr) {
if (!pAdvertising->start()) {
if (!pAdvertising->isAdvertising() && !pAdvertising->start()) {
ESP_LOGE(tag, "Failed to start advertising");
return;
}

View File

@ -3,7 +3,10 @@
#include <esp_log.h>
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 <>
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>
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 <>
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>();
if (value.isEmpty()) {
if (value.length() == 0) {
ESP_LOGW(tag, "Key [%s] value is empty. Using default value.", key);
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());
}
@ -93,7 +99,7 @@ String jsonConstrainString(const char *tag, const JsonObject &jsonObject, const
String value = jsonObject[key].as<String>();
// 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());
return def;
}

View File

@ -1,5 +1,6 @@
#include "PWM_Output.h"
#include <Arduino.h>
#include "global.h"
const char* tag = "pwmout";
@ -56,6 +57,11 @@ void PWM_Output::setOutput(float duty){
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);
this->currOutVal = outDutyVal;
this->currDuty = duty;
@ -77,8 +83,9 @@ void PWM_Output::setResolution(uint8_t res){
if(this->res < 4) this->res = 4;
if(this->res > 16) this->res = 16;
this->standardFactor = binaryPow[res] * 0.01;
this->visionFactor = binaryPow[res] * 0.0001;
// Use the clamped resolution when computing factors
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);
}

View File

@ -3,6 +3,14 @@
#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 ************************/
@ -11,59 +19,51 @@ HUE_PALLET_DISPENSER::HUE_PALLET_DISPENSER(void){
}
void HUE_PALLET_DISPENSER::Initialize(float hue, float range, int colSteps){
this->range = range;
this->hueSteps = 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;
}
// Clamp and normalize inputs
this->range = constrain(range, 0.0f, (float)MAX_HUE);
this->hueSteps = (colSteps < 0) ? 0 : colSteps;
this->startHue = wrapHue(hue - (this->range * 0.5f));
this->hueIndex = 0;
if(this->hueSteps <= 1){
this->hueIncrement = 0.0;
this->currHue = hue;
}else{
this->hueIncrement = this->range / (this->hueSteps - 1);
if (this->hueSteps <= 1) {
this->hueIncrement = 0.0f;
this->currHue = wrapHue(hue);
} else {
this->hueIncrement = (this->range / (this->hueSteps - 1));
this->currHue = this->startHue;
}
}
int HUE_PALLET_DISPENSER::GetNextPalletHue(void){
if(this->hueSteps > 1){
this->currHue = this->startHue + this->hueIndex * this->hueIncrement;
if(this->currHue < 0){
this->currHue += MAX_HUE;
}else if(this->currHue > MAX_HUE){
this->currHue -= MAX_HUE;
if (this->hueSteps > 1) {
this->currHue = wrapHue(this->startHue + (this->hueIndex * this->hueIncrement));
this->hueIndex = (this->hueIndex + 1) % this->hueSteps;
}
// TODO Remove later
//rgbpixel_t p = HUEtoRGB(huePallet.currHue);
//Log.traceln("<anim> index: %d, hue= %F, col: %d, %d, %d", huePallet.hueIndex, huePallet.currHue, p.red, p.grn, p.blu);
this->hueIndex = ++this->hueIndex % this->hueSteps;
return round(this->currHue);
}else{
return round(this->currHue);
}
// Convert to integer hue in [0, 359]
int out = (int)(this->currHue + 0.5f);
if (out >= (int)MAX_HUE) out -= (int)MAX_HUE;
if (out < 0) out = 0; // safety
return out;
}
float HUE_PALLET_DISPENSER::PeekNextPalletHue(int hueOffset){
float tempHue = this->startHue + (this->hueIndex + hueOffset) * this->hueIncrement;
if(tempHue < 0){
tempHue += MAX_HUE;
}else if(tempHue > MAX_HUE){
tempHue -= MAX_HUE;
}
float tempHue = wrapHue(this->startHue + ((this->hueIndex + hueOffset) * this->hueIncrement));
return tempHue;
}
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 <Wifi.h>
#include <WiFi.h>
#include <FS.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
@ -257,6 +257,12 @@ void Log_CPU_Load(void) {
// Get task stats
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
for (UBaseType_t i = 0; i < task_count; i++) {

View File

@ -93,8 +93,7 @@ void setup()
{
// Serial Port
Serial.begin(115200);
while (!Serial)
;
while (!Serial);
// Initialize I2C Port for TSensor, ...
Wire.begin(I2C_SDA1_Pin, I2C_SCL1_Pin);
@ -281,7 +280,7 @@ void loop()
{
static bool ledState = false;
// 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;
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;
File file = LittleFS.open(path);
if (!file) {
ESP_LOGE(tag, "Error opening %s...", path.c_str());
return;
return false;
}
JsonDocument doc;
@ -27,7 +43,7 @@ void Load_Board_Pins(BOARD_PINS& boardPins, String& path){
if (error) {
ESP_LOGE(tag, "%s deserialize error!..", path.c_str());
return;
return false;
}
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.rf433rx = jsonConstrain<int>(tag, boardJson, "rf433rx", -1, 48, -1);
// TODO Add hardware version to log
ESP_LOGI(tag, "loaded Pins from %s", path.c_str());
// Validate pins against reserved GPIOs
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

@ -8,17 +8,29 @@ OneButton *boardButtons[3];
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);
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);
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);
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() {
//IncrementEventIndex();
//Pulse_LED_Status(150);

View File

@ -1,38 +1,77 @@
#include "my_tsensor.h"
#include <Temperature_LM75_Derived.h>
#include "global.h"
static const char* tag = "tsensor";
T_SENSOR tSensorSettings;
TI_TMP102_Compatible *tSensor;
TI_TMP102_Compatible *tSensor = nullptr;
/******************* Temperature Control ********************/
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);
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) {
if (pwmOut == nullptr) {
ESP_LOGW(tag, "UpdateFanControl called with null PWM output");
return;
}
if (isnan(temperature) || isinf(temperature)) {
ESP_LOGW(tag, "Invalid temperature reading: %f", temperature);
return;
}
static uint8_t FanState = 0;
tSensorSettings.temperature = temperature; // cache last temp
float currentDuty = pwmOut->currDuty;
float newDuty = currentDuty;
// Sanitize settings locally (do not modify globals)
float sp1 = tSensorSettings.Setpoint1;
float sp2 = tSensorSettings.Setpoint2;
float hyst = tSensorSettings.hyst;
float fp1 = tSensorSettings.fanPower1;
float fp2 = tSensorSettings.fanPower2;
if (hyst < 0.0f) hyst = 0.0f;
if (sp2 < sp1) {
// Ensure sp2 >= sp1
float tmp = sp1; sp1 = sp2; sp2 = tmp;
}
const float maxDuty = pwmOut->getMaxDuty();
fp1 = clampf(fp1, 0.0f, maxDuty);
fp2 = clampf(fp2, 0.0f, maxDuty);
// Fan State Logic
if ((FanState == 2) && (temperature < (tSensorSettings.Setpoint2 - tSensorSettings.hyst))) {
newDuty = tSensorSettings.fanPower1;
if ((FanState == 2) && (temperature < (sp2 - hyst))) {
newDuty = fp1;
FanState = 1;
//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;
FanState = 0;
//ESP_LOGD(tag, "Dropping down to FanPower0");
}
else if ((FanState <= 1) && (temperature > tSensorSettings.Setpoint1)) {
newDuty = tSensorSettings.fanPower1;
if (temperature > tSensorSettings.Setpoint2) {
newDuty = tSensorSettings.fanPower2;
else if ((FanState <= 1) && (temperature > sp1)) {
newDuty = fp1;
if (temperature > sp2) {
newDuty = fp2;
FanState = 2;
//ESP_LOGD(tag, "Raising up to FanPower2");
} //else {
@ -43,6 +82,6 @@ TI_TMP102_Compatible *tSensor;
// Apply new duty cycle if changed
if (currentDuty != 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,37 +1036,81 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
return false;
}
// Open file with error checking
File file = fs.open(path, "w");
if (!file)
// Normalize and validate path
String finalPath(path);
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;
}
// Write with error handling
try
// Write to a temporary file first for atomicity
String tmpPath = finalPath + ".tmp";
File tmp = fs.open(tmpPath.c_str(), "w");
if (!tmp)
{
size_t bytesWritten = file.print(message);
if (bytesWritten == 0)
{
ESP_LOGE(tag, "Failed to write to file: %s", path);
file.close();
ESP_LOGE(tag, "Failed to open temp file: %s", tmpPath.c_str());
return false;
}
// Ensure all data is written
file.flush();
file.close();
ESP_LOGD(tag, "Successfully wrote %u bytes to %s", bytesWritten, path);
// Write in a loop to ensure all bytes are written
size_t written = 0;
const uint8_t *buf = reinterpret_cast<const uint8_t *>(message);
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;
}
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)