mirror of https://github.com/meshcore-dev/MeshCore
7 changed files with 485 additions and 69 deletions
@ -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); |
||||
|
}; |
||||
Loading…
Reference in new issue