fixed config, memory consumption, manual

This commit is contained in:
Stefan Ostermann 2025-10-18 22:56:11 +02:00
parent de44c789af
commit e677a7a8bf
4 changed files with 175 additions and 112 deletions

View File

@ -118,7 +118,7 @@ Manager features:
- Refresh playlist view. - Refresh playlist view.
-------------------------------------------------------------------- --------------------------------------------------------------------
## Mapping figurines (RFID → what to play) ## Mapping figurines (RFID → what to play)
Option A: Map through the web app (recommended) Option A: Map through the web app (recommended)

View File

@ -1,27 +1,30 @@
#include "config.h" #include "config.h"
#include "globals.h" #include "globals.h"
#include <SD.h> #include <SD.h>
#include <string.h>
#include <ctype.h>
#include <strings.h>
#include <stdio.h>
// Global config instance // Global config instance
Config config; Config config;
const String getConfigFilePath() { const char* getConfigFilePath() {
static String config_dir; static char path[64];
if (config_dir.isEmpty()) { static bool init = false;
config_dir.concat("/"); if (!init) {
config_dir.concat(sys_dir); // Build "/<sys_dir>/<config_file>" once into a static buffer
config_dir.concat("/"); snprintf(path, sizeof(path), "/%s/%s", sys_dir ? sys_dir : "", config_file ? config_file : "");
config_dir.concat(config_dir); init = true;
} }
return path;
return config_dir;
} }
void setDefaultConfig() { void setDefaultConfig() {
config.initialVolume = 7; config.initialVolume = 7;
config.maxVolume = 15; config.maxVolume = 15;
config.sleepTime = 1800000; config.sleepTime = 1800000;
config.minVoltage = 3200; config.minVoltage = 3000;
config.voltage100Percent = 4200; config.voltage100Percent = 4200;
config.sleepDelay = 1800000; config.sleepDelay = 1800000;
config.sleepMessageDelay = 1798000; config.sleepMessageDelay = 1798000;
@ -32,127 +35,169 @@ void setDefaultConfig() {
} }
bool loadConfig() { bool loadConfig() {
String configPath = getConfigFilePath(); const char* configPath = getConfigFilePath();
if (!SD.exists(configPath)) { if (!SD.exists(configPath)) {
Serial.println(F("Config file not found, using defaults")); Serial.println(F("Config file not found, using defaults"));
setDefaultConfig(); setDefaultConfig();
return saveConfig(); // Create config file with defaults return saveConfig(); // Create config file with defaults
} }
File file = SD.open(configPath, FILE_READ); File file = SD.open(configPath, FILE_READ);
if (!file) { if (!file) {
Serial.println(F("Failed to open config file")); Serial.println(F("Failed to open config file"));
setDefaultConfig(); setDefaultConfig();
return false; return false;
} }
// Set defaults first // Set defaults first
setDefaultConfig(); setDefaultConfig();
// Parse config file line by line // Parse config file line by line using a fixed-size buffer (no dynamic allocation)
while (file.available()) { char line[160];
String line = file.readStringUntil('\n'); while (true) {
line.trim(); int idx = 0;
int c;
// Read characters until newline or EOF
while (idx < (int)sizeof(line) - 1 && (c = file.read()) >= 0) {
if (c == '\r') continue;
if (c == '\n') break;
line[idx++] = (char)c;
}
if (idx == 0 && c < 0) {
// EOF with no more data
break;
}
line[idx] = '\0';
// Trim leading/trailing whitespace
char* s = line;
while (*s && isspace((unsigned char)*s)) ++s;
char* end = s + strlen(s);
while (end > s && isspace((unsigned char)end[-1])) --end;
*end = '\0';
// Skip empty lines and comments // Skip empty lines and comments
if (line.length() == 0 || line.startsWith("#")) { if (*s == '\0' || *s == '#') {
if (c < 0) break;
continue; continue;
} }
int separatorIndex = line.indexOf('='); // Split at '='
if (separatorIndex == -1) { char* eq = strchr(s, '=');
if (!eq) {
if (c < 0) break;
continue; continue;
} }
*eq = '\0';
String key = line.substring(0, separatorIndex); char* key = s;
String value = line.substring(separatorIndex + 1); char* val = eq + 1;
key.trim();
value.trim(); // Trim key
while (*key && isspace((unsigned char)*key)) ++key;
char* kend = key + strlen(key);
while (kend > key && isspace((unsigned char)kend[-1])) --kend;
*kend = '\0';
// Trim val
while (*val && isspace((unsigned char)*val)) ++val;
char* vend = val + strlen(val);
while (vend > val && isspace((unsigned char)vend[-1])) --vend;
*vend = '\0';
// Parse each configuration value // Parse each configuration value
if (key == "initialVolume") { if (strcmp(key, "initialVolume") == 0) {
config.initialVolume = constrain(value.toInt(), 0, 21); long v = strtol(val, nullptr, 10);
} else if (key == "maxVolume") { if (v < 0) v = 0; if (v > 21) v = 21;
config.maxVolume = constrain(value.toInt(), 1, 21); config.initialVolume = (uint8_t)v;
} else if (key == "sleepTime") { } else if (strcmp(key, "maxVolume") == 0) {
config.sleepTime = value.toInt(); long v = strtol(val, nullptr, 10);
} else if (key == "minVoltage") { if (v < 1) v = 1; if (v > 21) v = 21;
config.minVoltage = value.toInt(); config.maxVolume = (uint8_t)v;
} else if (key == "voltage100Percent") { } else if (strcmp(key, "sleepTime") == 0) {
config.voltage100Percent = value.toInt(); config.sleepTime = (uint32_t)strtoul(val, nullptr, 10);
} else if (key == "sleepDelay") { } else if (strcmp(key, "minVoltage") == 0) {
config.sleepDelay = value.toInt(); config.minVoltage = (uint16_t)strtoul(val, nullptr, 10);
} else if (key == "sleepMessageDelay") { } else if (strcmp(key, "voltage100Percent") == 0) {
config.sleepMessageDelay = value.toInt(); config.voltage100Percent = (uint16_t)strtoul(val, nullptr, 10);
} else if (key == "rfidLoopInterval") { } else if (strcmp(key, "sleepDelay") == 0) {
config.rfidLoopInterval = constrain(value.toInt(), 1, 255); config.sleepDelay = (uint32_t)strtoul(val, nullptr, 10);
} else if (key == "startAtStoredProgress") { } else if (strcmp(key, "sleepMessageDelay") == 0) {
config.startAtStoredProgress = (value == "1" || value.equalsIgnoreCase("true")); config.sleepMessageDelay = (uint32_t)strtoul(val, nullptr, 10);
} else if (key == "wifiSSID") { } else if (strcmp(key, "rfidLoopInterval") == 0) {
strncpy(config.wifiSSID, value.c_str(), sizeof(config.wifiSSID) - 1); long v = strtol(val, nullptr, 10);
if (v < 1) v = 1; if (v > 255) v = 255;
config.rfidLoopInterval = (uint8_t)v;
} else if (strcmp(key, "startAtStoredProgress") == 0) {
bool b = (strcmp(val, "1") == 0) || (strcasecmp(val, "true") == 0);
config.startAtStoredProgress = b;
} else if (strcmp(key, "wifiSSID") == 0) {
strncpy(config.wifiSSID, val, sizeof(config.wifiSSID) - 1);
config.wifiSSID[sizeof(config.wifiSSID) - 1] = '\0'; config.wifiSSID[sizeof(config.wifiSSID) - 1] = '\0';
} else if (key == "wifiPassword") { } else if (strcmp(key, "wifiPassword") == 0) {
strncpy(config.wifiPassword, value.c_str(), sizeof(config.wifiPassword) - 1); strncpy(config.wifiPassword, val, sizeof(config.wifiPassword) - 1);
config.wifiPassword[sizeof(config.wifiPassword) - 1] = '\0'; config.wifiPassword[sizeof(config.wifiPassword) - 1] = '\0';
} }
if (c < 0) break; // EOF after processing last line
} }
file.close(); file.close();
Serial.println("Config loaded successfully"); Serial.println(F("Config loaded successfully"));
Serial.print("Initial Volume: "); Serial.println(config.initialVolume); Serial.print(F("Initial Volume: ")); Serial.println(config.initialVolume);
Serial.print("Max Volume: "); Serial.println(config.maxVolume); Serial.print(F("Max Volume: ")); Serial.println(config.maxVolume);
Serial.print("Sleep Delay: "); Serial.println(config.sleepDelay); Serial.print(F("Sleep Delay: ")); Serial.println(config.sleepDelay);
Serial.print("RFID Interval: "); Serial.println(config.rfidLoopInterval); Serial.print(F("RFID Interval: ")); Serial.println(config.rfidLoopInterval);
Serial.print(F("min Voltage: ")); Serial.println(config.minVoltage);
return true; return true;
} }
bool saveConfig() { bool saveConfig() {
String configPath = getConfigFilePath(); const char* configPath = getConfigFilePath();
File file = SD.open(configPath, FILE_WRITE); File file = SD.open(configPath, FILE_WRITE);
if (!file) { if (!file) {
Serial.println(F("Failed to create config file")); Serial.println(F("Failed to create config file"));
return false; return false;
} }
// Write config file with comments for user reference // Write config file with comments for user reference
file.println("# HannaBox Conf File"); file.println(F("# HannaBox Conf File"));
file.println("# format: key=value"); file.println(F("# format: key=value"));
file.println(""); file.println();
file.println("# Audio"); file.println(F("# Audio"));
file.print("initialVolume="); file.println(config.initialVolume); file.print(F("initialVolume=")); file.println(config.initialVolume);
file.print("maxVolume="); file.println(config.maxVolume); file.print(F("maxVolume=")); file.println(config.maxVolume);
file.println(""); file.println();
file.println("# Power Management (in milliseconds)"); file.println(F("# Power Management (in milliseconds)"));
file.print("sleepTime="); file.println(config.sleepTime); file.print(F("sleepTime=")); file.println(config.sleepTime);
file.print("sleepDelay="); file.println(config.sleepDelay); file.print(F("sleepDelay=")); file.println(config.sleepDelay);
file.print("sleepMessageDelay="); file.println(config.sleepMessageDelay); file.print(F("sleepMessageDelay=")); file.println(config.sleepMessageDelay);
file.println(""); file.println();
file.println("# Battery (in millivolts)"); file.println(F("# Battery (in millivolts)"));
file.print("minVoltage="); file.println(config.minVoltage); file.print(F("minVoltage=")); file.println(config.minVoltage);
file.print("voltage100Percent="); file.println(config.voltage100Percent); file.print(F("voltage100Percent=")); file.println(config.voltage100Percent);
file.println(""); file.println();
file.println("# RFID"); file.println(F("# RFID"));
file.print("rfidLoopInterval="); file.println(config.rfidLoopInterval); file.print(F("rfidLoopInterval=")); file.println(config.rfidLoopInterval);
file.println(""); file.println();
file.println("# Playback"); file.println(F("# Playback"));
file.print("startAtStoredProgress="); file.println(config.startAtStoredProgress ? "true" : "false"); file.print(F("startAtStoredProgress=")); file.println(config.startAtStoredProgress ? F("true") : F("false"));
file.println(""); file.println();
file.println("# WiFi (leave empty to use current WiFiManager)"); file.println(F("# WiFi (leave empty to use current WiFiManager)"));
file.print("wifiSSID="); file.println(config.wifiSSID); file.print(F("wifiSSID=")); file.println(config.wifiSSID);
file.print("wifiPassword="); file.println(config.wifiPassword); file.print(F("wifiPassword=")); file.println(config.wifiPassword);
file.close(); file.close();
Serial.println(F("Config saved successfully")); Serial.println(F("Config saved successfully"));
return true; return true;
} }

View File

@ -3,19 +3,30 @@
#include <Arduino.h> #include <Arduino.h>
// Configuration structure - keep it minimal for memory efficiency // Keep the structure compact to minimize padding and RAM usage.
// Order fields from wider to narrower types, and avoid inline default initializers.
// Defaults are applied in setDefaultConfig() to keep initialization simple and lightweight.
struct Config { struct Config {
uint8_t initialVolume = 7; // 32-bit values
uint8_t maxVolume = 15; uint32_t sleepTime; // ms
uint32_t sleepTime = 1800000; // 30 minutes in ms uint32_t sleepDelay; // ms
uint16_t minVoltage = 3200; // mV - minimum voltage before sleep uint32_t sleepMessageDelay; // ms
uint16_t voltage100Percent = 4200; // mV - voltage representing 100% battery
uint32_t sleepDelay = 1800000; // 30 minutes in ms // 16-bit values
uint32_t sleepMessageDelay = 1798000; // 2 seconds before sleep uint16_t minVoltage; // mV
uint8_t rfidLoopInterval = 25; // RFID check interval uint16_t voltage100Percent; // mV
bool startAtStoredProgress = true; // Resume from last position
char wifiSSID[32] = ""; // WiFi SSID (empty = use current mechanism) // 8-bit values
char wifiPassword[64] = ""; // WiFi password (empty = use current mechanism) uint8_t initialVolume;
uint8_t maxVolume;
uint8_t rfidLoopInterval;
// flags
bool startAtStoredProgress;
// WiFi credentials (fixed-size buffers, no heap allocations)
char wifiSSID[32]; // empty => use current mechanism
char wifiPassword[64]; // empty => use current mechanism
}; };
// Global config instance // Global config instance
@ -25,6 +36,9 @@ extern Config config;
bool loadConfig(); bool loadConfig();
bool saveConfig(); bool saveConfig();
void setDefaultConfig(); void setDefaultConfig();
const String getConfigFilePath();
// Returns a pointer to a static buffer with the absolute config file path.
// Avoids dynamic allocation/Arduino String to reduce heap fragmentation.
const char* getConfigFilePath();
#endif #endif

View File

@ -1697,11 +1697,15 @@ void loop()
{ {
lastVoltage = getBatteryVoltageMv(); lastVoltage = getBatteryVoltageMv();
free_heap = xPortGetFreeHeapSize(); free_heap = xPortGetFreeHeapSize();
if (lastVoltage < config.minVoltage) if (lastVoltage < config.minVoltage && config.minVoltage > 0)
{ {
if (voltage_threshold_counter > 3) if (voltage_threshold_counter > 3)
{ {
Serial.println(F("deep sleep due to low volts..")); Serial.print("deep sleep due to low volts (");
Serial.print(lastVoltage);
Serial.print(") min: ");
Serial.println(config.minVoltage);
lastInteraction = millis() - config.sleepMessageDelay; lastInteraction = millis() - config.sleepMessageDelay;
voltage_threshold_counter = 0; voltage_threshold_counter = 0;
} }