mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 10:23:44 +00:00
284 lines
10 KiB
C++
284 lines
10 KiB
C++
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
* RepeaterDataStore - Filesystem storage for repeater
|
|
*/
|
|
|
|
#include "RepeaterDataStore.h"
|
|
#include <zephyr/fs/fs.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
LOG_MODULE_REGISTER(zephcore_repeater_store, CONFIG_ZEPHCORE_DATASTORE_LOG_LEVEL);
|
|
|
|
RepeaterDataStore::RepeaterDataStore() : _initialized(false) {
|
|
}
|
|
|
|
bool RepeaterDataStore::begin() {
|
|
if (_initialized) return true;
|
|
|
|
/* Create repeater directory if it doesn't exist */
|
|
struct fs_dirent entry;
|
|
int ret = fs_stat(BASE_PATH, &entry);
|
|
if (ret < 0) {
|
|
ret = fs_mkdir(BASE_PATH);
|
|
if (ret < 0 && ret != -EEXIST) {
|
|
LOG_ERR("Failed to create %s: %d", BASE_PATH, ret);
|
|
return false;
|
|
}
|
|
LOG_INF("Created %s directory", BASE_PATH);
|
|
}
|
|
|
|
_initialized = true;
|
|
LOG_INF("RepeaterDataStore initialized at %s", BASE_PATH);
|
|
return true;
|
|
}
|
|
|
|
const char* RepeaterDataStore::getBasePath() const { return BASE_PATH; }
|
|
|
|
const char* RepeaterDataStore::getAclPath() const {
|
|
static char buf[48];
|
|
snprintf(buf, sizeof(buf), "%s/acl", BASE_PATH);
|
|
return buf;
|
|
}
|
|
|
|
const char* RepeaterDataStore::getRegionsPath() const {
|
|
static char buf[48];
|
|
snprintf(buf, sizeof(buf), "%s/regions2", BASE_PATH);
|
|
return buf;
|
|
}
|
|
|
|
bool RepeaterDataStore::loadIdentity(mesh::LocalIdentity& id) {
|
|
char path[48];
|
|
snprintf(path, sizeof(path), "%s/_main.id", BASE_PATH);
|
|
|
|
struct fs_file_t file;
|
|
fs_file_t_init(&file);
|
|
|
|
int ret = fs_open(&file, path, FS_O_READ);
|
|
if (ret < 0) {
|
|
LOG_WRN("No identity file at %s", path);
|
|
return false;
|
|
}
|
|
|
|
uint8_t buf[PRV_KEY_SIZE + PUB_KEY_SIZE];
|
|
ssize_t n = fs_read(&file, buf, sizeof(buf));
|
|
fs_close(&file);
|
|
|
|
LOG_INF("loadIdentity: read %d bytes from %s", (int)n, path);
|
|
|
|
if (n >= PRV_KEY_SIZE) {
|
|
if (id.readFrom(buf, n)) {
|
|
LOG_INF("Loaded identity from %s", path);
|
|
return true;
|
|
}
|
|
LOG_ERR("loadIdentity: readFrom failed");
|
|
}
|
|
|
|
LOG_ERR("Identity file corrupt");
|
|
return false;
|
|
}
|
|
|
|
bool RepeaterDataStore::saveIdentity(const mesh::LocalIdentity& id) {
|
|
if (!_initialized) begin();
|
|
|
|
char path[48];
|
|
snprintf(path, sizeof(path), "%s/_main.id", BASE_PATH);
|
|
|
|
fs_unlink(path);
|
|
|
|
struct fs_file_t file;
|
|
fs_file_t_init(&file);
|
|
|
|
int ret = fs_open(&file, path, FS_O_CREATE | FS_O_WRITE);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to open %s for write: %d", path, ret);
|
|
return false;
|
|
}
|
|
|
|
uint8_t buf[PRV_KEY_SIZE];
|
|
int len = id.writeTo(buf, sizeof(buf));
|
|
ssize_t n = fs_write(&file, buf, len);
|
|
fs_close(&file);
|
|
|
|
if (n == len) {
|
|
LOG_INF("Saved identity to %s", path);
|
|
return true;
|
|
}
|
|
|
|
LOG_ERR("Failed to write identity: wrote %d of %d", (int)n, len);
|
|
return false;
|
|
}
|
|
|
|
bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) {
|
|
char path[48];
|
|
snprintf(path, sizeof(path), "%s/prefs", BASE_PATH);
|
|
|
|
struct fs_file_t file;
|
|
fs_file_t_init(&file);
|
|
|
|
int ret = fs_open(&file, path, FS_O_READ);
|
|
if (ret < 0) {
|
|
LOG_DBG("No prefs file at %s, using defaults", path);
|
|
initNodePrefs(&prefs);
|
|
strcpy(prefs.node_name, "Repeater");
|
|
return true;
|
|
}
|
|
|
|
struct fs_dirent entry;
|
|
ret = fs_stat(path, &entry);
|
|
LOG_INF("loadPrefs: file size = %d bytes", ret < 0 ? 0 : (int)entry.size);
|
|
|
|
uint8_t pad[8];
|
|
|
|
/* Read prefs in same format as Arduino CommonCLI for compatibility */
|
|
fs_read(&file, &prefs.airtime_factor, sizeof(prefs.airtime_factor));
|
|
fs_read(&file, &prefs.node_name, sizeof(prefs.node_name));
|
|
fs_read(&file, pad, 4);
|
|
fs_read(&file, &prefs.node_lat, sizeof(prefs.node_lat));
|
|
fs_read(&file, &prefs.node_lon, sizeof(prefs.node_lon));
|
|
fs_read(&file, &prefs.password, sizeof(prefs.password));
|
|
fs_read(&file, &prefs.freq, sizeof(prefs.freq));
|
|
fs_read(&file, &prefs.tx_power_dbm, sizeof(prefs.tx_power_dbm));
|
|
fs_read(&file, &prefs.disable_fwd, sizeof(prefs.disable_fwd));
|
|
fs_read(&file, &prefs.advert_interval, sizeof(prefs.advert_interval));
|
|
fs_read(&file, pad, 1);
|
|
fs_read(&file, &prefs.rx_delay_base, sizeof(prefs.rx_delay_base));
|
|
fs_read(&file, &prefs.tx_delay_factor, sizeof(prefs.tx_delay_factor));
|
|
fs_read(&file, &prefs.guest_password, sizeof(prefs.guest_password));
|
|
fs_read(&file, &prefs.direct_tx_delay_factor, sizeof(prefs.direct_tx_delay_factor));
|
|
fs_read(&file, &prefs.backoff_multiplier, sizeof(prefs.backoff_multiplier));
|
|
fs_read(&file, &prefs.sf, sizeof(prefs.sf));
|
|
fs_read(&file, &prefs.cr, sizeof(prefs.cr));
|
|
fs_read(&file, &prefs.allow_read_only, sizeof(prefs.allow_read_only));
|
|
fs_read(&file, &prefs.multi_acks, sizeof(prefs.multi_acks));
|
|
fs_read(&file, &prefs.bw, sizeof(prefs.bw));
|
|
fs_read(&file, &prefs.agc_reset_interval, sizeof(prefs.agc_reset_interval));
|
|
fs_read(&file, pad, 3);
|
|
fs_read(&file, &prefs.flood_max, sizeof(prefs.flood_max));
|
|
fs_read(&file, &prefs.flood_advert_interval, sizeof(prefs.flood_advert_interval));
|
|
fs_read(&file, &prefs.interference_threshold, sizeof(prefs.interference_threshold));
|
|
fs_read(&file, pad, 25); // skip bridge settings
|
|
fs_read(&file, &prefs.powersaving_enabled, sizeof(prefs.powersaving_enabled));
|
|
fs_read(&file, pad, 3);
|
|
fs_read(&file, &prefs.gps_enabled, sizeof(prefs.gps_enabled));
|
|
fs_read(&file, &prefs.gps_interval, sizeof(prefs.gps_interval));
|
|
fs_read(&file, &prefs.advert_loc_policy, sizeof(prefs.advert_loc_policy));
|
|
fs_read(&file, &prefs.discovery_mod_timestamp, sizeof(prefs.discovery_mod_timestamp));
|
|
fs_read(&file, &prefs.adc_multiplier, sizeof(prefs.adc_multiplier));
|
|
fs_read(&file, prefs.owner_info, sizeof(prefs.owner_info));
|
|
|
|
fs_close(&file);
|
|
|
|
/* Migrate uninitialized backoff_multiplier (0.0 or NaN) to default */
|
|
if (prefs.backoff_multiplier == 0.0f || prefs.backoff_multiplier != prefs.backoff_multiplier) {
|
|
prefs.backoff_multiplier = 0.5f;
|
|
}
|
|
|
|
LOG_INF("Loaded prefs from %s", path);
|
|
LOG_INF(" name='%s' freq=%.3f sf=%u bw=%.1f tx_pwr=%d",
|
|
prefs.node_name, (double)prefs.freq, prefs.sf, (double)prefs.bw, prefs.tx_power_dbm);
|
|
|
|
/* Validate radio params - use defaults if garbage */
|
|
if (prefs.freq < 300.0f || prefs.freq > 1000.0f ||
|
|
prefs.sf < 5 || prefs.sf > 12 ||
|
|
prefs.bw < 7.0f || prefs.bw > 500.0f) {
|
|
LOG_WRN("Invalid radio params in prefs, using defaults: freq=%.3f sf=%u bw=%.1f",
|
|
(double)prefs.freq, prefs.sf, (double)prefs.bw);
|
|
prefs.freq = 869.618f;
|
|
prefs.bw = 62.5f;
|
|
prefs.sf = 8;
|
|
prefs.cr = 8;
|
|
prefs.tx_power_dbm = 22;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RepeaterDataStore::savePrefs(const NodePrefs& prefs) {
|
|
if (!_initialized) begin();
|
|
|
|
char path[48];
|
|
snprintf(path, sizeof(path), "%s/prefs", BASE_PATH);
|
|
|
|
fs_unlink(path);
|
|
|
|
struct fs_file_t file;
|
|
fs_file_t_init(&file);
|
|
|
|
int ret = fs_open(&file, path, FS_O_CREATE | FS_O_WRITE);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to open %s for write: %d", path, ret);
|
|
return false;
|
|
}
|
|
|
|
uint8_t pad[25];
|
|
memset(pad, 0, sizeof(pad));
|
|
|
|
/* Write prefs in same format as Arduino CommonCLI for compatibility */
|
|
fs_write(&file, &prefs.airtime_factor, sizeof(prefs.airtime_factor));
|
|
fs_write(&file, &prefs.node_name, sizeof(prefs.node_name));
|
|
fs_write(&file, pad, 4);
|
|
fs_write(&file, &prefs.node_lat, sizeof(prefs.node_lat));
|
|
fs_write(&file, &prefs.node_lon, sizeof(prefs.node_lon));
|
|
fs_write(&file, &prefs.password, sizeof(prefs.password));
|
|
fs_write(&file, &prefs.freq, sizeof(prefs.freq));
|
|
fs_write(&file, &prefs.tx_power_dbm, sizeof(prefs.tx_power_dbm));
|
|
fs_write(&file, &prefs.disable_fwd, sizeof(prefs.disable_fwd));
|
|
fs_write(&file, &prefs.advert_interval, sizeof(prefs.advert_interval));
|
|
fs_write(&file, pad, 1);
|
|
fs_write(&file, &prefs.rx_delay_base, sizeof(prefs.rx_delay_base));
|
|
fs_write(&file, &prefs.tx_delay_factor, sizeof(prefs.tx_delay_factor));
|
|
fs_write(&file, &prefs.guest_password, sizeof(prefs.guest_password));
|
|
fs_write(&file, &prefs.direct_tx_delay_factor, sizeof(prefs.direct_tx_delay_factor));
|
|
fs_write(&file, &prefs.backoff_multiplier, sizeof(prefs.backoff_multiplier));
|
|
fs_write(&file, &prefs.sf, sizeof(prefs.sf));
|
|
fs_write(&file, &prefs.cr, sizeof(prefs.cr));
|
|
fs_write(&file, &prefs.allow_read_only, sizeof(prefs.allow_read_only));
|
|
fs_write(&file, &prefs.multi_acks, sizeof(prefs.multi_acks));
|
|
fs_write(&file, &prefs.bw, sizeof(prefs.bw));
|
|
fs_write(&file, &prefs.agc_reset_interval, sizeof(prefs.agc_reset_interval));
|
|
fs_write(&file, pad, 3);
|
|
fs_write(&file, &prefs.flood_max, sizeof(prefs.flood_max));
|
|
fs_write(&file, &prefs.flood_advert_interval, sizeof(prefs.flood_advert_interval));
|
|
fs_write(&file, &prefs.interference_threshold, sizeof(prefs.interference_threshold));
|
|
fs_write(&file, pad, 25); // skip bridge settings
|
|
fs_write(&file, &prefs.powersaving_enabled, sizeof(prefs.powersaving_enabled));
|
|
fs_write(&file, pad, 3);
|
|
fs_write(&file, &prefs.gps_enabled, sizeof(prefs.gps_enabled));
|
|
fs_write(&file, &prefs.gps_interval, sizeof(prefs.gps_interval));
|
|
fs_write(&file, &prefs.advert_loc_policy, sizeof(prefs.advert_loc_policy));
|
|
fs_write(&file, &prefs.discovery_mod_timestamp, sizeof(prefs.discovery_mod_timestamp));
|
|
fs_write(&file, &prefs.adc_multiplier, sizeof(prefs.adc_multiplier));
|
|
fs_write(&file, prefs.owner_info, sizeof(prefs.owner_info));
|
|
|
|
fs_close(&file);
|
|
LOG_INF("Saved prefs to %s", path);
|
|
return true;
|
|
}
|
|
|
|
bool RepeaterDataStore::formatFileSystem() {
|
|
LOG_WRN("Factory reset: erasing repeater data at %s", BASE_PATH);
|
|
|
|
struct fs_dir_t dir;
|
|
fs_dir_t_init(&dir);
|
|
|
|
int ret = fs_opendir(&dir, BASE_PATH);
|
|
if (ret < 0) {
|
|
LOG_WRN("No repeater directory to erase");
|
|
return true;
|
|
}
|
|
|
|
struct fs_dirent entry;
|
|
char path[280];
|
|
|
|
while (fs_readdir(&dir, &entry) == 0 && entry.name[0] != '\0') {
|
|
snprintf(path, sizeof(path), "%s/%s", BASE_PATH, entry.name);
|
|
LOG_INF("Deleting %s", path);
|
|
fs_unlink(path);
|
|
}
|
|
fs_closedir(&dir);
|
|
|
|
LOG_INF("Repeater data erased");
|
|
return true;
|
|
}
|