mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-07-16 23:12:02 +00:00
* first draft of new ConfigSerializer. (simple embedded JSON prefs)
This commit is contained in:
@@ -872,7 +872,6 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
#endif
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
|
||||
@@ -628,7 +628,6 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
recv_pkt_region = NULL;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f;
|
||||
|
||||
@@ -710,7 +710,6 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
|
||||
+20
-64
@@ -28,16 +28,21 @@ static bool isValidName(const char *n) {
|
||||
}
|
||||
|
||||
void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
if (fs->exists("/com_prefs")) {
|
||||
loadPrefsInt(fs, "/com_prefs"); // new filename
|
||||
} else if (fs->exists("/node_prefs")) {
|
||||
loadPrefsInt(fs, "/node_prefs");
|
||||
savePrefs(fs); // save to new filename
|
||||
fs->remove("/node_prefs"); // remove old
|
||||
if (fs->exists("/ser_prefs")) {
|
||||
File file = fs->open("/ser_prefs");
|
||||
if (file) {
|
||||
_prefs->loadSerial(file); // new Serial prefs
|
||||
file.close();
|
||||
}
|
||||
} else if (fs->exists("/com_prefs")) {
|
||||
loadPrefsInt(fs, "/com_prefs");
|
||||
if (savePrefs(fs)) { // save to new Serial prefs
|
||||
// fs->remove("/com_prefs"); // remove old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy prefs loader
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open(filename, "r");
|
||||
#else
|
||||
@@ -130,70 +135,21 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
bool CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
fs->remove("/com_prefs");
|
||||
File file = fs->open("/com_prefs", FILE_O_WRITE);
|
||||
fs->remove("/ser_prefs");
|
||||
File file = fs->open("/ser_prefs", FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/com_prefs", "w");
|
||||
File file = fs->open("/ser_prefs", "w");
|
||||
#else
|
||||
File file = fs->open("/com_prefs", "w", true);
|
||||
File file = fs->open("/ser_prefs", "w", true);
|
||||
#endif
|
||||
if (file) {
|
||||
uint8_t pad[8];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
|
||||
file.write((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.write(pad, 4); // 36
|
||||
file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.write(pad, 1); // 79 : 1 byte unused (rx_boosted_gain moved to end)
|
||||
file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.write(pad, 4); // 108 : 4 byte unused
|
||||
file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
|
||||
file.write((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122
|
||||
file.write(pad, 1); // 123
|
||||
file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
|
||||
file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
|
||||
file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
|
||||
file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
|
||||
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
|
||||
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
|
||||
file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
|
||||
file.write(pad, 3); // 153
|
||||
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
|
||||
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
|
||||
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
|
||||
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
|
||||
file.write((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
|
||||
file.write((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
|
||||
// next: 295
|
||||
|
||||
bool success = _prefs->saveSerial(file);
|
||||
file.close();
|
||||
return success;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define MIN_LOCAL_ADVERT_INTERVAL 60
|
||||
|
||||
+120
-2
@@ -5,6 +5,7 @@
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <helpers/RegionMap.h>
|
||||
#include <helpers/ConfigSerializer.h>
|
||||
|
||||
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
|
||||
#define WITH_BRIDGE
|
||||
@@ -19,7 +20,9 @@
|
||||
#define LOOP_DETECT_MODERATE 2
|
||||
#define LOOP_DETECT_STRICT 3
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
class NodePrefs : public ConfigSerializer {
|
||||
public:
|
||||
// in-memory backing data
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
double node_lat, node_lon;
|
||||
@@ -65,6 +68,121 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
uint8_t loop_detect;
|
||||
uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean)
|
||||
|
||||
private:
|
||||
class RadioPrefs : public ConfigSerializer {
|
||||
NodePrefs* _parent;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("freq", _parent->freq);
|
||||
def("bw", _parent->bw);
|
||||
def("sf", _parent->sf);
|
||||
def("cr", _parent->cr);
|
||||
def("cad", _parent->cad_enabled);
|
||||
def("int_thr", _parent->interference_threshold);
|
||||
def("rxgain", _parent->rx_boosted_gain);
|
||||
def("fem_rxgain", _parent->rx_boosted_gain);
|
||||
def("tx", _parent->tx_power_dbm);
|
||||
def("af", _parent->airtime_factor);
|
||||
def("rxdelay", _parent->rx_delay_base);
|
||||
def("f_txdelay", _parent->tx_delay_factor);
|
||||
def("d_txdelay", _parent->direct_tx_delay_factor);
|
||||
def("agc_int", _parent->agc_reset_interval);
|
||||
def("hash_mode", _parent->path_hash_mode);
|
||||
def("multi_ack", _parent->multi_acks);
|
||||
}
|
||||
public:
|
||||
RadioPrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
};
|
||||
RadioPrefs radio;
|
||||
|
||||
class BridgePrefs : public ConfigSerializer {
|
||||
NodePrefs* _parent;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("en", _parent->bridge_enabled); // boolean
|
||||
def("delay", _parent->bridge_delay); // milliseconds (default 500 ms)
|
||||
def("src", _parent->bridge_pkt_src); // 0 = logTx, 1 = logRx (default logTx)
|
||||
def("baud", _parent->bridge_baud); // 9600, 19200, 38400, 57600, 115200 (default 115200)
|
||||
def("ch", _parent->bridge_channel); // 1-14 (ESP-NOW only)
|
||||
def("secret", _parent->bridge_secret, sizeof(_parent->bridge_secret)); // for XOR encryption of bridge packets (ESP-NOW only)
|
||||
}
|
||||
public:
|
||||
BridgePrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
};
|
||||
BridgePrefs bridge;
|
||||
|
||||
class GPSPrefs : public ConfigSerializer {
|
||||
NodePrefs* _parent;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("en", _parent->gps_enabled); // boolean
|
||||
def("int", _parent->gps_interval); // interval in seconds
|
||||
def("adv_loc", _parent->advert_loc_policy);
|
||||
}
|
||||
public:
|
||||
GPSPrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
};
|
||||
GPSPrefs gps;
|
||||
|
||||
class PowerPrefs : public ConfigSerializer {
|
||||
NodePrefs* _parent;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("adc_mult", _parent->adc_multiplier);
|
||||
def("pwr_sav_en", _parent->powersaving_enabled);
|
||||
}
|
||||
public:
|
||||
PowerPrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
};
|
||||
PowerPrefs power;
|
||||
|
||||
class RepeatPrefs : public ConfigSerializer {
|
||||
NodePrefs* _parent;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("disable", _parent->disable_fwd);
|
||||
def("f_max", _parent->flood_max);
|
||||
def("f_max_uns", _parent->flood_max_unscoped);
|
||||
def("f_max_adv", _parent->flood_max_advert);
|
||||
def("loop", _parent->loop_detect);
|
||||
}
|
||||
public:
|
||||
RepeatPrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
};
|
||||
RepeatPrefs repeat;
|
||||
|
||||
class RoomPrefs : public ConfigSerializer {
|
||||
NodePrefs* _parent;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("rd_only", _parent->allow_read_only);
|
||||
}
|
||||
public:
|
||||
RoomPrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
};
|
||||
RoomPrefs room;
|
||||
|
||||
protected:
|
||||
void structure() override {
|
||||
def("name", node_name, sizeof(node_name));
|
||||
def("pass", password, sizeof(password));
|
||||
def("guest", guest_password, sizeof(guest_password));
|
||||
def("owner", owner_info, sizeof(owner_info));
|
||||
def("adv_int", advert_interval);
|
||||
def("f_adv_int", flood_advert_interval);
|
||||
def("lat", node_lat);
|
||||
def("lon", node_lon);
|
||||
def("radio", radio);
|
||||
def("bridge", bridge);
|
||||
def("gps", gps);
|
||||
def("repeat", repeat);
|
||||
def("room", room);
|
||||
def("power", power);
|
||||
}
|
||||
|
||||
public:
|
||||
NodePrefs() : ConfigSerializer(), bridge(this), gps(this), radio(this), power(this), repeat(this), room(this) { }
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
@@ -139,7 +257,7 @@ public:
|
||||
: _board(&board), _rtc(&rtc), _sensors(&sensors), _region_map(®ion_map), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { }
|
||||
|
||||
void loadPrefs(FILESYSTEM* _fs);
|
||||
void savePrefs(FILESYSTEM* _fs);
|
||||
bool savePrefs(FILESYSTEM* _fs);
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
#include "ConfigSerializer.h"
|
||||
|
||||
bool ConfigSerializer::saveSerial(Stream& s) {
|
||||
Context context(&s, OP::WRITE);
|
||||
_context = &context; // set the context for structure() call
|
||||
s.print("{"); // root object
|
||||
_first = true;
|
||||
structure();
|
||||
if (s.print("}") != 1) context.success = false; // failure detect
|
||||
_context = NULL;
|
||||
return context.success;
|
||||
}
|
||||
|
||||
#define TOK_ERROR -1
|
||||
#define TOK_EOF 0
|
||||
#define TOK_KEY 1
|
||||
#define TOK_VALUE 2
|
||||
#define TOK_START_OBJ 3
|
||||
#define TOK_END_OBJ 4
|
||||
#define TOK_WHITESPACE 5
|
||||
|
||||
static bool is_whitespace(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||
}
|
||||
static bool is_key_char(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
|
||||
}
|
||||
static bool is_value_char(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-' || c == '.';
|
||||
}
|
||||
|
||||
#define EXPECT_OPEN_BRACE 0
|
||||
#define EXPECT_KEY 1
|
||||
#define EXPECT_VAL_OR_OBJ 2
|
||||
#define EXPECT_STRING_VAL 3
|
||||
#define EXPECT_STRING_ESCAPE 4
|
||||
#define EXPECT_COMMA_OR_CLOSE 5
|
||||
#define EXPECT_COMMA_OR_KEY 6
|
||||
#define EXPECT_COMMA_OR_KEY_OR_CLOSE 7
|
||||
|
||||
int ConfigSerializer::Context::readNext() {
|
||||
char c;
|
||||
if (pending) {
|
||||
c = pending;
|
||||
pending = 0;
|
||||
} else {
|
||||
if (_f->available() == 0) return TOK_EOF;
|
||||
|
||||
int n = _f->read();
|
||||
if (n < 0) return TOK_EOF;
|
||||
c = (char)n;
|
||||
}
|
||||
|
||||
switch (rd_mode) {
|
||||
case EXPECT_OPEN_BRACE:
|
||||
if (c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; }
|
||||
if (is_whitespace(c)) return TOK_WHITESPACE;
|
||||
return TOK_ERROR;
|
||||
|
||||
case EXPECT_COMMA_OR_KEY_OR_CLOSE:
|
||||
if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; }
|
||||
case EXPECT_COMMA_OR_KEY:
|
||||
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; }
|
||||
case EXPECT_KEY:
|
||||
if (rd_len > 0 && c == ':') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_VAL_OR_OBJ; return TOK_KEY; }
|
||||
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE;
|
||||
if (rd_len < CONFIG_MAX_KEYLEN-1 && is_key_char(c)) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }
|
||||
return TOK_ERROR;
|
||||
|
||||
case EXPECT_VAL_OR_OBJ:
|
||||
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE;
|
||||
if (rd_len == 0 && c == '"') { rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; }
|
||||
if (rd_len == 0 && c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; }
|
||||
if (is_value_char(c) && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }
|
||||
if (rd_len > 0 && (c == ',' || c == '}' || is_whitespace(c))) { pending = c; rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; }
|
||||
return TOK_ERROR;
|
||||
|
||||
case EXPECT_STRING_ESCAPE:
|
||||
if ((c == '"' || c == '\\' || c == '/') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; }
|
||||
return TOK_ERROR; // unsupport escape
|
||||
|
||||
case EXPECT_STRING_VAL:
|
||||
if (c == '"') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; }
|
||||
if (c == '\\') { rd_mode = EXPECT_STRING_ESCAPE; return TOK_WHITESPACE; }
|
||||
if (rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }
|
||||
return TOK_ERROR;
|
||||
|
||||
case EXPECT_COMMA_OR_CLOSE:
|
||||
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; }
|
||||
if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; }
|
||||
if (is_whitespace(c)) return TOK_WHITESPACE;
|
||||
return TOK_ERROR;
|
||||
}
|
||||
return TOK_ERROR; // unknown mode
|
||||
}
|
||||
|
||||
bool ConfigSerializer::loadSerial(Stream& s) {
|
||||
Context context(&s, OP::READ);
|
||||
_context = &context; // set the context for structure() call
|
||||
// parse the Json file
|
||||
uint8_t sp = 0;
|
||||
int next_tok;
|
||||
|
||||
while ((next_tok = context.readNext()) > TOK_EOF) {
|
||||
if (next_tok == TOK_KEY) {
|
||||
context.setKey(sp, context.getToken());
|
||||
} else if (next_tok == TOK_VALUE) {
|
||||
_depth = 1; // re-run the structure() hierarchy again (looking for specific key, at specific depth)
|
||||
structure();
|
||||
} else if (next_tok == TOK_START_OBJ) {
|
||||
if (sp < CONFIF_MAX_DEPTH - 1) {
|
||||
sp++;
|
||||
} else {
|
||||
Serial.printf("Error: max nesting reached"); // TODO: debug logging
|
||||
context.success = false;
|
||||
break;
|
||||
}
|
||||
} else if (next_tok == TOK_END_OBJ) {
|
||||
if (sp > 0) {
|
||||
sp--;
|
||||
} else {
|
||||
Serial.printf("Error: too many closing '}'"); // TODO: debug logging
|
||||
context.success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sp != 0 || next_tok == TOK_ERROR) {
|
||||
context.success = false; // unmatched { }, or other parse error
|
||||
}
|
||||
_context = NULL;
|
||||
return context.success;
|
||||
}
|
||||
|
||||
void ConfigSerializer::writeComma() {
|
||||
if (_first) {
|
||||
_first = false;
|
||||
} else {
|
||||
_context->file()->print(","); // comma separated properties
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, char* value, size_t max_len) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":\"");
|
||||
_context->file()->print(value); // TODO: escape quotes
|
||||
_context->file()->print("\"");
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
strncpy(value, _context->getToken(), max_len - 1);
|
||||
value[max_len - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, int32_t& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print(value);
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = atol(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, uint32_t& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print(value);
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = atol(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, int16_t& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print((int32_t) value, 10);
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = atol(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, uint16_t& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print((uint32_t) value, 10);
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = atoi(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, uint8_t& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print((uint32_t) value, 10);
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = atoi(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, int8_t& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print((int32_t) value, 10);
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = atoi(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, double& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print(value, 6); // REVISIT: how many dec places?
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = atof(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, float& value) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":");
|
||||
_context->file()->print(value, 4); // REVISIT: how many dec places?
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
value = (float) atof(_context->getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigSerializer::def(const char* key, ConfigSerializer& sub_obj) {
|
||||
if (_context->op() == OP::WRITE) {
|
||||
writeComma();
|
||||
_context->file()->print(key);
|
||||
_context->file()->print(":{");
|
||||
sub_obj._context = _context; // inherit the Context
|
||||
sub_obj._first = true;
|
||||
sub_obj.structure(); // recurse into sub object
|
||||
if (_context->file()->print("}") != 1) _context->success = false; // failure detect
|
||||
} else {
|
||||
if (_context->keyMatch(_depth, key)) {
|
||||
sub_obj._context = _context; // inherit the Context
|
||||
sub_obj._depth = _depth + 1;
|
||||
sub_obj.structure(); // recurse into sub object
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef CONFIF_MAX_DEPTH
|
||||
#define CONFIF_MAX_DEPTH 8
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_MAX_KEYLEN
|
||||
#define CONFIG_MAX_KEYLEN 16
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_MAX_TOKEN_LEN
|
||||
#define CONFIG_MAX_TOKEN_LEN 128
|
||||
#endif
|
||||
|
||||
class ConfigSerializer {
|
||||
bool _first;
|
||||
int8_t _depth;
|
||||
|
||||
enum OP { READ, WRITE };
|
||||
|
||||
class Context {
|
||||
Stream* _f;
|
||||
OP _op;
|
||||
uint8_t rd_len;
|
||||
uint8_t rd_mode;
|
||||
char pending;
|
||||
char rd_buf[CONFIG_MAX_TOKEN_LEN];
|
||||
char _keys[CONFIF_MAX_DEPTH][CONFIG_MAX_KEYLEN];
|
||||
|
||||
public:
|
||||
bool success = true;
|
||||
Context(Stream* f, OP op) : _f(f), _op(op) { rd_buf[rd_len = 0] = 0; rd_mode = 0; pending = 0; }
|
||||
OP op() const { return _op; }
|
||||
Stream* file() const { return _f; }
|
||||
int readNext();
|
||||
const char* getToken() const { return rd_buf; }
|
||||
bool keyMatch(int8_t depth, const char* key) { return strcmp(key, _keys[depth]) == 0; }
|
||||
void setKey(uint8_t depth, const char* key) { strcpy(_keys[depth], key); }
|
||||
};
|
||||
|
||||
Context* _context = NULL;
|
||||
|
||||
void writeComma();
|
||||
|
||||
protected:
|
||||
ConfigSerializer() { }
|
||||
|
||||
void def(const char* key, char* value, size_t max_len); // max_len inclusive of null
|
||||
void def(const char* key, int32_t& value);
|
||||
void def(const char* key, int16_t& value);
|
||||
void def(const char* key, int8_t& value);
|
||||
void def(const char* key, uint32_t& value);
|
||||
void def(const char* key, uint16_t& value);
|
||||
void def(const char* key, uint8_t& value);
|
||||
void def(const char* key, float& value);
|
||||
void def(const char* key, double& value);
|
||||
void def(const char* key, bool& value);
|
||||
void def(const char* key, ConfigSerializer& sub_obj);
|
||||
|
||||
virtual void structure() = 0;
|
||||
|
||||
public:
|
||||
bool loadSerial(Stream& s);
|
||||
bool saveSerial(Stream& s);
|
||||
};
|
||||
Reference in New Issue
Block a user