Files
HaloKeymind/src/helpers/ui/DisplayPowerSettings.cpp
T

208 lines
8.0 KiB
C++

#include "DisplayPowerSettings.h"
#include "DisplayBuildFlags.h"
#include <stdio.h>
namespace mesh { namespace ui {
namespace {
bool pairing_available = false;
#ifdef MESHCORE_HAS_REAL_DISPLAY
FILESYSTEM* settings_fs = nullptr;
const char* const path = "/display_prefs";
const char* const backup = "/display_prefs.bak";
const char* const temporary = "/display_prefs.tmp";
// Explicit bytes avoid compiler padding and preserve a small fixed format.
void encode(const DisplayPowerPrefs& prefs, uint8_t (&data)[12]) {
data[0] = 'D'; data[1] = 'P'; data[2] = 2;
data[3] = uint8_t(prefs.battery.mode);
data[4] = prefs.battery.seconds & 255; data[5] = prefs.battery.seconds >> 8;
data[6] = uint8_t(prefs.usb.mode);
data[7] = prefs.usb.seconds & 255; data[8] = prefs.usb.seconds >> 8;
data[9] = uint8_t(prefs.inbox); data[10] = 0; data[11] = 0;
for (unsigned i = 0; i < 11; ++i) data[11] ^= data[i];
}
bool readPrefs(const char* name, DisplayPowerPrefs& prefs) {
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
File file(*settings_fs);
if (!file.open(name, FILE_O_READ)) return false;
#else
File file = settings_fs->open(name, "r");
if (!file) return false;
#endif
uint8_t data[12], expected[12];
const bool complete = file.size() == sizeof(data)
&& file.read(data, sizeof(data)) == sizeof(data);
file.close();
if (!complete) return false;
prefs.battery.mode = DisplayMode(data[3]);
prefs.battery.seconds = uint16_t(data[4]) | (uint16_t(data[5]) << 8);
prefs.usb.mode = DisplayMode(data[6]);
prefs.usb.seconds = uint16_t(data[7]) | (uint16_t(data[8]) << 8);
prefs.inbox = data[2] == 1 ? DisplayInboxMode::History : DisplayInboxMode(data[9]);
encode(prefs, expected);
if (data[2] == 1) {
// Version 1 reserved byte 9 and had no inbox preference.
expected[2] = 1;
expected[11] ^= 0x02 ^ 0x01;
}
return memcmp(data, expected, sizeof(data)) == 0
&& validDisplayProfile(prefs.battery) && validDisplayProfile(prefs.usb)
&& uint8_t(prefs.inbox) <= uint8_t(DisplayInboxMode::Unread);
}
bool savePrefs(const DisplayPowerPrefs& prefs) {
if (!settings_fs) return false;
// Preserve the previous complete file until the replacement is verified.
settings_fs->remove(temporary);
if (settings_fs->exists(temporary)) return false;
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
File file(*settings_fs);
if (!file.open(temporary, FILE_O_WRITE)) return false;
#elif defined(RP2040_PLATFORM)
File file = settings_fs->open(temporary, "w");
if (!file) return false;
#else
File file = settings_fs->open(temporary, "w", true);
if (!file) return false;
#endif
uint8_t data[12];
encode(prefs, data);
const bool written = file.write(data, sizeof(data)) == sizeof(data);
file.close();
DisplayPowerPrefs checked;
if (!written || !readPrefs(temporary, checked)) return false;
if (settings_fs->exists(backup) && !settings_fs->remove(backup)) return false;
if (settings_fs->exists(path) && !settings_fs->rename(path, backup)) return false;
if (!settings_fs->rename(temporary, path)) {
if (settings_fs->exists(backup)) settings_fs->rename(backup, path);
return false;
}
settings_fs->remove(backup);
return true;
}
#endif // MESHCORE_HAS_REAL_DISPLAY
}
bool loadDisplayPowerSettings(FILESYSTEM* fs, bool pairing_supported, bool wake_on_boot) {
#ifdef MESHCORE_HAS_REAL_DISPLAY
settings_fs = fs;
#else
(void)fs;
#endif
pairing_available = pairing_supported;
DisplayPowerPrefs prefs;
prefs.wake_on_boot = wake_on_boot;
if (pairing_supported) prefs.battery.mode = prefs.usb.mode = DisplayMode::ButtonPairing;
displayPowerPrefs() = prefs;
#ifdef MESHCORE_HAS_REAL_DISPLAY
if (!fs) return false;
bool loaded = readPrefs(path, prefs);
if (!loaded && readPrefs(backup, prefs)) {
loaded = true;
fs->remove(path);
fs->rename(backup, path);
}
if (!loaded) return false;
if (!pairing_supported) {
if (displayModeSupportsPairing(prefs.battery.mode)) prefs.battery.mode = DisplayMode::Button;
if (displayModeSupportsPairing(prefs.usb.mode)) prefs.usb.mode = DisplayMode::Button;
}
displayPowerPrefs() = prefs;
return true;
#else
// Headless commands already report unsupported. Preserve runtime defaults
// without retaining or touching a display-only persistent store.
return false;
#endif
}
bool displayPairingSupported() { return pairing_available; }
void migrateLegacyDisplayTimeout(uint16_t seconds) {
if (seconds > 3600) return;
auto prefs = displayPowerPrefs();
prefs.battery.mode = prefs.usb.mode = seconds == 0 ? DisplayMode::On : DisplayMode::Automatic;
if (seconds) prefs.battery.seconds = prefs.usb.seconds = seconds;
// Keep the old behavior in memory even if flash is temporarily unavailable.
displayPowerPrefs() = prefs;
#ifdef MESHCORE_HAS_REAL_DISPLAY
savePrefs(prefs);
#endif
}
bool handleDisplayPowerCommand(const char* command, char* reply, size_t reply_size,
bool inbox_supported) {
#ifndef MESHCORE_HAS_REAL_DISPLAY
(void)inbox_supported;
if ((command[0] != 'g' && command[0] != 's')
|| strncmp(command + 1, "et display.", 11) != 0) return false;
const char* key = command + 12;
const bool usb = strncmp(key, "usb.", 4) == 0;
if (usb) key += 4;
size_t length;
if (strncmp(key, "mode", 4) == 0) length = 4;
else if (strncmp(key, "timeout", 7) == 0) length = 7;
else if (!usb && strncmp(key, "inbox", 5) == 0) length = 5;
else return false;
if (key[length] && key[length] != ' ') return false;
snprintf(reply, reply_size, "Error: display unsupported");
return true;
#else
const bool get = strncmp(command, "get ", 4) == 0;
const bool set = strncmp(command, "set ", 4) == 0;
if (!get && !set) return false;
const char* key = command + 4;
const char* keys[] = {"display.mode", "display.timeout", "display.usb.mode", "display.usb.timeout", "display.inbox"};
int index = -1;
for (int i = 0; i < 5; ++i) {
const size_t len = strlen(keys[i]);
if (strncmp(key, keys[i], len) == 0 && (key[len] == 0 || key[len] == ' ')) {
index = i; break;
}
}
if (index < 0) return false;
auto candidate = displayPowerPrefs();
auto& profile = index < 2 ? candidate.battery : candidate.usb;
const char* value = key + strlen(keys[index]);
if (index == 4 && !inbox_supported) {
snprintf(reply, reply_size, "Error: inbox modes unsupported");
return true;
}
if (get) {
if (*value) snprintf(reply, reply_size, "Error: unexpected argument");
else if (index == 4) snprintf(reply, reply_size, "> %s", displayInboxModeName(candidate.inbox));
else if (index & 1) snprintf(reply, reply_size, "> %u", unsigned(profile.seconds));
else snprintf(reply, reply_size, "> %s", displayModeName(profile.mode));
return true;
}
while (*value == ' ') ++value;
if (index == 4) {
unsigned mode = 0;
while (mode <= unsigned(DisplayInboxMode::Unread)
&& strcmp(value, displayInboxModeName(DisplayInboxMode(mode))) != 0) ++mode;
if (mode > unsigned(DisplayInboxMode::Unread)) {
snprintf(reply, reply_size, "Error: use history|pending|unread"); return true;
}
candidate.inbox = DisplayInboxMode(mode);
} else if (index & 1) {
uint32_t seconds = 0;
bool valid = *value != 0;
for (const char* p = value; *p && valid; ++p) {
valid = *p >= '0' && *p <= '9';
if (valid) { seconds = seconds * 10 + (*p - '0'); valid = seconds <= 3600; }
}
if (!valid || seconds == 0) {
snprintf(reply, reply_size, "Error: timeout must be 1-3600 seconds"); return true;
}
profile.seconds = uint16_t(seconds);
} else if (!parseDisplayMode(value, profile.mode)) {
snprintf(reply, reply_size, "Error: use off|on|button|pairing|button-pairing|automatic"); return true;
} else if (displayModeSupportsPairing(profile.mode) && !pairing_available) {
snprintf(reply, reply_size, "Error: BLE pairing display unsupported"); return true;
}
if (!savePrefs(candidate)) snprintf(reply, reply_size, "Error: display settings save failed");
else { displayPowerPrefs() = candidate; snprintf(reply, reply_size, "OK"); }
return true;
#endif
}
} }