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.
--------------------------------------------------------------------
## Mapping figurines (RFID → what to play)
Option A: Map through the web app (recommended)

View File

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

View File

@ -3,19 +3,30 @@
#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 {
uint8_t initialVolume = 7;
uint8_t maxVolume = 15;
uint32_t sleepTime = 1800000; // 30 minutes in ms
uint16_t minVoltage = 3200; // mV - minimum voltage before sleep
uint16_t voltage100Percent = 4200; // mV - voltage representing 100% battery
uint32_t sleepDelay = 1800000; // 30 minutes in ms
uint32_t sleepMessageDelay = 1798000; // 2 seconds before sleep
uint8_t rfidLoopInterval = 25; // RFID check interval
bool startAtStoredProgress = true; // Resume from last position
char wifiSSID[32] = ""; // WiFi SSID (empty = use current mechanism)
char wifiPassword[64] = ""; // WiFi password (empty = use current mechanism)
// 32-bit values
uint32_t sleepTime; // ms
uint32_t sleepDelay; // ms
uint32_t sleepMessageDelay; // ms
// 16-bit values
uint16_t minVoltage; // mV
uint16_t voltage100Percent; // mV
// 8-bit values
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
@ -25,6 +36,9 @@ extern Config config;
bool loadConfig();
bool saveConfig();
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

View File

@ -1697,11 +1697,15 @@ void loop()
{
lastVoltage = getBatteryVoltageMv();
free_heap = xPortGetFreeHeapSize();
if (lastVoltage < config.minVoltage)
if (lastVoltage < config.minVoltage && config.minVoltage > 0)
{
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;
voltage_threshold_counter = 0;
}