mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-11-04 16:47:15 +00:00
208 lines
8.0 KiB
C++
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
|
|
}
|
|
|
|
} }
|