mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
feat(sim): add variants/sim/ — real companion_radio firmware on native + Emscripten
New board variant compiling the unmodified MyMesh/UITask/DataStore app logic against real mesh::Radio/MainBoard/RTCClock/RNG interfaces, for running the actual firmware outside embedded hardware: - Native (plain g++, platform = native): ASCII-art display over stdout, stdin-driven input, local-disk-backed DataStore/IdentityStore. - Emscripten/WASM (variants/sim/build_wasm.sh, since PlatformIO's native platform force-overrides any CC/CXX toolchain override back to system clang++): canvas-backed display, IDBFS-backed persistence across page reloads, JS-callable input via sim_enqueue_key(), emscripten_set_main_loop. Real rweather/Crypto (AES128/SHA256/Ed25519) vendored unmodified and proven working on both targets. variants/sim/web/index.html is a bare verification harness, not the polished website embed.
This commit is contained in:
@@ -20,6 +20,9 @@ platformio.local.ini
|
|||||||
CODE_REVIEW.md
|
CODE_REVIEW.md
|
||||||
tools/pngs/
|
tools/pngs/
|
||||||
tools/gpx/
|
tools/gpx/
|
||||||
|
sim_data/
|
||||||
|
variants/sim/tools/emsdk/
|
||||||
|
variants/sim/web/build/
|
||||||
|
|
||||||
# graphify
|
# graphify
|
||||||
graphify-out/
|
graphify-out/
|
||||||
|
|||||||
@@ -201,6 +201,8 @@ bool DataStore::formatFileSystem() {
|
|||||||
bool fs_success = ((fs::SPIFFSFS *)_fs)->format();
|
bool fs_success = ((fs::SPIFFSFS *)_fs)->format();
|
||||||
esp_err_t nvs_err = nvs_flash_erase(); // no need to reinit, will be done by reboot
|
esp_err_t nvs_err = nvs_flash_erase(); // no need to reinit, will be done by reboot
|
||||||
return fs_success && (nvs_err == ESP_OK);
|
return fs_success && (nvs_err == ESP_OK);
|
||||||
|
#elif defined(SIM_PLATFORM)
|
||||||
|
return _fs->format();
|
||||||
#else
|
#else
|
||||||
#error "need to implement format()"
|
#error "need to implement format()"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2845,7 +2845,12 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
bool success = _store->formatFileSystem();
|
bool success = _store->formatFileSystem();
|
||||||
if (success) {
|
if (success) {
|
||||||
writeOKFrame();
|
writeOKFrame();
|
||||||
|
#ifdef SIM_PLATFORM
|
||||||
|
// Skip the pre-reboot UX pause -- board.reboot() just exits the
|
||||||
|
// process in the sim (see SimMainBoard::reboot()).
|
||||||
|
#else
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
#endif
|
||||||
board.reboot(); // doesn't return
|
board.reboot(); // doesn't return
|
||||||
} else {
|
} else {
|
||||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ static uint32_t _atoi(const char* sp) {
|
|||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
#include <SPIFFS.h>
|
#include <SPIFFS.h>
|
||||||
DataStore store(SPIFFS, rtc_clock);
|
DataStore store(SPIFFS, rtc_clock);
|
||||||
|
#elif defined(SIM_PLATFORM)
|
||||||
|
#include <SimFS.h>
|
||||||
|
// Real files under ./sim_data/ (relative to the process's cwd) so
|
||||||
|
// NodePrefs/contacts/identity genuinely round-trip across process
|
||||||
|
// restarts -- see SimFS.h.
|
||||||
|
SimFS sim_fs("./sim_data");
|
||||||
|
DataStore store(sim_fs, rtc_clock);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
@@ -88,6 +95,11 @@ static uint32_t _atoi(const char* sp) {
|
|||||||
#elif defined(STM32_PLATFORM)
|
#elif defined(STM32_PLATFORM)
|
||||||
#include <helpers/ArduinoSerialInterface.h>
|
#include <helpers/ArduinoSerialInterface.h>
|
||||||
ArduinoSerialInterface serial_interface;
|
ArduinoSerialInterface serial_interface;
|
||||||
|
#elif defined(SIM_PLATFORM)
|
||||||
|
// No real BLE/USB companion-app transport in Phase 1 -- always reports
|
||||||
|
// "not connected". See variants/sim/SimSerialInterface.h.
|
||||||
|
#include <SimSerialInterface.h>
|
||||||
|
SimSerialInterface serial_interface;
|
||||||
#else
|
#else
|
||||||
#error "need to define a serial interface"
|
#error "need to define a serial interface"
|
||||||
#endif
|
#endif
|
||||||
@@ -239,6 +251,18 @@ void setup() {
|
|||||||
serial_interface.begin(Serial);
|
serial_interface.begin(Serial);
|
||||||
#endif
|
#endif
|
||||||
the_mesh.startInterface(serial_interface);
|
the_mesh.startInterface(serial_interface);
|
||||||
|
#elif defined(SIM_PLATFORM)
|
||||||
|
// sim_fs already exists/mkdir'd itself in its constructor above.
|
||||||
|
store.begin();
|
||||||
|
the_mesh.begin(
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
disp != NULL
|
||||||
|
#else
|
||||||
|
false
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
serial_interface.begin();
|
||||||
|
the_mesh.startInterface(serial_interface);
|
||||||
#else
|
#else
|
||||||
#error "need to define filesystem"
|
#error "need to define filesystem"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -9,6 +9,22 @@
|
|||||||
#ifdef WIFI_SSID
|
#ifdef WIFI_SSID
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef SIM_PLATFORM
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
// The single UITask instance is a file-scope global in
|
||||||
|
// examples/companion_radio/main.cpp (`UITask ui_task(...)`, only under
|
||||||
|
// `#ifdef DISPLAY_CLASS`, which the sim build always defines) -- not
|
||||||
|
// reachable from here by name, so UITask::begin() stashes `this` here
|
||||||
|
// (see below) the same way every other single-instance sim glue point
|
||||||
|
// does. Declared up here (rather than next to its use near enqueueKey(),
|
||||||
|
// further down this file) since UITask::begin() -- also further down,
|
||||||
|
// but earlier in the file -- needs it too.
|
||||||
|
static UITask* g_sim_ui_task_for_js = nullptr;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef AUTO_OFF_MILLIS
|
#ifndef AUTO_OFF_MILLIS
|
||||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||||
@@ -1361,6 +1377,10 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
|||||||
uint32_t aoff = autoOffMillis();
|
uint32_t aoff = autoOffMillis();
|
||||||
_auto_off = millis() + (aoff > 0 ? aoff : AUTO_OFF_MILLIS);
|
_auto_off = millis() + (aoff > 0 ? aoff : AUTO_OFF_MILLIS);
|
||||||
|
|
||||||
|
#if defined(SIM_PLATFORM) && defined(__EMSCRIPTEN__)
|
||||||
|
g_sim_ui_task_for_js = this; // see sim_enqueue_key() below
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(CARDKB_I2C)
|
#if defined(CARDKB_I2C)
|
||||||
// On the ENV_PIN_SDA/SCL path, CARDKB_I2C is Wire1, already brought up by
|
// On the ENV_PIN_SDA/SCL path, CARDKB_I2C is Wire1, already brought up by
|
||||||
// sensors.begin() (EnvironmentSensorManager), which runs before this. On
|
// sensors.begin() (EnvironmentSensorManager), which runs before this. On
|
||||||
@@ -2099,6 +2119,23 @@ void UITask::enqueueKey(char c) {
|
|||||||
_kq_head = next;
|
_kq_head = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(SIM_PLATFORM) && defined(__EMSCRIPTEN__)
|
||||||
|
void UITask::injectSimKey(char c) {
|
||||||
|
enqueueKey(checkDisplayOn(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called directly from a host HTML page's JS (button onclick / keydown
|
||||||
|
// listener) -- e.g. `Module._sim_enqueue_key(keyCode)` -- to drive the real
|
||||||
|
// on-device menu. `c` is one of the KEY_* codes in src/helpers/ui/
|
||||||
|
// UIScreen.h (KEY_UP/DOWN/LEFT/RIGHT/ENTER/CANCEL/NEXT/PREV/SELECT), the
|
||||||
|
// exact same values the native build's stdin-poll branch above already
|
||||||
|
// enqueues -- so the host page owns key-mapping (arrow keys, on-screen
|
||||||
|
// D-pad buttons, whatever), not this function.
|
||||||
|
extern "C" EMSCRIPTEN_KEEPALIVE void sim_enqueue_key(char c) {
|
||||||
|
if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->injectSimKey(c);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool UITask::dequeueKey(char& c) {
|
bool UITask::dequeueKey(char& c) {
|
||||||
if (_kq_tail == _kq_head) return false;
|
if (_kq_tail == _kq_head) return false;
|
||||||
c = _key_queue[_kq_tail];
|
c = _key_queue[_kq_tail];
|
||||||
@@ -2364,6 +2401,66 @@ void UITask::loop() {
|
|||||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||||
if (!_locked) enqueueKey(handleTripleClick(KEY_SELECT));
|
if (!_locked) enqueueKey(handleTripleClick(KEY_SELECT));
|
||||||
}
|
}
|
||||||
|
#elif defined(SIM_PLATFORM)
|
||||||
|
// Native terminal input: stdin is put into raw/non-canonical mode by
|
||||||
|
// variants/sim/sim_main.cpp's main(), so keys arrive here one at a time
|
||||||
|
// with no Enter-to-submit line buffering. Non-blocking select() on fd 0
|
||||||
|
// (VMIN=0/VTIME=0 on the fd itself would also work, but select() keeps
|
||||||
|
// the intent -- "is there a key waiting?" -- explicit) takes the place of
|
||||||
|
// every concrete MomentaryButton/GPIO poll above. Every real board maps
|
||||||
|
// its own physical buttons down to the same enqueueKey() choke point;
|
||||||
|
// this is the sim's one input source instead.
|
||||||
|
// Arrow keys -> KEY_UP/DOWN/LEFT/RIGHT
|
||||||
|
// Enter/Space -> KEY_ENTER
|
||||||
|
// Esc/Backspace-> KEY_CANCEL
|
||||||
|
// w/a/s/d -> up/left/down/right (arrow keys need a real terminal;
|
||||||
|
// WASD works even through a dumb pipe/redirected stdin)
|
||||||
|
// n / p -> KEY_NEXT / KEY_PREV
|
||||||
|
{
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(0, &fds);
|
||||||
|
struct timeval tv = {0, 0};
|
||||||
|
if (select(1, &fds, NULL, NULL, &tv) > 0) {
|
||||||
|
uint8_t buf[16];
|
||||||
|
int n = (int)read(0, buf, sizeof(buf));
|
||||||
|
int i = 0;
|
||||||
|
while (i < n) {
|
||||||
|
uint8_t c = buf[i++];
|
||||||
|
char key = 0;
|
||||||
|
if (c == 0x1b && i + 1 < n && buf[i] == '[') {
|
||||||
|
uint8_t code = buf[i + 1];
|
||||||
|
i += 2;
|
||||||
|
switch (code) {
|
||||||
|
case 'A': key = KEY_UP; break;
|
||||||
|
case 'B': key = KEY_DOWN; break;
|
||||||
|
case 'C': key = KEY_RIGHT; break;
|
||||||
|
case 'D': key = KEY_LEFT; break;
|
||||||
|
default: key = 0; break;
|
||||||
|
}
|
||||||
|
} else if (c == 0x1b) {
|
||||||
|
key = KEY_CANCEL;
|
||||||
|
} else if (c == '\r' || c == '\n' || c == ' ') {
|
||||||
|
key = KEY_ENTER;
|
||||||
|
} else if (c == 127 || c == 8) {
|
||||||
|
key = KEY_CANCEL;
|
||||||
|
} else if (c == 'w' || c == 'W') {
|
||||||
|
key = KEY_UP;
|
||||||
|
} else if (c == 's' || c == 'S') {
|
||||||
|
key = KEY_DOWN;
|
||||||
|
} else if (c == 'a' || c == 'A') {
|
||||||
|
key = KEY_LEFT;
|
||||||
|
} else if (c == 'd' || c == 'D') {
|
||||||
|
key = KEY_RIGHT;
|
||||||
|
} else if (c == 'n') {
|
||||||
|
key = KEY_NEXT;
|
||||||
|
} else if (c == 'p') {
|
||||||
|
key = KEY_PREV;
|
||||||
|
}
|
||||||
|
if (key) enqueueKey(checkDisplayOn(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(PIN_USER_BTN_ANA)
|
#if defined(PIN_USER_BTN_ANA)
|
||||||
if (millis() - _analogue_pin_read_millis > 10) {
|
if (millis() - _analogue_pin_read_millis > 10) {
|
||||||
@@ -2616,7 +2713,11 @@ void UITask::loop() {
|
|||||||
_display->drawTextCentered(_display->width() / 2, mid - step, "Low Battery");
|
_display->drawTextCentered(_display->width() / 2, mid - step, "Low Battery");
|
||||||
_display->drawTextCentered(_display->width() / 2, mid, "Shutting down");
|
_display->drawTextCentered(_display->width() / 2, mid, "Shutting down");
|
||||||
_display->endFrame();
|
_display->endFrame();
|
||||||
|
#ifdef SIM_PLATFORM
|
||||||
|
// Skip the pre-shutdown UX pause in the sim.
|
||||||
|
#else
|
||||||
if (_display->isEink() == false) { delay(2000); }
|
if (_display->isEink() == false) { delay(2000); }
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,20 @@ class UITask : public AbstractUITask {
|
|||||||
void enqueueKey(char c);
|
void enqueueKey(char c);
|
||||||
bool dequeueKey(char& c);
|
bool dequeueKey(char& c);
|
||||||
|
|
||||||
|
#if defined(SIM_PLATFORM) && defined(__EMSCRIPTEN__)
|
||||||
|
public:
|
||||||
|
// JS-callable input entry point for the Phase 2 (Emscripten) sim build --
|
||||||
|
// see the sim_enqueue_key() EMSCRIPTEN_KEEPALIVE wrapper defined at the
|
||||||
|
// bottom of UITask.cpp, which is what a host HTML page's buttons/keyboard
|
||||||
|
// listener actually calls. Routes through checkDisplayOn() (wake-on-any-key,
|
||||||
|
// same as every other input source below) then the same enqueueKey()
|
||||||
|
// choke point every real board's button poll already uses -- this is the
|
||||||
|
// sim's substitute for a real board's GPIO/joystick poll, not a new input
|
||||||
|
// path of its own.
|
||||||
|
void injectSimKey(char c);
|
||||||
|
private:
|
||||||
|
#endif
|
||||||
|
|
||||||
// Optional M5Stack CardKB (I2C keyboard, addr 0x5F). See the CARDKB_I2C
|
// Optional M5Stack CardKB (I2C keyboard, addr 0x5F). See the CARDKB_I2C
|
||||||
// definition near the top of this file for which bus it's on and why.
|
// definition near the top of this file for which bus it's on and why.
|
||||||
#if defined(CARDKB_I2C)
|
#if defined(CARDKB_I2C)
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
#define FILESYSTEM Adafruit_LittleFS
|
#define FILESYSTEM Adafruit_LittleFS
|
||||||
|
|
||||||
using namespace Adafruit_LittleFS_Namespace;
|
using namespace Adafruit_LittleFS_Namespace;
|
||||||
|
#elif defined(SIM_PLATFORM)
|
||||||
|
#include <SimFS.h>
|
||||||
|
#define FILESYSTEM SimFS
|
||||||
|
// SimFS.h already typedefs File itself (matches the ESP32/NRF52 pattern of
|
||||||
|
// a bare, unqualified `File` symbol coming from the platform's own core).
|
||||||
#endif
|
#endif
|
||||||
#include <Identity.h>
|
#include <Identity.h>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <helpers/ui/DisplayDriver.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// DisplayDriver implementation for the native sim build. Shaped like
|
||||||
|
// src/helpers/ui/NullDisplayDriver.h (same pure-virtual overrides -- start
|
||||||
|
// from that file, per the Phase-1 plan) but instead of no-ops, maintains an
|
||||||
|
// in-memory framebuffer and prints it to stdout as ASCII/block-art on
|
||||||
|
// endFrame(), so the real UITask/menu system's actual draw calls are
|
||||||
|
// visible in a terminal.
|
||||||
|
//
|
||||||
|
// Logical canvas is 128x64 (matches NullDisplayDriver / a typical SSD1306
|
||||||
|
// OLED, so layout math in the real screens behaves exactly as on that
|
||||||
|
// hardware). For terminal rendering it's downsampled onto a coarser
|
||||||
|
// CELL_W x CELL_H-pixel grid:
|
||||||
|
// - fillRect()/drawRect()/drawXbm() mark the cells they cover as "filled"
|
||||||
|
// (drawXbm -- icons -- has no real bitmap to rasterize in ASCII, so it's
|
||||||
|
// approximated as a solid block, same as a fillRect over that area).
|
||||||
|
// - print() does NOT rasterize a bitmap font -- it places the real
|
||||||
|
// characters of the real string into the grid at the (approximate)
|
||||||
|
// cursor cell, which is what actually makes the output legible. Real
|
||||||
|
// text always wins over a "filled" block in the same cell.
|
||||||
|
class SimDisplayDriver : public DisplayDriver {
|
||||||
|
static const int CELL_W = 2; // pixels per terminal column
|
||||||
|
static const int CELL_H = 2; // pixels per terminal row
|
||||||
|
static const int COLS = 128 / CELL_W; // 64
|
||||||
|
static const int ROWS = 64 / CELL_H; // 32
|
||||||
|
|
||||||
|
bool _on = false;
|
||||||
|
int _cursor_x = 0, _cursor_y = 0;
|
||||||
|
Color _color = LIGHT;
|
||||||
|
bool _filled[ROWS][COLS];
|
||||||
|
char _text[ROWS][COLS]; // 0 = no character placed
|
||||||
|
bool _dirty = false;
|
||||||
|
int _frame_no = 0;
|
||||||
|
|
||||||
|
void cellOf(int px, int py, int& cx, int& cy) const {
|
||||||
|
cx = px / CELL_W; cy = py / CELL_H;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimDisplayDriver() : DisplayDriver(128, 64) { clearBuffers(); }
|
||||||
|
|
||||||
|
bool begin() { _on = true; return true; }
|
||||||
|
|
||||||
|
void clearBuffers() {
|
||||||
|
memset(_filled, 0, sizeof(_filled));
|
||||||
|
memset(_text, 0, sizeof(_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOn() override { return _on; }
|
||||||
|
void turnOn() override { _on = true; }
|
||||||
|
void turnOff() override { _on = false; }
|
||||||
|
void clear() override { clearBuffers(); }
|
||||||
|
|
||||||
|
void startFrame(Color bkg = DARK) override {
|
||||||
|
clearBuffers();
|
||||||
|
_color = LIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTextSize(int sz) override { /* one fixed size in ASCII output */ }
|
||||||
|
|
||||||
|
void setColor(Color c) override { _color = c; }
|
||||||
|
|
||||||
|
void setCursor(int x, int y) override { _cursor_x = x; _cursor_y = y; }
|
||||||
|
|
||||||
|
void print(const char* str) override {
|
||||||
|
if (!str) return;
|
||||||
|
int cx, cy;
|
||||||
|
cellOf(_cursor_x, _cursor_y, cx, cy);
|
||||||
|
int col = cx;
|
||||||
|
for (const char* p = str; *p; p++) {
|
||||||
|
if (*p == '\n') { cy++; col = cx; continue; }
|
||||||
|
if (col >= 0 && col < COLS && cy >= 0 && cy < ROWS) {
|
||||||
|
_text[cy][col] = (*p >= 32 && *p < 127) ? *p : '?';
|
||||||
|
}
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
// advance cursor horizontally by the printed width, like a real display
|
||||||
|
_cursor_x += getTextWidth(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillRect(int x, int y, int w, int h) override { markRect(x, y, w, h); }
|
||||||
|
void drawRect(int x, int y, int w, int h) override {
|
||||||
|
markRect(x, y, w, 1);
|
||||||
|
markRect(x, y + h - 1, w, 1);
|
||||||
|
markRect(x, y, 1, h);
|
||||||
|
markRect(x + w - 1, y, 1, h);
|
||||||
|
}
|
||||||
|
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override {
|
||||||
|
markRect(x, y, w, h); // icon placeholder: solid block (see class comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getTextWidth(const char* str) override {
|
||||||
|
return str ? (uint16_t)(strlen(str) * getCharWidth()) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void endFrame() {
|
||||||
|
printf("\n===== SimDisplayDriver frame #%d =====\n", _frame_no++);
|
||||||
|
printf("+");
|
||||||
|
for (int c = 0; c < COLS; c++) printf("-");
|
||||||
|
printf("+\n");
|
||||||
|
for (int r = 0; r < ROWS; r++) {
|
||||||
|
printf("|");
|
||||||
|
for (int c = 0; c < COLS; c++) {
|
||||||
|
char ch = _text[r][c];
|
||||||
|
if (ch) putchar(ch);
|
||||||
|
else if (_filled[r][c]) putchar('#');
|
||||||
|
else putchar(' ');
|
||||||
|
}
|
||||||
|
printf("|\n");
|
||||||
|
}
|
||||||
|
printf("+");
|
||||||
|
for (int c = 0; c < COLS; c++) printf("-");
|
||||||
|
printf("+\n");
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void markRect(int x, int y, int w, int h) {
|
||||||
|
bool lit = (_color != DARK);
|
||||||
|
int cx0, cy0, cx1, cy1;
|
||||||
|
cellOf(x, y, cx0, cy0);
|
||||||
|
cellOf(x + w - 1, y + h - 1, cx1, cy1);
|
||||||
|
for (int r = cy0; r <= cy1; r++) {
|
||||||
|
if (r < 0 || r >= ROWS) continue;
|
||||||
|
for (int c = cx0; c <= cx1; c++) {
|
||||||
|
if (c < 0 || c >= COLS) continue;
|
||||||
|
_filled[r][c] = lit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Phase 2 (Emscripten): DisplayDriver backend that draws to a real
|
||||||
|
// HTML5 <canvas> instead of dumping ASCII art to stdout. SimDisplayDriver
|
||||||
|
// above is left completely untouched -- the native build still links that
|
||||||
|
// class (see variants/sim/platformio.ini's DISPLAY_CLASS=SimDisplayDriver);
|
||||||
|
// this class is only selected when DISPLAY_CLASS=SimDisplayDriverCanvas is
|
||||||
|
// set by the Emscripten build (variants/sim/build_wasm.sh).
|
||||||
|
//
|
||||||
|
// Design choice: draw straight through the browser's canvas 2D API on every
|
||||||
|
// draw call (fillRect/strokeRect/fillText), rather than building an offscreen
|
||||||
|
// RGBA framebuffer in linear memory and blitting it with putImageData(). The
|
||||||
|
// canvas 2D approach was simpler and more robust for this app's actual draw
|
||||||
|
// call shape:
|
||||||
|
// - print() needs real text rendering (variable glyphs, marquee/ellipsis
|
||||||
|
// logic in DisplayDriver.h measures via getTextWidth()) -- letting the
|
||||||
|
// browser's own font rasterizer draw it is both less code and crisper
|
||||||
|
// than hand-rolling a bitmap font + blit.
|
||||||
|
// - Every draw happens synchronously within one call to startFrame()..
|
||||||
|
// endFrame() inside a single JS "tick" (called from the Emscripten main
|
||||||
|
// loop -- see sim_main.cpp) -- the browser never paints a partial canvas
|
||||||
|
// mid-tick, so there's no tearing/flicker risk from not double-buffering
|
||||||
|
/// in C++ first.
|
||||||
|
// - putImageData() would still need *something* to rasterize text and
|
||||||
|
// icons into an RGBA buffer first -- it doesn't remove that work, it
|
||||||
|
/// only relocates it into C++ for no real benefit here.
|
||||||
|
//
|
||||||
|
// Each call reaches into the DOM via EM_ASM (synchronous, main-thread JS --
|
||||||
|
// fine since this build has no pthreads/proxying). The canvas is looked up
|
||||||
|
// by id once in begin() and cached on a JS global (window.__simCtx) so every
|
||||||
|
// later call is one property read, not a fresh getElementById().
|
||||||
|
class SimDisplayDriverCanvas : public DisplayDriver {
|
||||||
|
bool _on = false;
|
||||||
|
int _cursor_x = 0, _cursor_y = 0;
|
||||||
|
Color _color = LIGHT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimDisplayDriverCanvas() : DisplayDriver(128, 64) { }
|
||||||
|
|
||||||
|
bool begin() {
|
||||||
|
_on = true;
|
||||||
|
EM_ASM({
|
||||||
|
var c = document.getElementById('sim-canvas');
|
||||||
|
if (!c) { console.error('[sim] #sim-canvas not found in the host page'); return; }
|
||||||
|
window.__simCtx = c.getContext('2d');
|
||||||
|
window.__simCtx.imageSmoothingEnabled = false;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOn() override { return _on; }
|
||||||
|
void turnOn() override { _on = true; }
|
||||||
|
void turnOff() override {
|
||||||
|
_on = false;
|
||||||
|
EM_ASM({
|
||||||
|
if (!window.__simCtx) return;
|
||||||
|
window.__simCtx.fillStyle = '#000';
|
||||||
|
window.__simCtx.fillRect(0, 0, 128, 64);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void clear() override { turnOff(); _on = true; }
|
||||||
|
|
||||||
|
void startFrame(Color bkg = DARK) override {
|
||||||
|
_color = LIGHT;
|
||||||
|
EM_ASM({
|
||||||
|
if (!window.__simCtx) return;
|
||||||
|
window.__simCtx.fillStyle = '#000';
|
||||||
|
window.__simCtx.fillRect(0, 0, 128, 64);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTextSize(int sz) override { /* one fixed size, like the native ASCII backend */ }
|
||||||
|
void setColor(Color c) override { _color = c; }
|
||||||
|
void setCursor(int x, int y) override { _cursor_x = x; _cursor_y = y; }
|
||||||
|
|
||||||
|
// Amber-on-black palette (a common OLED look) for LIGHT/DARK; the other
|
||||||
|
// Color enumerators (RED/GREEN/BLUE/YELLOW/ORANGE) aren't used on the real
|
||||||
|
// monochrome OLED boards this sim mirrors either (DisplayDriver.h's own
|
||||||
|
// comment: "on b/w screen, colors will be !=0 synonym of light").
|
||||||
|
static const char* jsColor(Color c) { return c == DARK ? "#000" : "#ffb000"; }
|
||||||
|
|
||||||
|
void print(const char* str) override {
|
||||||
|
if (!str) return;
|
||||||
|
EM_ASM({
|
||||||
|
if (!window.__simCtx) return;
|
||||||
|
var ctx = window.__simCtx;
|
||||||
|
ctx.fillStyle = UTF8ToString($3) === 'L' ? '#ffb000' : '#000';
|
||||||
|
ctx.font = '8px monospace';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
// Advance width is fixed (getCharWidth()==6, DisplayDriver.h default) --
|
||||||
|
// draw one character per cell so glyph spacing matches the layout math
|
||||||
|
// every screen already does off getTextWidth()'s strlen()*6 estimate,
|
||||||
|
// instead of leaving it to the font's own (proportional) metrics.
|
||||||
|
// NB: EM_ASM's argument-splitting only understands parens, not
|
||||||
|
// braces -- an unparenthesized top-level comma (e.g. a multi-name
|
||||||
|
// `var a, b;`) gets misread as separating this macro's own C++
|
||||||
|
// arguments and breaks the whole block. Every declaration below is
|
||||||
|
// therefore its own separate `var` statement.
|
||||||
|
var s = UTF8ToString($0);
|
||||||
|
var x = $1;
|
||||||
|
var y = $2;
|
||||||
|
for (var i = 0; i < s.length; i++) {
|
||||||
|
ctx.fillText(s[i], x + i * 6, y);
|
||||||
|
}
|
||||||
|
}, str, _cursor_x, _cursor_y, (_color != DARK) ? "L" : "D");
|
||||||
|
_cursor_x += getTextWidth(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillRect(int x, int y, int w, int h) override {
|
||||||
|
EM_ASM({
|
||||||
|
if (!window.__simCtx) return;
|
||||||
|
window.__simCtx.fillStyle = UTF8ToString($4) === 'L' ? '#ffb000' : '#000';
|
||||||
|
window.__simCtx.fillRect($0, $1, $2, $3);
|
||||||
|
}, x, y, w, h, (_color != DARK) ? "L" : "D");
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawRect(int x, int y, int w, int h) override {
|
||||||
|
EM_ASM({
|
||||||
|
if (!window.__simCtx) return;
|
||||||
|
var ctx = window.__simCtx;
|
||||||
|
ctx.strokeStyle = UTF8ToString($4) === 'L' ? '#ffb000' : '#000';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeRect($0 + 0.5, $1 + 0.5, $2 - 1, $3 - 1);
|
||||||
|
}, x, y, w, h, (_color != DARK) ? "L" : "D");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real XBM bit-unpacking (row-major, MSB-first, rows padded to whole
|
||||||
|
// bytes) -- same convention every real DisplayDriver's drawXbm() already
|
||||||
|
// assumes (see e.g. src/helpers/ui/ST7789Display.cpp's own drawXbm(),
|
||||||
|
// `0x80 >> (bx & 7)` against `widthInBytes = (w+7)/8`). A canvas can
|
||||||
|
// afford to rasterize the real icon pixels cheaply, unlike the native
|
||||||
|
// ASCII backend's solid-block placeholder (no ASCII resolution for that).
|
||||||
|
// `bits` is a pointer into wasm linear memory; EM_ASM passes it through as
|
||||||
|
// a plain integer and the JS side indexes HEAPU8 with it directly.
|
||||||
|
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override {
|
||||||
|
EM_ASM({
|
||||||
|
if (!window.__simCtx) return;
|
||||||
|
var ctx = window.__simCtx;
|
||||||
|
var x0 = $0;
|
||||||
|
var y0 = $1;
|
||||||
|
var w = $2;
|
||||||
|
var h = $3;
|
||||||
|
var bits = $4;
|
||||||
|
var lit = UTF8ToString($5) === 'L';
|
||||||
|
var widthInBytes = (w + 7) >> 3;
|
||||||
|
ctx.fillStyle = lit ? '#ffb000' : '#000';
|
||||||
|
for (var ry = 0; ry < h; ry++) {
|
||||||
|
for (var rx = 0; rx < w; rx++) {
|
||||||
|
var byteOff = bits + ry * widthInBytes + (rx >> 3);
|
||||||
|
var mask = 0x80 >> (rx & 7);
|
||||||
|
if (HEAPU8[byteOff] & mask) ctx.fillRect(x0 + rx, y0 + ry, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, x, y, w, h, bits, (_color != DARK) ? "L" : "D");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getTextWidth(const char* str) override {
|
||||||
|
return str ? (uint16_t)(strlen(str) * getCharWidth()) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every draw call above already lands directly on the visible canvas
|
||||||
|
// (see the class comment) -- nothing left to flush.
|
||||||
|
void endFrame() override { }
|
||||||
|
};
|
||||||
|
#endif // __EMSCRIPTEN__
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Native-disk-backed FILESYSTEM/File shim for the sim build's DataStore
|
||||||
|
// persistence (examples/companion_radio/DataStore.cpp / .h,
|
||||||
|
// src/helpers/IdentityStore.h/.cpp). Modelled on the ESP32 fs::FS/fs::File
|
||||||
|
// shape (DataStore.cpp's own "#else" fallback branch already calls
|
||||||
|
// fs->open(path, "r"/"w", create_bool) -- matching that signature here
|
||||||
|
// means SIM_PLATFORM falls straight into those existing branches with no
|
||||||
|
// DataStore.cpp/IdentityStore.cpp edits needed beyond the FILESYSTEM/File
|
||||||
|
// typedefs in IdentityStore.h), but backed by plain fopen/fread/fwrite
|
||||||
|
// under a local directory (./sim_data/ by default) so NodePrefs/contacts/
|
||||||
|
// identity genuinely round-trip across process restarts.
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
class SimFile : public Stream {
|
||||||
|
friend class SimFS;
|
||||||
|
FILE* _fp = nullptr;
|
||||||
|
DIR* _dir = nullptr;
|
||||||
|
bool _is_dir = false;
|
||||||
|
std::string _dirpath; // full on-disk path, directory mode only
|
||||||
|
std::string _name; // basename
|
||||||
|
size_t _size = 0;
|
||||||
|
|
||||||
|
SimFile(FILE* fp, const std::string& name, size_t size) : _fp(fp), _name(name), _size(size) { }
|
||||||
|
SimFile(DIR* dir, const std::string& dirpath, const std::string& name)
|
||||||
|
: _dir(dir), _is_dir(true), _dirpath(dirpath), _name(name) { }
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimFile() { }
|
||||||
|
|
||||||
|
operator bool() const { return _fp != nullptr || _dir != nullptr; }
|
||||||
|
|
||||||
|
bool isDirectory() { return _is_dir; }
|
||||||
|
const char* name() { return _name.c_str(); }
|
||||||
|
size_t size() { return _size; }
|
||||||
|
|
||||||
|
// directory iteration (MyMesh.cpp's CLI-rescue "ls" debug command)
|
||||||
|
SimFile openNextFile() {
|
||||||
|
if (!_dir) return SimFile();
|
||||||
|
struct dirent* ent;
|
||||||
|
while ((ent = readdir(_dir)) != nullptr) {
|
||||||
|
std::string nm = ent->d_name;
|
||||||
|
if (nm == "." || nm == "..") continue;
|
||||||
|
std::string full = _dirpath + "/" + nm;
|
||||||
|
struct stat st;
|
||||||
|
if (stat(full.c_str(), &st) != 0) continue;
|
||||||
|
SimFile f;
|
||||||
|
f._name = nm;
|
||||||
|
f._is_dir = S_ISDIR(st.st_mode);
|
||||||
|
f._size = (size_t)st.st_size;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
return SimFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read(uint8_t* buf, size_t len) {
|
||||||
|
if (!_fp) return 0;
|
||||||
|
return fread(buf, 1, len, _fp);
|
||||||
|
}
|
||||||
|
int read() override {
|
||||||
|
if (!_fp) return -1;
|
||||||
|
int c = fgetc(_fp);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
int peek() override {
|
||||||
|
if (!_fp) return -1;
|
||||||
|
int c = fgetc(_fp);
|
||||||
|
if (c != EOF) ungetc(c, _fp);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
int available() override {
|
||||||
|
if (!_fp) return 0;
|
||||||
|
long cur = ftell(_fp);
|
||||||
|
if (cur < 0) return 0;
|
||||||
|
return (int)((long)_size - cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(uint8_t b) override { return _fp ? fwrite(&b, 1, 1, _fp) : 0; }
|
||||||
|
size_t write(const uint8_t* buf, size_t len) override {
|
||||||
|
return _fp ? fwrite(buf, 1, len, _fp) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool seek(uint32_t pos) {
|
||||||
|
if (!_fp) return false;
|
||||||
|
return fseek(_fp, (long)pos, SEEK_SET) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
if (_fp) { fclose(_fp); _fp = nullptr; }
|
||||||
|
if (_dir) { closedir(_dir); _dir = nullptr; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef SimFile File;
|
||||||
|
|
||||||
|
class SimFS {
|
||||||
|
std::string _root;
|
||||||
|
|
||||||
|
// Creates every path component of `path`, whether it's absolute ("/a/b")
|
||||||
|
// or -- as SimFS's own root ("./sim_data") and every path built from it
|
||||||
|
// always are -- relative ("./a/b", "a/b"). A relative path must accumulate
|
||||||
|
// starting from "." (not ""), otherwise the first component gets
|
||||||
|
// mkdir()'d as if it were rooted at the real filesystem's "/" (a real bug
|
||||||
|
// this had: "./sim_data" was being split into a leading "." component and
|
||||||
|
// then prefixed with "/", trying -- and silently failing, permission
|
||||||
|
// denied -- to mkdir "/./sim_data" instead of "./sim_data").
|
||||||
|
static void mkdirsRecursive(const std::string& path) {
|
||||||
|
if (path.empty() || path == "/" || path == ".") return;
|
||||||
|
bool absolute = (path[0] == '/');
|
||||||
|
std::string cur = absolute ? "" : ".";
|
||||||
|
size_t pos = absolute ? 1 : 0;
|
||||||
|
while (pos <= path.size()) {
|
||||||
|
size_t slash = path.find('/', pos);
|
||||||
|
std::string part = (slash == std::string::npos) ? path.substr(pos) : path.substr(pos, slash - pos);
|
||||||
|
if (!part.empty() && part != ".") {
|
||||||
|
cur += "/" + part;
|
||||||
|
::mkdir(cur.c_str(), 0755);
|
||||||
|
}
|
||||||
|
if (slash == std::string::npos) break;
|
||||||
|
pos = slash + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fullPath(const char* path) const {
|
||||||
|
if (path && path[0] == '/') return _root + path;
|
||||||
|
return _root + "/" + (path ? path : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string basenameOf(const char* path) {
|
||||||
|
if (!path) return "";
|
||||||
|
const char* slash = strrchr(path, '/');
|
||||||
|
return slash ? std::string(slash + 1) : std::string(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensureParentDir(const std::string& fullFilePath) const {
|
||||||
|
size_t slash = fullFilePath.find_last_of('/');
|
||||||
|
if (slash == std::string::npos) return;
|
||||||
|
std::string dir = fullFilePath.substr(0, slash);
|
||||||
|
mkdirsRecursive(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool rmrf(const std::string& path) {
|
||||||
|
DIR* d = opendir(path.c_str());
|
||||||
|
if (!d) { return ::remove(path.c_str()) == 0; }
|
||||||
|
struct dirent* ent;
|
||||||
|
bool ok = true;
|
||||||
|
while ((ent = readdir(d)) != nullptr) {
|
||||||
|
std::string nm = ent->d_name;
|
||||||
|
if (nm == "." || nm == "..") continue;
|
||||||
|
ok = rmrf(path + "/" + nm) && ok;
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
ok = (::rmdir(path.c_str()) == 0) && ok;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SimFS(const std::string& root) : _root(root) { mkdirsRecursive(root); }
|
||||||
|
|
||||||
|
bool mkdir(const char* path) {
|
||||||
|
mkdirsRecursive(fullPath(path));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exists(const char* path) {
|
||||||
|
struct stat st;
|
||||||
|
return stat(fullPath(path).c_str(), &st) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remove(const char* path) {
|
||||||
|
return ::remove(fullPath(path).c_str()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rename(const char* oldPath, const char* newPath) {
|
||||||
|
return ::rename(fullPath(oldPath).c_str(), fullPath(newPath).c_str()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool format() {
|
||||||
|
// Wipe the sim device's whole data directory and recreate it empty --
|
||||||
|
// mirrors a real LittleFS/SPIFFS format().
|
||||||
|
rmrf(_root);
|
||||||
|
mkdirsRecursive(_root);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches the ESP32 fs::FS::open(path, mode, create) shape that
|
||||||
|
// DataStore.cpp/IdentityStore.cpp's platform-agnostic "#else" fallback
|
||||||
|
// branches already call.
|
||||||
|
SimFile open(const char* path, const char* mode = "r", bool create = false) {
|
||||||
|
std::string full = fullPath(path);
|
||||||
|
struct stat st;
|
||||||
|
bool file_exists = (stat(full.c_str(), &st) == 0);
|
||||||
|
|
||||||
|
if (file_exists && S_ISDIR(st.st_mode)) {
|
||||||
|
DIR* d = opendir(full.c_str());
|
||||||
|
if (!d) return SimFile();
|
||||||
|
return SimFile(d, full, basenameOf(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool want_write = (mode && mode[0] == 'w');
|
||||||
|
if (want_write) {
|
||||||
|
ensureParentDir(full);
|
||||||
|
FILE* fp = fopen(full.c_str(), "wb");
|
||||||
|
if (!fp) return SimFile();
|
||||||
|
return SimFile(fp, basenameOf(path), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists) return SimFile();
|
||||||
|
FILE* fp = fopen(full.c_str(), "rb");
|
||||||
|
if (!fp) return SimFile();
|
||||||
|
return SimFile(fp, basenameOf(path), (size_t)st.st_size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
|
||||||
|
// Phase 2 (Emscripten) IDBFS backend.
|
||||||
|
//
|
||||||
|
// Important finding from actually reading SimFile/SimFS above before writing
|
||||||
|
// this: NEITHER class needs a single line changed for Emscripten. Every op
|
||||||
|
// here (fopen/fread/fwrite/fclose, mkdir, opendir/readdir, stat, rename,
|
||||||
|
// remove) is a plain libc call, and Emscripten's C runtime already
|
||||||
|
// implements all of those against its own virtual filesystem (MEMFS by
|
||||||
|
// default) -- that's the whole point of Emscripten's libc port, and it's
|
||||||
|
// why this shim was written directly against fopen()-shaped calls in Phase
|
||||||
|
// 1 rather than some native-only API. So SimFS's constructor (mkdirsRecursive
|
||||||
|
// on "./sim_data") and every DataStore/IdentityStore read/write already work
|
||||||
|
// unmodified under Emscripten, exactly as they do natively -- just against
|
||||||
|
// MEMFS (in-memory, gone on refresh) instead of the real disk.
|
||||||
|
//
|
||||||
|
// The only thing actually missing for the browser is durability: MEMFS
|
||||||
|
// alone doesn't survive a page reload. IDBFS is Emscripten's IndexedDB-
|
||||||
|
// backed filesystem type that mirrors a MEMFS directory to/from IndexedDB.
|
||||||
|
// Mounting it is the one piece of real Emscripten-specific code needed, so
|
||||||
|
// it lives here (colocated with the FS shim it backs) rather than in
|
||||||
|
// sim_main.cpp:
|
||||||
|
//
|
||||||
|
// 1. FS.mount(IDBFS, {autoPersist: true}, root) -- autoPersist is a
|
||||||
|
// built-in IDBFS option (see this SDK's upstream/emscripten/src/lib/
|
||||||
|
// libidbfs.js) that hooks every file close() following a write and
|
||||||
|
// queues a debounced FS.syncfs(false, cb) push to IndexedDB
|
||||||
|
// automatically (batched to one push per JS event-loop tick, so a
|
||||||
|
// DataStore save that opens/writes/closes several files in a row still
|
||||||
|
// costs one IndexedDB commit, not several). This was chosen over a
|
||||||
|
// hand-rolled periodic timer in the main loop: it can't drift out of
|
||||||
|
// sync with what was actually written (a timer firing between saves
|
||||||
|
// could persist a half-written state; a close()-triggered persist
|
||||||
|
// never can), and it does nothing at all when nothing changed instead
|
||||||
|
// of a timer's fixed idle-polling cost.
|
||||||
|
// 2. FS.syncfs(true, cb) -- the reverse direction, a one-time pull *from*
|
||||||
|
// IndexedDB into the MEMFS mirror. This must complete, and its callback
|
||||||
|
// must fire, before the app ever reads a file -- i.e. before setup()
|
||||||
|
// runs -- since IndexedDB has no synchronous API. sim_main.cpp's
|
||||||
|
/// Emscripten main() calls this and only calls setup() from the
|
||||||
|
// callback (sim_idbfs_ready(), EMSCRIPTEN_KEEPALIVE'd below so the
|
||||||
|
// generated JS glue can call it back by name).
|
||||||
|
//
|
||||||
|
// root must be an absolute path ("/sim_data") that already exists as a
|
||||||
|
// plain MEMFS directory by the time this runs -- true here because SimFS's
|
||||||
|
// own constructor (a file-scope global in examples/companion_radio/main.cpp,
|
||||||
|
// so it runs during static init, before this function's caller in
|
||||||
|
// sim_main.cpp's main()) already mkdir'd "./sim_data", which resolves to the
|
||||||
|
// same node as "/sim_data" since Emscripten's cwd defaults to "/". Mounting
|
||||||
|
// IDBFS onto an existing *empty* directory is the exact pattern Emscripten's
|
||||||
|
// own test suite uses (test/fs/test_idbfs_sync.c: `FS.mkdir(...);
|
||||||
|
// FS.mount(IDBFS, ..., ...)`) -- BUT unlike the real ::mkdir() syscall
|
||||||
|
// SimFS's own C++ constructor already called (which returns EEXIST quietly
|
||||||
|
// and is ignored, POSIX mkdir -p style), JS's own `FS.mkdir()` throws a hard
|
||||||
|
// exception if the directory is already there, which it always will be by
|
||||||
|
// this point. Confirmed by actually running this in a real browser before
|
||||||
|
// trusting the comment that used to be here: first load produced an
|
||||||
|
// uncaught `ErrnoError: File exists` right here, which silently aborted
|
||||||
|
// main() before sim_idbfs_ready() ever ran -- so this needs its own
|
||||||
|
// try/catch, unlike Emscripten's own test (which never pre-creates the dir
|
||||||
|
// through a second path first).
|
||||||
|
inline void sim_fs_mount_idbfs(const char* root) {
|
||||||
|
EM_ASM({
|
||||||
|
var root = UTF8ToString($0);
|
||||||
|
try { FS.mkdir(root); } catch (e) { /* already exists -- see comment above */ }
|
||||||
|
FS.mount(IDBFS, { autoPersist: true }, root);
|
||||||
|
FS.syncfs(true, function(err) {
|
||||||
|
if (err) console.error('[sim] IDBFS initial syncfs(true) failed:', err);
|
||||||
|
else console.log('[sim] IDBFS pull from IndexedDB complete, booting app...');
|
||||||
|
_sim_idbfs_ready();
|
||||||
|
});
|
||||||
|
}, root);
|
||||||
|
}
|
||||||
|
#endif // __EMSCRIPTEN__
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <helpers/sensors/LocationProvider.h>
|
||||||
|
|
||||||
|
// LocationProvider stub for the native sim build: no real GPS, always
|
||||||
|
// reports "no fix". A settable lat/lon (JS-driven) is a Phase-2/3 concern.
|
||||||
|
class SimLocationProvider : public LocationProvider {
|
||||||
|
public:
|
||||||
|
long getLatitude() override { return 0; }
|
||||||
|
long getLongitude() override { return 0; }
|
||||||
|
long getAltitude() override { return 0; }
|
||||||
|
long satellitesCount() override { return 0; }
|
||||||
|
bool isValid() override { return false; }
|
||||||
|
long getTimestamp() override { return 0; }
|
||||||
|
void reset() override { }
|
||||||
|
void begin() override { }
|
||||||
|
void stop() override { }
|
||||||
|
void loop() override { }
|
||||||
|
bool isEnabled() override { return false; }
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// mesh::MainBoard implementation for the native sim build -- no real
|
||||||
|
// hardware, so battery/manufacturer/reboot are all just plausible fakes.
|
||||||
|
class SimMainBoard : public mesh::MainBoard {
|
||||||
|
public:
|
||||||
|
void begin() { }
|
||||||
|
|
||||||
|
uint16_t getBattMilliVolts() override { return 4000; } // pretend full battery
|
||||||
|
const char* getManufacturerName() const override { return "MeshCore Sim (native)"; }
|
||||||
|
|
||||||
|
void reboot() override {
|
||||||
|
printf("\n[sim] reboot() requested -- exiting process (rerun the binary to simulate a reboot)\n");
|
||||||
|
fflush(stdout);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getStartupReason() const override { return BD_STARTUP_NORMAL; }
|
||||||
|
|
||||||
|
void onBootComplete() override { }
|
||||||
|
void sleep(uint32_t secs) override { } // no-op: native process never actually sleeps the CPU
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Utils.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
// mesh::RNG implementation for the native sim build. Not cryptographically
|
||||||
|
// strong (rand() under the hood) -- fine for a terminal demo; a real
|
||||||
|
// two-device-messaging phase (Phase 3 of the sim plan) may want to swap
|
||||||
|
// this for something seeded from the OS CSPRNG.
|
||||||
|
class SimRNG : public mesh::RNG {
|
||||||
|
public:
|
||||||
|
SimRNG() { }
|
||||||
|
void begin() {
|
||||||
|
unsigned seed = (unsigned)time(NULL) ^ (unsigned)(uintptr_t)this;
|
||||||
|
srand(seed);
|
||||||
|
}
|
||||||
|
void random(uint8_t* dest, size_t sz) override {
|
||||||
|
for (size_t i = 0; i < sz; i++) dest[i] = (uint8_t)rand();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
// mesh::RTCClock backed by the real host wall clock (time(nullptr)), with
|
||||||
|
// setCurrentTime() applying an offset so DataStore::restoreRTCTime()
|
||||||
|
// (persisted last-known time) and the CLI's `time` command still work.
|
||||||
|
class SimRTCClock : public mesh::RTCClock {
|
||||||
|
long _offset = 0; // added to real wall-clock time()
|
||||||
|
public:
|
||||||
|
uint32_t getCurrentTime() override {
|
||||||
|
return (uint32_t)((long)time(NULL) + _offset);
|
||||||
|
}
|
||||||
|
void setCurrentTime(uint32_t t) override {
|
||||||
|
_offset = (long)t - (long)time(NULL);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Dispatcher.h>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// mesh::Radio implementation for the native sim build (Phase 1). Mirrors
|
||||||
|
// the FakeRadio in test/test_kiss_modem/test_tx_backpressure.cpp in spirit
|
||||||
|
// (always-succeed send, no real RF) but is written directly against the
|
||||||
|
// REAL mesh::Radio interface in src/Dispatcher.h -- that test mock is for a
|
||||||
|
// different, out-of-date mocked Mesh.h (see the Phase-1 plan) and must not
|
||||||
|
// be copied.
|
||||||
|
//
|
||||||
|
// Phase 1 has exactly one logical device, so there is nothing to actually
|
||||||
|
// exchange packets with: recvRaw() always reports "nothing received",
|
||||||
|
// startSendRaw()/isSendComplete() always report success instantly. Phase 3
|
||||||
|
// of the sim plan (two simulated devices + a repeater) is where this class
|
||||||
|
// grows a real in-memory "ether" so two instances can actually talk.
|
||||||
|
class SimRadio : public mesh::Radio {
|
||||||
|
uint32_t n_recv = 0, n_sent = 0, n_recv_errors = 0;
|
||||||
|
bool _power_save = false;
|
||||||
|
bool _rx_boosted_gain = false;
|
||||||
|
int8_t _tx_dbm = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void begin() override { }
|
||||||
|
|
||||||
|
int recvRaw(uint8_t* bytes, int sz) override {
|
||||||
|
return 0; // never any incoming data yet (Phase 3: real ether)
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getEstAirtimeFor(int len_bytes) override {
|
||||||
|
// Rough LoRa-ish estimate so anything that logs/uses airtime for
|
||||||
|
// scheduling doesn't see nonsense; not calibrated to any real profile.
|
||||||
|
return (uint32_t)(len_bytes * 3 + 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
float packetScore(float snr, int packet_len) override {
|
||||||
|
return 100.0f; // pretend every packet we'd send is a clean, high-quality one
|
||||||
|
}
|
||||||
|
|
||||||
|
bool startSendRaw(const uint8_t* bytes, int len) override {
|
||||||
|
n_sent++;
|
||||||
|
return true; // instantly "succeeds" -- nothing is actually transmitted yet
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSendComplete() override { return true; }
|
||||||
|
void onSendFinished() override { }
|
||||||
|
|
||||||
|
bool isInRecvMode() const override { return true; }
|
||||||
|
|
||||||
|
// --- Extra methods below (not part of mesh::Radio) -------------------
|
||||||
|
// MyMesh.cpp/DataStore.cpp/the Settings/Diagnostics UI screens call these
|
||||||
|
// directly on the concrete radio_driver object on every real board, the
|
||||||
|
// same way they'd call them on a RadioLibWrapper subclass (see
|
||||||
|
// src/helpers/radiolib/RadioLibWrappers.h, which every one of these
|
||||||
|
// mirrors). No real chip underneath, so these just report plausible
|
||||||
|
// static/no-op values.
|
||||||
|
|
||||||
|
uint32_t getRngSeed() {
|
||||||
|
return (uint32_t)time(NULL) ^ (uint32_t)(uintptr_t)this ^ (uint32_t)rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
void getFreqBounds(float& min_mhz, float& max_mhz) const {
|
||||||
|
min_mhz = 150.0f;
|
||||||
|
max_mhz = 2500.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) { }
|
||||||
|
void powerOff() { }
|
||||||
|
|
||||||
|
void setPowerSaving(bool en) { _power_save = en; }
|
||||||
|
bool getPowerSaving() const { return _power_save; }
|
||||||
|
|
||||||
|
void setTxPower(int8_t dbm) { _tx_dbm = dbm; }
|
||||||
|
int8_t getTxPower() const { return _tx_dbm; }
|
||||||
|
|
||||||
|
bool setRxBoostedGainMode(bool en) { _rx_boosted_gain = en; return true; }
|
||||||
|
bool getRxBoostedGainMode() const { return _rx_boosted_gain; }
|
||||||
|
|
||||||
|
uint32_t getPacketsRecv() const { return n_recv; }
|
||||||
|
uint32_t getPacketsRecvErrors() const { return n_recv_errors; }
|
||||||
|
uint32_t getPacketsSent() const { return n_sent; }
|
||||||
|
uint32_t getRxPsWatchdogSoftCount() const { return 0; }
|
||||||
|
uint32_t getRxPsWatchdogHardCount() const { return 0; }
|
||||||
|
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
|
||||||
|
|
||||||
|
static float snrFloorForSF(uint8_t sf) {
|
||||||
|
if (sf < 7) sf = 7; else if (sf > 12) sf = 12;
|
||||||
|
return -7.5f - 2.5f * (float)(sf - 7);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <helpers/BaseSerialInterface.h>
|
||||||
|
|
||||||
|
// BaseSerialInterface stub for the native sim build: no companion app can
|
||||||
|
// connect over BLE/USB in Phase 1 (there's no real transport), so this
|
||||||
|
// always reports "not connected, nothing to do". Enough to let MyMesh's
|
||||||
|
// startInterface()/loop() run unmodified.
|
||||||
|
class SimSerialInterface : public BaseSerialInterface {
|
||||||
|
public:
|
||||||
|
void begin() { }
|
||||||
|
|
||||||
|
void enable() override { }
|
||||||
|
void disable() override { }
|
||||||
|
bool isEnabled() const override { return false; }
|
||||||
|
bool isConnected() const override { return false; }
|
||||||
|
bool isWriteBusy() const override { return false; }
|
||||||
|
size_t writeFrame(const uint8_t src[], size_t len) override { return 0; }
|
||||||
|
size_t checkRecvFrame(uint8_t dest[]) override { return 0; }
|
||||||
|
};
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Minimal native Arduino-core shim for the SIM_PLATFORM (variants/sim)
|
||||||
|
// native build. Scoped empirically (see the Phase-1 status report) by
|
||||||
|
// grepping examples/companion_radio + ui-new + the src/helpers files the
|
||||||
|
// sim build actually compiles for real Arduino API usage -- the app logic
|
||||||
|
// turned out to use almost none of it: no Arduino String anywhere, no
|
||||||
|
// PROGMEM/pgm_read, no digitalWrite/pinMode/analogRead/Wire/SPI reachable
|
||||||
|
// (all gated behind board-specific PIN_* defines this build never sets),
|
||||||
|
// and Serial. only in main.cpp's Serial.begin() plus MyMesh.cpp's
|
||||||
|
// CLI-rescue debug command handler.
|
||||||
|
//
|
||||||
|
// Deliberately NOT defining the ARDUINO preprocessor macro: a couple of
|
||||||
|
// vendored third-party libs (CayenneLPP.cpp, ArduinoJson) branch on
|
||||||
|
// `#ifdef ARDUINO` to choose between Arduino String/Stream and plain
|
||||||
|
// std::string/std::ostream -- leaving ARDUINO undefined routes them onto
|
||||||
|
// their portable std:: path, which is exactly what we want and needs no
|
||||||
|
// Arduino String implementation at all.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cmath>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "Stream.h"
|
||||||
|
|
||||||
|
using std::isnan;
|
||||||
|
using std::isinf;
|
||||||
|
|
||||||
|
// --- timing -----------------------------------------------------------
|
||||||
|
|
||||||
|
inline uint32_t _sim_millis_start_epoch_ms() {
|
||||||
|
static const uint32_t t0 = (uint32_t)std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
|
return t0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned long millis() {
|
||||||
|
uint32_t now = (uint32_t)std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
|
return (unsigned long)(now - _sim_millis_start_epoch_ms());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned long micros() {
|
||||||
|
uint64_t now = (uint64_t)std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
|
return (unsigned long)now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real delay(); MyMesh.cpp:2848 and UITask.cpp:2619's one-shot pre-reboot/
|
||||||
|
// pre-shutdown pauses get their own SIM_PLATFORM branch that skips calling
|
||||||
|
// this entirely (see the Phase-1 status report), so an actual sleep here
|
||||||
|
// never blocks the sim's stdin-reader thread for long.
|
||||||
|
inline void delay(unsigned long ms) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- randomSeed()/random() (Arduino's global RNG, used by StdRNG in
|
||||||
|
// src/helpers/ArduinoHelpers.h -- NOT the same thing as mesh::RNG) ----
|
||||||
|
inline void randomSeed(unsigned long seed) { if (seed != 0) ::srand((unsigned)seed); }
|
||||||
|
inline long random(long howbig) {
|
||||||
|
if (howbig <= 0) return 0;
|
||||||
|
return (long)(::rand() % howbig);
|
||||||
|
}
|
||||||
|
inline long random(long howsmall, long howbig) {
|
||||||
|
if (howsmall >= howbig) return howsmall;
|
||||||
|
return howsmall + (long)(::rand() % (howbig - howsmall));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- misc Arduino macros/helpers ---------------------------------------
|
||||||
|
|
||||||
|
#ifndef PROGMEM
|
||||||
|
#define PROGMEM
|
||||||
|
#endif
|
||||||
|
#ifndef F
|
||||||
|
#define F(x) (x)
|
||||||
|
#endif
|
||||||
|
#ifndef PSTR
|
||||||
|
#define PSTR(x) (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Real Arduino cores define min()/max()/constrain() as untyped macros, which
|
||||||
|
// works but is notorious for silently corrupting any <algorithm>/<vector>/etc
|
||||||
|
// internals that use those same names if such a header is ever included
|
||||||
|
// afterwards (this codebase's CayenneLPPPolyline.h uses std::vector/std::map,
|
||||||
|
// and SimFS.h/etc use std::string) -- so these are real (mixed-argument-type)
|
||||||
|
// templates instead, deducing a common value type via std::common_type
|
||||||
|
// rather than decltype(a<b?a:b) (which can deduce a *reference* type when
|
||||||
|
// both arguments are same-typed lvalues, and then fail to bind that
|
||||||
|
// reference to the temporary a cast/mixed-type branch produces).
|
||||||
|
#ifndef min
|
||||||
|
template <typename T, typename U>
|
||||||
|
typename std::common_type<T, U>::type min(T a, U b) { return a < b ? a : b; }
|
||||||
|
#endif
|
||||||
|
#ifndef max
|
||||||
|
template <typename T, typename U>
|
||||||
|
typename std::common_type<T, U>::type max(T a, U b) { return a > b ? a : b; }
|
||||||
|
#endif
|
||||||
|
#ifndef constrain
|
||||||
|
template <typename T, typename U, typename V>
|
||||||
|
typename std::common_type<T, U, V>::type constrain(T amt, U lo, V hi) {
|
||||||
|
return amt < lo ? lo : (amt > hi ? hi : amt);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifndef abs
|
||||||
|
#define abs(x) ((x) > 0 ? (x) : -(x))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Arduino global itoa/ltoa/utoa/ultoa (AVR-libc-style, not in standard C++);
|
||||||
|
// src/helpers/TxtDataHelpers.cpp's float-formatting code calls ltoa()
|
||||||
|
// directly. glibc/libc++ don't provide these, so implement via snprintf's
|
||||||
|
// base-10/16/8/2 support for 10, else a manual digit loop for other bases.
|
||||||
|
inline char* ltoa(long value, char* result, int base) {
|
||||||
|
if (base == 10) { snprintf(result, 32, "%ld", value); return result; }
|
||||||
|
if (value == 0) { result[0] = '0'; result[1] = 0; return result; }
|
||||||
|
bool neg = value < 0;
|
||||||
|
unsigned long v = neg ? (unsigned long)(-value) : (unsigned long)value;
|
||||||
|
char tmp[34]; int i = 0;
|
||||||
|
while (v > 0) { int d = v % base; tmp[i++] = d < 10 ? ('0' + d) : ('a' + d - 10); v /= base; }
|
||||||
|
int j = 0;
|
||||||
|
if (neg) result[j++] = '-';
|
||||||
|
while (i > 0) result[j++] = tmp[--i];
|
||||||
|
result[j] = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
inline char* itoa(int value, char* result, int base) { return ltoa((long)value, result, base); }
|
||||||
|
|
||||||
|
// Cooperative-multitasking yield point (ESP32/RP2040 cores feed this to their
|
||||||
|
// scheduler/watchdog between blocking waits; a plain native process has none
|
||||||
|
// of that, so it's a no-op). src/helpers/StreamUtils.h calls it directly.
|
||||||
|
inline void yield() { }
|
||||||
|
|
||||||
|
// Opaque incomplete type used only for PROGMEM-string pointer typing on real
|
||||||
|
// Arduino cores (F("...") normally returns a `const __FlashStringHelper*`).
|
||||||
|
// Since F(x) is defined as a plain passthrough above (no real PROGMEM on a
|
||||||
|
// native build), nothing ever actually constructs one of these -- it only
|
||||||
|
// needs to exist as a type so a `print(const __FlashStringHelper*)` overload
|
||||||
|
// (e.g. examples/companion_radio/ui-new/TrailScreen.h's BoundedSerialPrint)
|
||||||
|
// declares cleanly, exactly like on a real board.
|
||||||
|
class __FlashStringHelper;
|
||||||
|
|
||||||
|
// --- Serial -------------------------------------------------------------
|
||||||
|
// Only ever used for Serial.begin() (main.cpp, ignored) and MyMesh.cpp's
|
||||||
|
// CLI-rescue debug command handler (Serial.print/println/printf as output,
|
||||||
|
// Serial.available()/read() as input). Output goes to real stdout; input
|
||||||
|
// always reports "nothing available" -- CLI-rescue isn't reachable through
|
||||||
|
// the sim's stdin-driven UI input path (see variants/sim/README) and isn't
|
||||||
|
// one of the Phase-1 exit criteria, but every call still needs to compile.
|
||||||
|
class SimSerialClass : public Stream {
|
||||||
|
public:
|
||||||
|
void begin(unsigned long baud) { }
|
||||||
|
// Real HardwareSerial's operator bool() reports "is a USB/BLE host
|
||||||
|
// actually connected" (some real boards genuinely gate on this, e.g.
|
||||||
|
// TrailScreen.h's GPX-export-over-serial feature). stdout is always
|
||||||
|
// "there" for a native process, so just always report ready.
|
||||||
|
explicit operator bool() const { return true; }
|
||||||
|
int available() override { return 0; }
|
||||||
|
int read() override { return -1; }
|
||||||
|
int peek() override { return -1; }
|
||||||
|
size_t write(uint8_t b) override { putchar(b); return 1; }
|
||||||
|
size_t write(const uint8_t *buffer, size_t size) override {
|
||||||
|
fwrite(buffer, 1, size, stdout);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
void flush() override { fflush(stdout); }
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SimSerialClass Serial;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Minimal native stand-in for Arduino's Print class -- just enough of the
|
||||||
|
// real API surface for MeshCore's app logic (which never uses Arduino
|
||||||
|
// String) to link: byte/buffer write(), the numeric print() overloads, and
|
||||||
|
// printf() (a genuine Arduino Print extension on ESP32/etc, used sparingly
|
||||||
|
// by MyMesh.cpp's CLI-rescue debug path).
|
||||||
|
//
|
||||||
|
// Modelled on test/mocks/Stream.h's Print (same shape, same DEC/HEX/OCT/BIN
|
||||||
|
// constants) but adds printf() since the real app needs it.
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#define DEC 10
|
||||||
|
#define HEX 16
|
||||||
|
#define OCT 8
|
||||||
|
#define BIN 2
|
||||||
|
|
||||||
|
class Print {
|
||||||
|
public:
|
||||||
|
virtual ~Print() = default;
|
||||||
|
|
||||||
|
virtual size_t write(uint8_t b) { return 1; }
|
||||||
|
|
||||||
|
size_t write(const char *str) {
|
||||||
|
if (str == NULL) return 0;
|
||||||
|
return write((const uint8_t *)str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t write(const uint8_t *buffer, size_t size) {
|
||||||
|
size_t t = 0;
|
||||||
|
for (size_t i = 0; i < size; i++) t += write(buffer[i]);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(const char *buffer, size_t size) {
|
||||||
|
return write((const uint8_t *)buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t print(unsigned char b, int base = DEC) { return printInt((unsigned long)b, base); }
|
||||||
|
virtual size_t print(int v, int base = DEC) { return base == DEC ? printSigned((long)v) : printInt((unsigned long)v, base); }
|
||||||
|
virtual size_t print(unsigned int v, int base = DEC) { return printInt((unsigned long)v, base); }
|
||||||
|
virtual size_t print(long v, int base = DEC) { return base == DEC ? printSigned(v) : printInt((unsigned long)v, base); }
|
||||||
|
virtual size_t print(unsigned long v, int base = DEC) { return printInt(v, base); }
|
||||||
|
virtual size_t print(long long v, int base = DEC) { char buf[32]; snprintf(buf, sizeof(buf), "%lld", v); return write(buf); }
|
||||||
|
virtual size_t print(unsigned long long v, int base = DEC) { char buf[32]; snprintf(buf, sizeof(buf), "%llu", v); return write(buf); }
|
||||||
|
virtual size_t print(double v, int digits = 2) { char buf[64]; snprintf(buf, sizeof(buf), "%.*f", digits, v); return write(buf); }
|
||||||
|
|
||||||
|
size_t print(char c) { return write((uint8_t)c); }
|
||||||
|
size_t print(const char* s) { return write(s); }
|
||||||
|
|
||||||
|
size_t println() { return write("\r\n"); }
|
||||||
|
size_t println(const char* s) { size_t n = print(s); n += println(); return n; }
|
||||||
|
size_t println(char c) { size_t n = print(c); n += println(); return n; }
|
||||||
|
size_t println(int v, int base = DEC) { size_t n = print(v, base); n += println(); return n; }
|
||||||
|
size_t println(unsigned int v, int base = DEC) { size_t n = print(v, base); n += println(); return n; }
|
||||||
|
size_t println(long v, int base = DEC) { size_t n = print(v, base); n += println(); return n; }
|
||||||
|
size_t println(unsigned long v, int base = DEC){ size_t n = print(v, base); n += println(); return n; }
|
||||||
|
size_t println(double v, int digits = 2) { size_t n = print(v, digits); n += println(); return n; }
|
||||||
|
|
||||||
|
// Real Arduino cores (ESP32/NRF52) expose Print::printf(); used by
|
||||||
|
// MyMesh.cpp's CLI-rescue debug command handler.
|
||||||
|
size_t printf(const char* fmt, ...) {
|
||||||
|
char buf[256];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
int n = vsnprintf(buf, sizeof(buf), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
if (n < 0) return 0;
|
||||||
|
if ((size_t)n < sizeof(buf)) return write(buf);
|
||||||
|
// truncated -- still write what fit
|
||||||
|
return write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void flush() { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t printInt(unsigned long v, int base) {
|
||||||
|
char buf[34];
|
||||||
|
if (base == DEC) { snprintf(buf, sizeof(buf), "%lu", v); }
|
||||||
|
else if (base == HEX) { snprintf(buf, sizeof(buf), "%lx", v); }
|
||||||
|
else if (base == OCT) { snprintf(buf, sizeof(buf), "%lo", v); }
|
||||||
|
else {
|
||||||
|
// generic base conversion (BIN etc.)
|
||||||
|
char tmp[34]; int i = 0;
|
||||||
|
unsigned long n = v;
|
||||||
|
if (n == 0) tmp[i++] = '0';
|
||||||
|
while (n > 0) { tmp[i++] = "0123456789abcdefghijklmnopqrstuvwxyz"[n % base]; n /= base; }
|
||||||
|
int j = 0;
|
||||||
|
while (i > 0) buf[j++] = tmp[--i];
|
||||||
|
buf[j] = 0;
|
||||||
|
}
|
||||||
|
return write(buf);
|
||||||
|
}
|
||||||
|
size_t printSigned(long v) {
|
||||||
|
char buf[34];
|
||||||
|
snprintf(buf, sizeof(buf), "%ld", v);
|
||||||
|
return write(buf);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
// Minimal native stand-in for Adafruit's RTClib.h. Real hardware boards pull
|
||||||
|
// this in transitively for helpers/AutoDiscoverRTCClock.h (not used by the
|
||||||
|
// sim build -- SimRTCClock.h reads the host wall clock directly) and for
|
||||||
|
// DateTime, which src/helpers/CommonCLI.cpp's "clock"/"clock sync"/"time"
|
||||||
|
// CLI commands genuinely construct and call hour()/minute()/day()/month()/
|
||||||
|
// year() on. Nothing in companion_radio (root files or ui-new/) uses
|
||||||
|
// DateTime, but CommonCLI.cpp (built for every board, including the sim) does.
|
||||||
|
//
|
||||||
|
// Implemented via the real C library's gmtime_r() (UTC calendar breakdown)
|
||||||
|
// rather than hand-rolling RTClib's own unixtime<->y/m/d/h/m/s conversion --
|
||||||
|
// same result, zero risk of a transcription bug in that math.
|
||||||
|
#include <ctime>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class DateTime {
|
||||||
|
uint32_t _unixtime;
|
||||||
|
public:
|
||||||
|
DateTime(uint32_t t = 0) : _unixtime(t) { }
|
||||||
|
|
||||||
|
uint16_t year() const { return 1900 + tmFields().tm_year; }
|
||||||
|
uint8_t month() const { return (uint8_t)(tmFields().tm_mon + 1); }
|
||||||
|
uint8_t day() const { return (uint8_t)tmFields().tm_mday; }
|
||||||
|
uint8_t hour() const { return (uint8_t)tmFields().tm_hour; }
|
||||||
|
uint8_t minute() const { return (uint8_t)tmFields().tm_min; }
|
||||||
|
uint8_t second() const { return (uint8_t)tmFields().tm_sec; }
|
||||||
|
uint32_t unixtime() const { return _unixtime; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct tm tmFields() const {
|
||||||
|
time_t t = (time_t)_unixtime;
|
||||||
|
struct tm out;
|
||||||
|
gmtime_r(&t, &out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Minimal native stand-in for Arduino's Stream class. src/Utils.h includes
|
||||||
|
// <Stream.h> directly (mesh::Utils::printHex(Stream&, ...)), and
|
||||||
|
// src/Identity.cpp's readFrom/writeTo/printTo take a Stream& -- both real
|
||||||
|
// hardware File objects (fs::File, Adafruit_LittleFS's File) and our own
|
||||||
|
// SimFile (variants/sim/SimFS.h) derive from this, exactly like on real
|
||||||
|
// boards.
|
||||||
|
|
||||||
|
#include "Print.h"
|
||||||
|
|
||||||
|
class Stream : public Print {
|
||||||
|
public:
|
||||||
|
virtual ~Stream() = default;
|
||||||
|
|
||||||
|
virtual int available() { return 0; }
|
||||||
|
virtual int availableForWrite() { return 0; }
|
||||||
|
virtual int read() { return -1; }
|
||||||
|
virtual int peek() { return -1; }
|
||||||
|
|
||||||
|
virtual size_t readBytes(char *buffer, size_t length) {
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < length) {
|
||||||
|
int c = read();
|
||||||
|
if (c < 0) break;
|
||||||
|
buffer[i++] = (char)c;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
size_t readBytes(uint8_t *buffer, size_t length) {
|
||||||
|
return readBytes((char *)buffer, length);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
// Minimal native stand-in for densaugeo/base64's base64.hpp. Real board envs
|
||||||
|
// pull in the whole library via lib_deps; src/helpers/BaseChatMesh.cpp
|
||||||
|
// (built for every board, including the sim, whenever MAX_GROUP_CHANNELS is
|
||||||
|
// defined) only ever calls decode_base64() -- to decode a channel's
|
||||||
|
// user-supplied PSK -- so that's the only function reproduced here, as a
|
||||||
|
// plain standard base64 decoder (skips '=' padding/whitespace/invalid chars
|
||||||
|
// rather than erroring, same permissive behaviour the real library has).
|
||||||
|
|
||||||
|
static inline int _sim_b64_val(unsigned char c) {
|
||||||
|
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||||
|
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||||
|
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||||
|
if (c == '+') return 62;
|
||||||
|
if (c == '/') return 63;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int decode_base64(const unsigned char input[], unsigned int inputLength, unsigned char output[]) {
|
||||||
|
unsigned int out_len = 0;
|
||||||
|
int val = 0, bits = -8;
|
||||||
|
for (unsigned int i = 0; i < inputLength; i++) {
|
||||||
|
unsigned char c = input[i];
|
||||||
|
if (c == '=') break;
|
||||||
|
int v = _sim_b64_val(c);
|
||||||
|
if (v < 0) continue; // skip whitespace/invalid characters
|
||||||
|
val = (val << 6) + v;
|
||||||
|
bits += 6;
|
||||||
|
if (bits >= 0) {
|
||||||
|
output[out_len++] = (unsigned char)((val >> bits) & 0xFF);
|
||||||
|
bits -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out_len;
|
||||||
|
}
|
||||||
Executable
+185
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Phase 2 (Emscripten) build script for the companion_radio sim.
|
||||||
|
#
|
||||||
|
# Why a shell script instead of a PlatformIO env: PlatformIO's `platform =
|
||||||
|
# native` env (see variants/sim/platformio.ini's [env:sim_companion_radio])
|
||||||
|
# was tried first, by pointing a `pre:` extra_script at this same source
|
||||||
|
# list and overriding env['CC']/env['CXX'] to em++/emcc via env.Replace().
|
||||||
|
# That override *did* take effect (confirmed: the extra_script's own print()
|
||||||
|
# showed the correct em++ path) but was silently discarded before any file
|
||||||
|
# was actually compiled -- PlatformIO's native platform package
|
||||||
|
# (~/.platformio/platforms/native/builder/main.py) unconditionally calls
|
||||||
|
# env.Tool("gcc") / env.Tool("g++") to (re-)detect the toolchain, and that
|
||||||
|
# happens to run *after* extra_scripts regardless of pre:/post: ordering,
|
||||||
|
# re-overwriting CC/CXX back to the real system clang++ every time. Every
|
||||||
|
# object file in that experiment was still compiled by Xcode's clang++, not
|
||||||
|
# em++ -- a genuine, reproducible wall (not a config typo), and PlatformIO's
|
||||||
|
# native platform has no supported hook to stop it from re-detecting the
|
||||||
|
# toolchain like that. Rather than fight PlatformIO's SCons integration
|
||||||
|
# further, this script just invokes em++ directly -- it mirrors
|
||||||
|
# platformio.ini's build_flags/build_src_filter/-I list by hand (see the
|
||||||
|
# SRCS/INCLUDES/DEFINES arrays below), so if that .ini file's source list
|
||||||
|
# ever changes, this script's arrays need the same edit alongside it.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# variants/sim/build_wasm.sh # release-ish build (-O2)
|
||||||
|
# variants/sim/build_wasm.sh debug # -O0 -g, easier to debug in devtools
|
||||||
|
#
|
||||||
|
# Requires emsdk 6.0.9 (pinned; see variants/sim/tools/emsdk/ -- installed by
|
||||||
|
# this same task, see the Phase 2 report for the exact activation command).
|
||||||
|
# This script finds em++ itself via a fixed relative path, so `source
|
||||||
|
# emsdk_env.sh` first is NOT required to run it (but IS required for
|
||||||
|
# interactive use of emcc/em++/emrun directly on the command line).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
EMSDK_DIR="$SCRIPT_DIR/tools/emsdk"
|
||||||
|
EMXX="$EMSDK_DIR/upstream/emscripten/em++"
|
||||||
|
OUT_DIR="$SCRIPT_DIR/web/build"
|
||||||
|
|
||||||
|
if [ ! -x "$EMXX" ]; then
|
||||||
|
echo "error: em++ not found at $EMXX" >&2
|
||||||
|
echo "Install it first:" >&2
|
||||||
|
echo " cd $EMSDK_DIR && python3 ./emsdk.py install 6.0.9 && python3 ./emsdk.py activate 6.0.9" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILD_MODE="${1:-release}"
|
||||||
|
if [ "$BUILD_MODE" = "debug" ]; then
|
||||||
|
OPT_FLAGS=(-O0 -g)
|
||||||
|
else
|
||||||
|
OPT_FLAGS=(-O2)
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
# Same list as variants/sim/platformio.ini's build_src_filter, just spelled
|
||||||
|
# as real paths from the repo root instead of PlatformIO's src-relative
|
||||||
|
# "+<../x>" syntax. Keep in sync with that file by hand.
|
||||||
|
SRCS=(
|
||||||
|
src/Dispatcher.cpp
|
||||||
|
src/Identity.cpp
|
||||||
|
src/Mesh.cpp
|
||||||
|
src/Packet.cpp
|
||||||
|
src/Utils.cpp
|
||||||
|
src/helpers/AdvertDataHelpers.cpp
|
||||||
|
src/helpers/BaseChatMesh.cpp
|
||||||
|
src/helpers/ClientACL.cpp
|
||||||
|
src/helpers/CommonCLI.cpp
|
||||||
|
src/helpers/ConfigSerializer.cpp
|
||||||
|
src/helpers/DeviceDiag.cpp
|
||||||
|
src/helpers/IdentityStore.cpp
|
||||||
|
src/helpers/RegionMap.cpp
|
||||||
|
src/helpers/StaticPoolPacketManager.cpp
|
||||||
|
src/helpers/TransportKeyStore.cpp
|
||||||
|
src/helpers/TxtDataHelpers.cpp
|
||||||
|
lib/ed25519/add_scalar.c
|
||||||
|
lib/ed25519/fe.c
|
||||||
|
lib/ed25519/ge.c
|
||||||
|
lib/ed25519/key_exchange.c
|
||||||
|
lib/ed25519/keypair.c
|
||||||
|
lib/ed25519/sc.c
|
||||||
|
lib/ed25519/seed.c
|
||||||
|
lib/ed25519/sha512.c
|
||||||
|
lib/ed25519/sign.c
|
||||||
|
lib/ed25519/verify.c
|
||||||
|
variants/sim/sim_main.cpp
|
||||||
|
variants/sim/target.cpp
|
||||||
|
variants/sim/thirdparty/crypto/AES128.cpp
|
||||||
|
variants/sim/thirdparty/crypto/AESCommon.cpp
|
||||||
|
variants/sim/thirdparty/crypto/BigNumberUtil.cpp
|
||||||
|
variants/sim/thirdparty/crypto/BlockCipher.cpp
|
||||||
|
variants/sim/thirdparty/crypto/Crypto.cpp
|
||||||
|
variants/sim/thirdparty/crypto/Curve25519.cpp
|
||||||
|
variants/sim/thirdparty/crypto/Ed25519.cpp
|
||||||
|
variants/sim/thirdparty/crypto/Hash.cpp
|
||||||
|
variants/sim/thirdparty/crypto/rng_stub.cpp
|
||||||
|
variants/sim/thirdparty/crypto/SHA256.cpp
|
||||||
|
variants/sim/thirdparty/crypto/SHA512.cpp
|
||||||
|
variants/sim/thirdparty/cayennelpp/CayenneLPP.cpp
|
||||||
|
variants/sim/thirdparty/cayennelpp/CayenneLPPPolyline.cpp
|
||||||
|
examples/companion_radio/main.cpp
|
||||||
|
examples/companion_radio/MyMesh.cpp
|
||||||
|
examples/companion_radio/DataStore.cpp
|
||||||
|
examples/companion_radio/ui-new/UITask.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
INCLUDES=(
|
||||||
|
-Ivariants/sim/arduino
|
||||||
|
-Ivariants/sim
|
||||||
|
-Ivariants/sim/thirdparty/crypto
|
||||||
|
-Ivariants/sim/thirdparty/cayennelpp
|
||||||
|
-Ivariants/sim/thirdparty/arduinojson
|
||||||
|
-Ilib/ed25519
|
||||||
|
-Isrc
|
||||||
|
-Iexamples/companion_radio
|
||||||
|
-Iexamples/companion_radio/ui-new
|
||||||
|
)
|
||||||
|
|
||||||
|
DEFINES=(
|
||||||
|
-DSIM_PLATFORM
|
||||||
|
-DMESH_DEBUG=0
|
||||||
|
# The one difference from platformio.ini's native env: the canvas-backed
|
||||||
|
# DisplayDriver (variants/sim/SimDisplayDriver.h's __EMSCRIPTEN__-guarded
|
||||||
|
# SimDisplayDriverCanvas class) instead of the ASCII/stdout one.
|
||||||
|
-DDISPLAY_CLASS=SimDisplayDriverCanvas
|
||||||
|
-DMAX_CONTACTS=100
|
||||||
|
-DMAX_GROUP_CHANNELS=8
|
||||||
|
)
|
||||||
|
|
||||||
|
# -funsigned-char: carried over from Phase 1 verbatim -- real ARM cores
|
||||||
|
# default `char` to unsigned; em++'s target (wasm32) defaults it to signed,
|
||||||
|
# same mismatch Phase 1 hit on a native x86/ARM64 host, for the same reason
|
||||||
|
# (KEY_* codes up to 0xF3 compared as plain `char` throughout UIScreen.h/
|
||||||
|
# KeyboardWidget.h/PopupMenu.h) -- without it keyboard input compiles but
|
||||||
|
# silently never matches.
|
||||||
|
COMMON_FLAGS=(-std=c++17 -funsigned-char "${OPT_FLAGS[@]}" "${DEFINES[@]}" "${INCLUDES[@]}")
|
||||||
|
|
||||||
|
# Compile each source to its own object file, one em++ invocation per file,
|
||||||
|
# with the object path mirroring the source's own directory (obj/<same
|
||||||
|
# relative path>.o) rather than every object landing in one flat directory.
|
||||||
|
# This isn't just tidiness: a first attempt passed every source straight to
|
||||||
|
# a single em++ invocation and let it manage its own (flat) temp object
|
||||||
|
# directory internally, which broke on this specific source tree --
|
||||||
|
# lib/ed25519/sha512.c (ed25519's own plain-C SHA512, unrelated to the
|
||||||
|
# rweather/Crypto library) and variants/sim/thirdparty/crypto/SHA512.cpp
|
||||||
|
# (rweather's C++ SHA512 class) both produce a "sha512.o"/"SHA512.o" object,
|
||||||
|
# which collided as the SAME file on macOS's case-insensitive-by-default
|
||||||
|
# APFS -- wasm-ld then reported duplicate symbols for the *second* file's
|
||||||
|
# whole contents, because it was quite literally linking the first file's
|
||||||
|
# object twice under two different names. PlatformIO's native build never
|
||||||
|
# hits this because SCons mirrors each source's own directory under
|
||||||
|
# .pio/build/<env>/ -- that's exactly what this does too, by hand.
|
||||||
|
OBJ_DIR="$OUT_DIR/obj"
|
||||||
|
rm -rf "$OBJ_DIR"
|
||||||
|
OBJS=()
|
||||||
|
for src in "${SRCS[@]}"; do
|
||||||
|
obj="$OBJ_DIR/${src%.*}.o"
|
||||||
|
mkdir -p "$(dirname "$obj")"
|
||||||
|
"$EMXX" -c "${COMMON_FLAGS[@]}" "$src" -o "$obj"
|
||||||
|
OBJS+=("$obj")
|
||||||
|
done
|
||||||
|
|
||||||
|
# FS is exported so a host page (or a manual verification script) can
|
||||||
|
# directly inspect what DataStore/IdentityStore actually wrote -- e.g.
|
||||||
|
# Module.FS.readFile('/sim_data/identity/_main.id') -- to prove IDBFS
|
||||||
|
# persistence with a real file-content comparison across a reload, not just
|
||||||
|
# "the app didn't crash". Not required for the app itself.
|
||||||
|
"$EMXX" \
|
||||||
|
"${OBJS[@]}" \
|
||||||
|
-lidbfs.js \
|
||||||
|
-sALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-sFORCE_FILESYSTEM=1 \
|
||||||
|
-sMODULARIZE=1 \
|
||||||
|
-sEXPORT_NAME=MeshCoreSim \
|
||||||
|
-sENVIRONMENT=web \
|
||||||
|
-sEXIT_RUNTIME=0 \
|
||||||
|
-sEXPORTED_RUNTIME_METHODS=FS,ccall,cwrap \
|
||||||
|
-o "$OUT_DIR/meshcore_sim.js"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Built: $OUT_DIR/meshcore_sim.js (+ .wasm alongside it)"
|
||||||
|
echo "Serve variants/sim/web/ locally and open index.html, e.g.:"
|
||||||
|
echo " cd $SCRIPT_DIR/web && python3 -m http.server 8080"
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
; variants/sim -- native (host) build of the real companion_radio app logic
|
||||||
|
; (MyMesh/UITask/DataStore), for Phase 1 of the in-house browser-sim project
|
||||||
|
; (see the plan doc / project memory for the full Emscripten roadmap this is
|
||||||
|
; step one of). No Emscripten here yet -- plain g++, a terminal ASCII-art
|
||||||
|
; "display", stdin for input. Deliberately the easiest possible target so the
|
||||||
|
; from-scratch port can be debugged without WASM complexity on top.
|
||||||
|
;
|
||||||
|
; Not `extends`-ing arduino_base/esp32_base etc: those pull in RadioLib, Wire,
|
||||||
|
; SPI and a real Arduino framework, none of which this build wants -- it's
|
||||||
|
; written against platform=native from scratch, closer in spirit to
|
||||||
|
; [env:native]/[env:native_kiss_modem] below than to a real board env.
|
||||||
|
[env:sim_companion_radio]
|
||||||
|
platform = native
|
||||||
|
build_type = debug
|
||||||
|
build_flags =
|
||||||
|
-std=c++17
|
||||||
|
-g
|
||||||
|
; ARM toolchains (every real board target) default `char` to UNSIGNED;
|
||||||
|
; x86/ARM64 host compilers default it to signed. The UI's KEY_* codes
|
||||||
|
; (src/helpers/ui/UIScreen.h) go up to 0xF3 and get compared/stored as
|
||||||
|
; plain `char` throughout (KeyboardWidget.h, PopupMenu.h, UITask.cpp) --
|
||||||
|
; without this flag those comparisons are silently always-false on a
|
||||||
|
; signed-char host (a negative char never equals a positive KEY_* int
|
||||||
|
; literal), which would make keyboard input compile cleanly but never
|
||||||
|
; actually navigate anything.
|
||||||
|
-funsigned-char
|
||||||
|
-D SIM_PLATFORM
|
||||||
|
-D MESH_DEBUG=0
|
||||||
|
-D DISPLAY_CLASS=SimDisplayDriver
|
||||||
|
-D MAX_CONTACTS=100
|
||||||
|
-D MAX_GROUP_CHANNELS=8
|
||||||
|
-I variants/sim/arduino
|
||||||
|
-I variants/sim
|
||||||
|
-I variants/sim/thirdparty/crypto
|
||||||
|
-I variants/sim/thirdparty/cayennelpp
|
||||||
|
-I variants/sim/thirdparty/arduinojson
|
||||||
|
-I lib/ed25519
|
||||||
|
-I src
|
||||||
|
-I examples/companion_radio
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
build_src_filter =
|
||||||
|
-<*>
|
||||||
|
+<../src/Dispatcher.cpp>
|
||||||
|
+<../src/Identity.cpp>
|
||||||
|
+<../src/Mesh.cpp>
|
||||||
|
+<../src/Packet.cpp>
|
||||||
|
+<../src/Utils.cpp>
|
||||||
|
+<../src/helpers/AdvertDataHelpers.cpp>
|
||||||
|
+<../src/helpers/BaseChatMesh.cpp>
|
||||||
|
+<../src/helpers/ClientACL.cpp>
|
||||||
|
+<../src/helpers/CommonCLI.cpp>
|
||||||
|
+<../src/helpers/ConfigSerializer.cpp>
|
||||||
|
+<../src/helpers/DeviceDiag.cpp>
|
||||||
|
+<../src/helpers/IdentityStore.cpp>
|
||||||
|
+<../src/helpers/RegionMap.cpp>
|
||||||
|
+<../src/helpers/StaticPoolPacketManager.cpp>
|
||||||
|
+<../src/helpers/TransportKeyStore.cpp>
|
||||||
|
+<../src/helpers/TxtDataHelpers.cpp>
|
||||||
|
+<../lib/ed25519/*.c>
|
||||||
|
+<../variants/sim/*.cpp>
|
||||||
|
+<../variants/sim/thirdparty/crypto/*.cpp>
|
||||||
|
+<../variants/sim/thirdparty/cayennelpp/*.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// Process/runtime entry point for the sim build. Real Arduino cores provide
|
||||||
|
// their own main() (call setup() once, then loop() forever); platform=native
|
||||||
|
// has no such core, so this is that main() for both Phase 1's native target
|
||||||
|
// and Phase 2's Emscripten target -- the two are different enough (a native
|
||||||
|
// process owns its own loop and a real stdin tty; a wasm module in a browser
|
||||||
|
// tab must hand control back to the browser's event loop between ticks, and
|
||||||
|
// has no stdin at all) that they get fully separate #ifdef __EMSCRIPTEN__
|
||||||
|
// branches below rather than one branch trying to cover both.
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// Defined by examples/companion_radio/main.cpp (Arduino sketch convention:
|
||||||
|
// no header declares these, every board's own entry point just forward-
|
||||||
|
// declares and calls them).
|
||||||
|
void setup();
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Phase 2: Emscripten/browser entry point.
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include "SimFS.h" // sim_fs_mount_idbfs()
|
||||||
|
|
||||||
|
// One tick of the real app's cooperative loop -- identical body to
|
||||||
|
// examples/companion_radio/main.cpp's own loop() (the_mesh.loop();
|
||||||
|
// sensors.loop(); ui_task.loop(); rtc_clock.tick(); ...), so this wrapper
|
||||||
|
// adds nothing of its own; it exists only because emscripten_set_main_loop
|
||||||
|
// wants a void(void) function pointer, and passing `loop` directly would
|
||||||
|
// also work here, but naming it makes the call below self-documenting.
|
||||||
|
static void sim_main_loop_tick() {
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called back from JS (see SimFS.h's sim_fs_mount_idbfs()) once the async
|
||||||
|
// IndexedDB -> MEMFS pull finishes. This is where the app's boot sequence
|
||||||
|
// actually starts -- deliberately NOT in main() itself, since main() must
|
||||||
|
// return (or hand off via emscripten_exit_with_live_runtime()) long before
|
||||||
|
// this callback ever fires.
|
||||||
|
extern "C" EMSCRIPTEN_KEEPALIVE void sim_idbfs_ready() {
|
||||||
|
setup();
|
||||||
|
// fps=0, simulate_infinite_loop=1: let the browser's own
|
||||||
|
// requestAnimationFrame cadence drive ticks (Emscripten's documented
|
||||||
|
// recommendation for anything drawing to a <canvas>) rather than a fixed
|
||||||
|
// interval -- ties the sim's tick rate to the actual display refresh, and
|
||||||
|
// it's throttled/paused for free by the browser when the tab is hidden or
|
||||||
|
// backgrounded, which a manual setInterval(..., fixed_ms) wouldn't get.
|
||||||
|
emscripten_set_main_loop(sim_main_loop_tick, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("MeshCore sim (wasm) starting -- mounting IDBFS at /sim_data...\n");
|
||||||
|
// "/sim_data" must match the relative "./sim_data" DataStore store(sim_fs,
|
||||||
|
// ...) in examples/companion_radio/main.cpp resolves to -- see the long
|
||||||
|
// comment on sim_fs_mount_idbfs() in SimFS.h for why those are the same
|
||||||
|
// filesystem node under Emscripten's default cwd ("/").
|
||||||
|
sim_fs_mount_idbfs("/sim_data");
|
||||||
|
// Keep the runtime alive after main() returns instead of tearing it down
|
||||||
|
// (the default for a `main()` that returns under Emscripten) -- the real
|
||||||
|
// boot sequence hasn't happened yet, it's waiting on sim_idbfs_ready()
|
||||||
|
// above, an async JS callback that fires well after main() itself is done.
|
||||||
|
emscripten_exit_with_live_runtime();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Phase 1: native (host process) entry point -- unchanged from the
|
||||||
|
// original Phase 1 implementation. Puts the terminal into raw/
|
||||||
|
// non-canonical mode so UITask.cpp's SIM_PLATFORM stdin-poll branch (see
|
||||||
|
// UITask::loop()) receives individual keystrokes immediately instead of
|
||||||
|
// once per Enter-terminated line.
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
static struct termios g_orig_termios;
|
||||||
|
static bool g_termios_saved = false;
|
||||||
|
|
||||||
|
static void restoreTerminal() {
|
||||||
|
if (g_termios_saved) tcsetattr(STDIN_FILENO, TCSANOW, &g_orig_termios);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setupTerminal() {
|
||||||
|
if (!isatty(STDIN_FILENO)) return; // piped/redirected stdin -- leave alone
|
||||||
|
if (tcgetattr(STDIN_FILENO, &g_orig_termios) != 0) return;
|
||||||
|
g_termios_saved = true;
|
||||||
|
atexit(restoreTerminal);
|
||||||
|
|
||||||
|
struct termios raw = g_orig_termios;
|
||||||
|
raw.c_lflag &= ~(ICANON | ECHO); // no line buffering, no local echo
|
||||||
|
raw.c_cc[VMIN] = 0;
|
||||||
|
raw.c_cc[VTIME] = 0;
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
setupTerminal();
|
||||||
|
printf("MeshCore sim (native) -- Ctrl-C to quit.\n");
|
||||||
|
printf("Keys: arrows/WASD move, Enter/Space select, Esc/Backspace cancel, n/p next/prev.\n\n");
|
||||||
|
|
||||||
|
setup();
|
||||||
|
for (;;) {
|
||||||
|
loop();
|
||||||
|
// Real boards spin their loop() as fast as the hardware allows too;
|
||||||
|
// this just keeps a native process from pegging a CPU core at 100%
|
||||||
|
// for no benefit -- 1ms is well under any UI timing this app cares
|
||||||
|
// about (refresh/animation intervals are tens-to-hundreds of ms).
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif // __EMSCRIPTEN__
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "target.h"
|
||||||
|
#include "SimRNG.h"
|
||||||
|
|
||||||
|
// Global stdout-backed Serial object, declared extern in Arduino.h.
|
||||||
|
SimSerialClass Serial;
|
||||||
|
|
||||||
|
SimMainBoard board;
|
||||||
|
SimRadio radio_driver;
|
||||||
|
SimRTCClock rtc_clock;
|
||||||
|
SensorManager sensors; // base class: no real sensors in Phase 1
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
DISPLAY_CLASS display;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init() {
|
||||||
|
// No real radio hardware to initialise -- always succeeds (see
|
||||||
|
// variants/sim/SimRadio.h; Phase 3 of the sim plan is where two SimRadio
|
||||||
|
// instances actually exchange bytes through a shared in-memory "ether").
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::LocalIdentity radio_new_identity() {
|
||||||
|
static SimRNG rng;
|
||||||
|
rng.begin();
|
||||||
|
return mesh::LocalIdentity(&rng);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// variants/sim -- native (host) "board" for running the real companion_radio
|
||||||
|
// app logic (MyMesh/UITask/DataStore) as a plain terminal program, with no
|
||||||
|
// real radio/display/BLE hardware. Follows the same target.h/target.cpp/
|
||||||
|
// platformio.ini triplet convention as every other board variant (see
|
||||||
|
// variants/generic-e22/ for the template this was modelled on).
|
||||||
|
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include "SimRadio.h"
|
||||||
|
#include "SimMainBoard.h"
|
||||||
|
#include "SimRTCClock.h"
|
||||||
|
#include <helpers/SensorManager.h>
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
#include "SimDisplayDriver.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern SimMainBoard board;
|
||||||
|
extern SimRadio radio_driver;
|
||||||
|
extern SimRTCClock rtc_clock;
|
||||||
|
extern SensorManager sensors;
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
extern DISPLAY_CLASS display;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init();
|
||||||
|
mesh::LocalIdentity radio_new_identity();
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
# include "ArduinoJson.hpp"
|
||||||
|
|
||||||
|
using namespace ArduinoJson;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#error ArduinoJson requires a C++ compiler, please change file extension to .cc or .cpp
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if __cplusplus < 201103L && (!defined(_MSC_VER) || _MSC_VER < 1910)
|
||||||
|
# error ArduinoJson requires C++11 or newer. Configure your compiler for C++11 or downgrade ArduinoJson to 6.20.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ArduinoJson/Configuration.hpp"
|
||||||
|
|
||||||
|
// Include Arduino.h before stdlib.h to avoid conflict with atexit()
|
||||||
|
// https://github.com/bblanchon/ArduinoJson/pull/1693#issuecomment-1001060240
|
||||||
|
#if ARDUINOJSON_ENABLE_ARDUINO_STRING || ARDUINOJSON_ENABLE_ARDUINO_STREAM || \
|
||||||
|
ARDUINOJSON_ENABLE_ARDUINO_PRINT || \
|
||||||
|
(ARDUINOJSON_ENABLE_PROGMEM && defined(ARDUINO))
|
||||||
|
# include <Arduino.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !ARDUINOJSON_DEBUG
|
||||||
|
# ifdef __clang__
|
||||||
|
# pragma clang system_header
|
||||||
|
# elif defined __GNUC__
|
||||||
|
# pragma GCC system_header
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Remove true and false macros defined by some cores, such as Arduino Due's
|
||||||
|
// See issues #2181 and arduino/ArduinoCore-sam#50
|
||||||
|
#ifdef true
|
||||||
|
# undef true
|
||||||
|
#endif
|
||||||
|
#ifdef false
|
||||||
|
# undef false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ArduinoJson/Array/JsonArray.hpp"
|
||||||
|
#include "ArduinoJson/Object/JsonObject.hpp"
|
||||||
|
#include "ArduinoJson/Variant/JsonVariantConst.hpp"
|
||||||
|
|
||||||
|
#include "ArduinoJson/Document/JsonDocument.hpp"
|
||||||
|
|
||||||
|
#include "ArduinoJson/Array/ArrayImpl.hpp"
|
||||||
|
#include "ArduinoJson/Array/ElementProxy.hpp"
|
||||||
|
#include "ArduinoJson/Array/Utilities.hpp"
|
||||||
|
#include "ArduinoJson/Collection/CollectionImpl.hpp"
|
||||||
|
#include "ArduinoJson/Memory/ResourceManagerImpl.hpp"
|
||||||
|
#include "ArduinoJson/Object/MemberProxy.hpp"
|
||||||
|
#include "ArduinoJson/Object/ObjectImpl.hpp"
|
||||||
|
#include "ArduinoJson/Variant/ConverterImpl.hpp"
|
||||||
|
#include "ArduinoJson/Variant/JsonVariantCopier.hpp"
|
||||||
|
#include "ArduinoJson/Variant/VariantCompare.hpp"
|
||||||
|
#include "ArduinoJson/Variant/VariantImpl.hpp"
|
||||||
|
#include "ArduinoJson/Variant/VariantRefBaseImpl.hpp"
|
||||||
|
|
||||||
|
#include "ArduinoJson/Json/JsonDeserializer.hpp"
|
||||||
|
#include "ArduinoJson/Json/JsonSerializer.hpp"
|
||||||
|
#include "ArduinoJson/Json/PrettyJsonSerializer.hpp"
|
||||||
|
#include "ArduinoJson/MsgPack/MsgPackBinary.hpp"
|
||||||
|
#include "ArduinoJson/MsgPack/MsgPackDeserializer.hpp"
|
||||||
|
#include "ArduinoJson/MsgPack/MsgPackExtension.hpp"
|
||||||
|
#include "ArduinoJson/MsgPack/MsgPackSerializer.hpp"
|
||||||
|
|
||||||
|
#include "ArduinoJson/compatibility.hpp"
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Collection/CollectionData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class ArrayData : public CollectionData {
|
||||||
|
public:
|
||||||
|
VariantData* addElement(ResourceManager* resources);
|
||||||
|
|
||||||
|
static VariantData* addElement(ArrayData* array, ResourceManager* resources) {
|
||||||
|
if (!array)
|
||||||
|
return nullptr;
|
||||||
|
return array->addElement(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool addValue(const T& value, ResourceManager* resources);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static bool addValue(ArrayData* array, const T& value,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
if (!array)
|
||||||
|
return false;
|
||||||
|
return array->addValue(value, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantData* getOrAddElement(size_t index, ResourceManager* resources);
|
||||||
|
|
||||||
|
VariantData* getElement(size_t index, const ResourceManager* resources) const;
|
||||||
|
|
||||||
|
static VariantData* getElement(const ArrayData* array, size_t index,
|
||||||
|
const ResourceManager* resources) {
|
||||||
|
if (!array)
|
||||||
|
return nullptr;
|
||||||
|
return array->getElement(index, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeElement(size_t index, ResourceManager* resources);
|
||||||
|
|
||||||
|
static void removeElement(ArrayData* array, size_t index,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
if (!array)
|
||||||
|
return;
|
||||||
|
array->removeElement(index, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(iterator it, ResourceManager* resources) {
|
||||||
|
CollectionData::removeOne(it, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove(ArrayData* array, iterator it,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
if (array)
|
||||||
|
return array->remove(it, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
iterator at(size_t index, const ResourceManager* resources) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Array/ArrayData.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantCompare.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
inline ArrayData::iterator ArrayData::at(
|
||||||
|
size_t index, const ResourceManager* resources) const {
|
||||||
|
auto it = createIterator(resources);
|
||||||
|
while (!it.done() && index) {
|
||||||
|
it.next(resources);
|
||||||
|
--index;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VariantData* ArrayData::addElement(ResourceManager* resources) {
|
||||||
|
auto slot = resources->allocVariant();
|
||||||
|
if (!slot)
|
||||||
|
return nullptr;
|
||||||
|
CollectionData::appendOne(slot, resources);
|
||||||
|
return slot.ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VariantData* ArrayData::getOrAddElement(size_t index,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
auto it = createIterator(resources);
|
||||||
|
while (!it.done() && index > 0) {
|
||||||
|
it.next(resources);
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
if (it.done())
|
||||||
|
index++;
|
||||||
|
VariantData* element = it.data();
|
||||||
|
while (index > 0) {
|
||||||
|
element = addElement(resources);
|
||||||
|
if (!element)
|
||||||
|
return nullptr;
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VariantData* ArrayData::getElement(
|
||||||
|
size_t index, const ResourceManager* resources) const {
|
||||||
|
return at(index, resources).data();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ArrayData::removeElement(size_t index, ResourceManager* resources) {
|
||||||
|
remove(at(index, resources), resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline bool ArrayData::addValue(const T& value, ResourceManager* resources) {
|
||||||
|
ARDUINOJSON_ASSERT(resources != nullptr);
|
||||||
|
auto slot = resources->allocVariant();
|
||||||
|
if (!slot)
|
||||||
|
return false;
|
||||||
|
JsonVariant variant(slot.ptr(), resources);
|
||||||
|
if (!variant.set(value)) {
|
||||||
|
resources->freeVariant(slot);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CollectionData::appendOne(slot, resources);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the size (in bytes) of an array with n elements.
|
||||||
|
constexpr size_t sizeofArray(size_t n) {
|
||||||
|
return n * ResourceManager::slotSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Variant/VariantRefBase.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// A proxy class to get or set an element of an array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/subscript/
|
||||||
|
template <typename TUpstream>
|
||||||
|
class ElementProxy : public VariantRefBase<ElementProxy<TUpstream>>,
|
||||||
|
public VariantOperators<ElementProxy<TUpstream>> {
|
||||||
|
friend class VariantAttorney;
|
||||||
|
|
||||||
|
friend class VariantRefBase<ElementProxy<TUpstream>>;
|
||||||
|
|
||||||
|
template <typename, typename>
|
||||||
|
friend class MemberProxy;
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
friend class ElementProxy;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ElementProxy(TUpstream upstream, size_t index)
|
||||||
|
: upstream_(upstream), index_(index) {}
|
||||||
|
|
||||||
|
ElementProxy& operator=(const ElementProxy& src) {
|
||||||
|
this->set(src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ElementProxy& operator=(const T& src) {
|
||||||
|
this->set(src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ElementProxy& operator=(T* src) {
|
||||||
|
this->set(src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// clang-format off
|
||||||
|
ElementProxy(const ElementProxy& src) // Error here? See https://arduinojson.org/v7/proxy-non-copyable/
|
||||||
|
: upstream_(src.upstream_), index_(src.index_) {}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
ResourceManager* getResourceManager() const {
|
||||||
|
return VariantAttorney::getResourceManager(upstream_);
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE VariantData* getData() const {
|
||||||
|
return VariantData::getElement(
|
||||||
|
VariantAttorney::getData(upstream_), index_,
|
||||||
|
VariantAttorney::getResourceManager(upstream_));
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantData* getOrCreateData() const {
|
||||||
|
auto data = VariantAttorney::getOrCreateData(upstream_);
|
||||||
|
if (!data)
|
||||||
|
return nullptr;
|
||||||
|
return data->getOrAddElement(
|
||||||
|
index_, VariantAttorney::getResourceManager(upstream_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TUpstream upstream_;
|
||||||
|
size_t index_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Array/ElementProxy.hpp>
|
||||||
|
#include <ArduinoJson/Array/JsonArrayConst.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class JsonObject;
|
||||||
|
|
||||||
|
// A reference to an array in a JsonDocument
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/
|
||||||
|
class JsonArray : public detail::VariantOperators<JsonArray> {
|
||||||
|
friend class detail::VariantAttorney;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator = JsonArrayIterator;
|
||||||
|
|
||||||
|
// Constructs an unbound reference.
|
||||||
|
JsonArray() : data_(0), resources_(0) {}
|
||||||
|
|
||||||
|
// INTERNAL USE ONLY
|
||||||
|
JsonArray(detail::ArrayData* data, detail::ResourceManager* resources)
|
||||||
|
: data_(data), resources_(resources) {}
|
||||||
|
|
||||||
|
// Returns a JsonVariant pointing to the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonvariant/
|
||||||
|
operator JsonVariant() {
|
||||||
|
void* data = data_; // prevent warning cast-align
|
||||||
|
return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a read-only reference to the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/
|
||||||
|
operator JsonArrayConst() const {
|
||||||
|
return JsonArrayConst(data_, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a new (empty) element to the array.
|
||||||
|
// Returns a reference to the new element.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/add/
|
||||||
|
template <typename T, detail::enable_if_t<
|
||||||
|
!detail::is_same<T, JsonVariant>::value, int> = 0>
|
||||||
|
T add() const {
|
||||||
|
return add<JsonVariant>().to<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a new (null) element to the array.
|
||||||
|
// Returns a reference to the new element.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/add/
|
||||||
|
template <typename T, detail::enable_if_t<
|
||||||
|
detail::is_same<T, JsonVariant>::value, int> = 0>
|
||||||
|
JsonVariant add() const {
|
||||||
|
return JsonVariant(detail::ArrayData::addElement(data_, resources_),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a value to the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/add/
|
||||||
|
template <typename T>
|
||||||
|
bool add(const T& value) const {
|
||||||
|
return detail::ArrayData::addValue(data_, value, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a value to the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/add/
|
||||||
|
template <typename T,
|
||||||
|
detail::enable_if_t<!detail::is_const<T>::value, int> = 0>
|
||||||
|
bool add(T* value) const {
|
||||||
|
return detail::ArrayData::addValue(data_, value, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator to the first element of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/begin/
|
||||||
|
iterator begin() const {
|
||||||
|
if (!data_)
|
||||||
|
return iterator();
|
||||||
|
return iterator(data_->createIterator(resources_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator following the last element of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/end/
|
||||||
|
iterator end() const {
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies an array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/set/
|
||||||
|
bool set(JsonArrayConst src) const {
|
||||||
|
if (!data_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
clear();
|
||||||
|
for (auto element : src) {
|
||||||
|
if (!add(element))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the element at the specified iterator.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/remove/
|
||||||
|
void remove(iterator it) const {
|
||||||
|
detail::ArrayData::remove(data_, it.iterator_, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the element at the specified index.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/remove/
|
||||||
|
void remove(size_t index) const {
|
||||||
|
detail::ArrayData::removeElement(data_, index, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the element at the specified index.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/remove/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
void remove(const TVariant& variant) const {
|
||||||
|
if (variant.template is<size_t>())
|
||||||
|
remove(variant.template as<size_t>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes all the elements of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/clear/
|
||||||
|
void clear() const {
|
||||||
|
detail::ArrayData::clear(data_, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets the element at the specified index.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/subscript/
|
||||||
|
template <typename T,
|
||||||
|
detail::enable_if_t<detail::is_integral<T>::value, int> = 0>
|
||||||
|
detail::ElementProxy<JsonArray> operator[](T index) const {
|
||||||
|
return {*this, size_t(index)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets the element at the specified index.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/subscript/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
detail::ElementProxy<JsonArray> operator[](const TVariant& variant) const {
|
||||||
|
if (variant.template is<size_t>())
|
||||||
|
return {*this, variant.template as<size_t>()};
|
||||||
|
else
|
||||||
|
return {*this, size_t(-1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
operator JsonVariantConst() const {
|
||||||
|
return JsonVariantConst(collectionToVariant(data_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is unbound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/isnull/
|
||||||
|
bool isNull() const {
|
||||||
|
return data_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is bound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/isnull/
|
||||||
|
operator bool() const {
|
||||||
|
return data_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the depth (nesting level) of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/nesting/
|
||||||
|
size_t nesting() const {
|
||||||
|
return detail::VariantData::nesting(collectionToVariant(data_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of elements in the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarray/size/
|
||||||
|
size_t size() const {
|
||||||
|
return data_ ? data_->size(resources_) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use add<JsonVariant>() instead
|
||||||
|
ARDUINOJSON_DEPRECATED("use add<JsonVariant>() instead")
|
||||||
|
JsonVariant add() const {
|
||||||
|
return add<JsonVariant>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use add<JsonArray>() instead
|
||||||
|
ARDUINOJSON_DEPRECATED("use add<JsonArray>() instead")
|
||||||
|
JsonArray createNestedArray() const {
|
||||||
|
return add<JsonArray>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use add<JsonObject>() instead
|
||||||
|
ARDUINOJSON_DEPRECATED("use add<JsonObject>() instead")
|
||||||
|
JsonObject createNestedObject() const;
|
||||||
|
|
||||||
|
// DEPRECATED: always returns zero
|
||||||
|
ARDUINOJSON_DEPRECATED("always returns zero")
|
||||||
|
size_t memoryUsage() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
detail::ResourceManager* getResourceManager() const {
|
||||||
|
return resources_;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::VariantData* getData() const {
|
||||||
|
return collectionToVariant(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::VariantData* getOrCreateData() const {
|
||||||
|
return collectionToVariant(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::ArrayData* data_;
|
||||||
|
detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Array/JsonArrayIterator.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantAttorney.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class JsonObject;
|
||||||
|
|
||||||
|
// A read-only reference to an array in a JsonDocument
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/
|
||||||
|
class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
|
||||||
|
friend class JsonArray;
|
||||||
|
friend class detail::VariantAttorney;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator = JsonArrayConstIterator;
|
||||||
|
|
||||||
|
// Returns an iterator to the first element of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/begin/
|
||||||
|
iterator begin() const {
|
||||||
|
if (!data_)
|
||||||
|
return iterator();
|
||||||
|
return iterator(data_->createIterator(resources_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator to the element following the last element of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/end/
|
||||||
|
iterator end() const {
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates an unbound reference.
|
||||||
|
JsonArrayConst() : data_(0), resources_(0) {}
|
||||||
|
|
||||||
|
// INTERNAL USE ONLY
|
||||||
|
JsonArrayConst(const detail::ArrayData* data,
|
||||||
|
const detail::ResourceManager* resources)
|
||||||
|
: data_(data), resources_(resources) {}
|
||||||
|
|
||||||
|
// Returns the element at the specified index.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/subscript/
|
||||||
|
template <typename T,
|
||||||
|
detail::enable_if_t<detail::is_integral<T>::value, int> = 0>
|
||||||
|
JsonVariantConst operator[](T index) const {
|
||||||
|
return JsonVariantConst(
|
||||||
|
detail::ArrayData::getElement(data_, size_t(index), resources_),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the element at the specified index.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/subscript/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
JsonVariantConst operator[](const TVariant& variant) const {
|
||||||
|
if (variant.template is<size_t>())
|
||||||
|
return operator[](variant.template as<size_t>());
|
||||||
|
else
|
||||||
|
return JsonVariantConst();
|
||||||
|
}
|
||||||
|
|
||||||
|
operator JsonVariantConst() const {
|
||||||
|
return JsonVariantConst(getData(), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is unbound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/isnull/
|
||||||
|
bool isNull() const {
|
||||||
|
return data_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is bound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/isnull/
|
||||||
|
operator bool() const {
|
||||||
|
return data_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the depth (nesting level) of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/nesting/
|
||||||
|
size_t nesting() const {
|
||||||
|
return detail::VariantData::nesting(getData(), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of elements in the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsonarrayconst/size/
|
||||||
|
size_t size() const {
|
||||||
|
return data_ ? data_->size(resources_) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: always returns zero
|
||||||
|
ARDUINOJSON_DEPRECATED("always returns zero")
|
||||||
|
size_t memoryUsage() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const detail::VariantData* getData() const {
|
||||||
|
return collectionToVariant(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detail::ArrayData* data_;
|
||||||
|
const detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compares the content of two arrays.
|
||||||
|
// Returns true if the two arrays are equal.
|
||||||
|
inline bool operator==(JsonArrayConst lhs, JsonArrayConst rhs) {
|
||||||
|
if (!lhs && !rhs)
|
||||||
|
return true;
|
||||||
|
if (!lhs || !rhs)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto a = lhs.begin();
|
||||||
|
auto b = rhs.begin();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (a == b) // same pointer or both null
|
||||||
|
return true;
|
||||||
|
if (a == lhs.end() || b == rhs.end())
|
||||||
|
return false;
|
||||||
|
if (*a != *b)
|
||||||
|
return false;
|
||||||
|
++a;
|
||||||
|
++b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Variant/JsonVariant.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Ptr {
|
||||||
|
public:
|
||||||
|
Ptr(T value) : value_(value) {}
|
||||||
|
|
||||||
|
T* operator->() {
|
||||||
|
return &value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator*() {
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class JsonArrayIterator {
|
||||||
|
friend class JsonArray;
|
||||||
|
|
||||||
|
public:
|
||||||
|
JsonArrayIterator() {}
|
||||||
|
explicit JsonArrayIterator(detail::ArrayData::iterator iterator,
|
||||||
|
detail::ResourceManager* resources)
|
||||||
|
: iterator_(iterator), resources_(resources) {}
|
||||||
|
|
||||||
|
JsonVariant operator*() {
|
||||||
|
return JsonVariant(iterator_.data(), resources_);
|
||||||
|
}
|
||||||
|
Ptr<JsonVariant> operator->() {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const JsonArrayIterator& other) const {
|
||||||
|
return iterator_ == other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const JsonArrayIterator& other) const {
|
||||||
|
return iterator_ != other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArrayIterator& operator++() {
|
||||||
|
iterator_.next(resources_);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
detail::ArrayData::iterator iterator_;
|
||||||
|
detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class JsonArrayConstIterator {
|
||||||
|
friend class JsonArray;
|
||||||
|
|
||||||
|
public:
|
||||||
|
JsonArrayConstIterator() {}
|
||||||
|
explicit JsonArrayConstIterator(detail::ArrayData::iterator iterator,
|
||||||
|
const detail::ResourceManager* resources)
|
||||||
|
: iterator_(iterator), resources_(resources) {}
|
||||||
|
|
||||||
|
JsonVariantConst operator*() const {
|
||||||
|
return JsonVariantConst(iterator_.data(), resources_);
|
||||||
|
}
|
||||||
|
Ptr<JsonVariantConst> operator->() {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const JsonArrayConstIterator& other) const {
|
||||||
|
return iterator_ == other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const JsonArrayConstIterator& other) const {
|
||||||
|
return iterator_ != other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArrayConstIterator& operator++() {
|
||||||
|
iterator_.next(resources_);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
detail::ArrayData::iterator iterator_;
|
||||||
|
const detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Array/JsonArray.hpp>
|
||||||
|
#include <ArduinoJson/Document/JsonDocument.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// Copies a value to a JsonVariant.
|
||||||
|
// This is a degenerated form of copyArray() to stop the recursion.
|
||||||
|
template <typename T, detail::enable_if_t<!detail::is_array<T>::value, int> = 0>
|
||||||
|
inline bool copyArray(const T& src, JsonVariant dst) {
|
||||||
|
return dst.set(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies values from an array to a JsonArray or a JsonVariant.
|
||||||
|
// https://arduinojson.org/v7/api/misc/copyarray/
|
||||||
|
template <typename T, size_t N, typename TDestination,
|
||||||
|
detail::enable_if_t<
|
||||||
|
!detail::is_base_of<JsonDocument, TDestination>::value, int> = 0>
|
||||||
|
inline bool copyArray(T (&src)[N], const TDestination& dst) {
|
||||||
|
return copyArray(src, N, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies values from an array to a JsonArray or a JsonVariant.
|
||||||
|
// https://arduinojson.org/v7/api/misc/copyarray/
|
||||||
|
template <typename T, typename TDestination,
|
||||||
|
detail::enable_if_t<
|
||||||
|
!detail::is_base_of<JsonDocument, TDestination>::value, int> = 0>
|
||||||
|
inline bool copyArray(const T* src, size_t len, const TDestination& dst) {
|
||||||
|
bool ok = true;
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
ok &= copyArray(src[i], dst.template add<JsonVariant>());
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies a string to a JsonVariant.
|
||||||
|
// This is a degenerated form of copyArray() to handle strings.
|
||||||
|
template <typename TDestination>
|
||||||
|
inline bool copyArray(const char* src, size_t, const TDestination& dst) {
|
||||||
|
return dst.set(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies values from an array to a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/misc/copyarray/
|
||||||
|
template <typename T>
|
||||||
|
inline bool copyArray(const T& src, JsonDocument& dst) {
|
||||||
|
return copyArray(src, dst.to<JsonArray>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies an array to a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/misc/copyarray/
|
||||||
|
template <typename T>
|
||||||
|
inline bool copyArray(const T* src, size_t len, JsonDocument& dst) {
|
||||||
|
return copyArray(src, len, dst.to<JsonArray>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies a value from a JsonVariant.
|
||||||
|
// This is a degenerated form of copyArray() to stop the recursion.
|
||||||
|
template <typename T, detail::enable_if_t<!detail::is_array<T>::value, int> = 0>
|
||||||
|
inline size_t copyArray(JsonVariantConst src, T& dst) {
|
||||||
|
dst = src.as<T>();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies values from a JsonArray or JsonVariant to an array.
|
||||||
|
// https://arduinojson.org/v7/api/misc/copyarray/
|
||||||
|
template <typename T, size_t N>
|
||||||
|
inline size_t copyArray(JsonArrayConst src, T (&dst)[N]) {
|
||||||
|
return copyArray(src, dst, N);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies values from a JsonArray or JsonVariant to an array.
|
||||||
|
// https://arduinojson.org/v7/api/misc/copyarray/
|
||||||
|
template <typename T>
|
||||||
|
inline size_t copyArray(JsonArrayConst src, T* dst, size_t len) {
|
||||||
|
size_t i = 0;
|
||||||
|
for (JsonArrayConst::iterator it = src.begin(); it != src.end() && i < len;
|
||||||
|
++it)
|
||||||
|
copyArray(*it, dst[i++]);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies a string from a JsonVariant.
|
||||||
|
// This is a degenerated form of copyArray() to handle strings.
|
||||||
|
template <size_t N>
|
||||||
|
inline size_t copyArray(JsonVariantConst src, char (&dst)[N]) {
|
||||||
|
JsonString s = src;
|
||||||
|
size_t len = N - 1;
|
||||||
|
if (len > s.size())
|
||||||
|
len = s.size();
|
||||||
|
memcpy(dst, s.c_str(), len);
|
||||||
|
dst[len] = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies values from a JsonDocument to an array.
|
||||||
|
// https://arduinojson.org/v7/api/misc/copyarray/
|
||||||
|
template <
|
||||||
|
typename TSource, typename T,
|
||||||
|
detail::enable_if_t<detail::is_array<T>::value &&
|
||||||
|
detail::is_base_of<JsonDocument, TSource>::value,
|
||||||
|
int> = 0>
|
||||||
|
inline size_t copyArray(const TSource& src, T& dst) {
|
||||||
|
return copyArray(src.template as<JsonArrayConst>(), dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/MemoryPool.hpp>
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
|
||||||
|
#include <stddef.h> // size_t
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class VariantData;
|
||||||
|
class ResourceManager;
|
||||||
|
|
||||||
|
class CollectionIterator {
|
||||||
|
friend class CollectionData;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CollectionIterator() : slot_(nullptr), currentId_(NULL_SLOT) {}
|
||||||
|
|
||||||
|
void next(const ResourceManager* resources);
|
||||||
|
|
||||||
|
bool done() const {
|
||||||
|
return slot_ == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CollectionIterator& other) const {
|
||||||
|
return slot_ == other.slot_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const CollectionIterator& other) const {
|
||||||
|
return slot_ != other.slot_;
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantData* operator->() {
|
||||||
|
ARDUINOJSON_ASSERT(slot_ != nullptr);
|
||||||
|
return data();
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantData& operator*() {
|
||||||
|
ARDUINOJSON_ASSERT(slot_ != nullptr);
|
||||||
|
return *data();
|
||||||
|
}
|
||||||
|
|
||||||
|
const VariantData& operator*() const {
|
||||||
|
ARDUINOJSON_ASSERT(slot_ != nullptr);
|
||||||
|
return *data();
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantData* data() {
|
||||||
|
return reinterpret_cast<VariantData*>(slot_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VariantData* data() const {
|
||||||
|
return reinterpret_cast<const VariantData*>(slot_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CollectionIterator(VariantData* slot, SlotId slotId);
|
||||||
|
|
||||||
|
VariantData* slot_;
|
||||||
|
SlotId currentId_, nextId_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CollectionData {
|
||||||
|
SlotId head_ = NULL_SLOT;
|
||||||
|
SlotId tail_ = NULL_SLOT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Placement new
|
||||||
|
static void* operator new(size_t, void* p) noexcept {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void operator delete(void*, void*) noexcept {}
|
||||||
|
|
||||||
|
using iterator = CollectionIterator;
|
||||||
|
|
||||||
|
iterator createIterator(const ResourceManager* resources) const;
|
||||||
|
|
||||||
|
size_t size(const ResourceManager*) const;
|
||||||
|
size_t nesting(const ResourceManager*) const;
|
||||||
|
|
||||||
|
void clear(ResourceManager* resources);
|
||||||
|
|
||||||
|
static void clear(CollectionData* collection, ResourceManager* resources) {
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection->clear(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotId head() const {
|
||||||
|
return head_;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void appendOne(Slot<VariantData> slot, const ResourceManager* resources);
|
||||||
|
void appendPair(Slot<VariantData> key, Slot<VariantData> value,
|
||||||
|
const ResourceManager* resources);
|
||||||
|
|
||||||
|
void removeOne(iterator it, ResourceManager* resources);
|
||||||
|
void removePair(iterator it, ResourceManager* resources);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Slot<VariantData> getPreviousSlot(VariantData*, const ResourceManager*) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const VariantData* collectionToVariant(
|
||||||
|
const CollectionData* collection) {
|
||||||
|
const void* data = collection; // prevent warning cast-align
|
||||||
|
return reinterpret_cast<const VariantData*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VariantData* collectionToVariant(CollectionData* collection) {
|
||||||
|
void* data = collection; // prevent warning cast-align
|
||||||
|
return reinterpret_cast<VariantData*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Collection/CollectionData.hpp>
|
||||||
|
#include <ArduinoJson/Memory/Alignment.hpp>
|
||||||
|
#include <ArduinoJson/Strings/StringAdapters.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantCompare.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
inline CollectionIterator::CollectionIterator(VariantData* slot, SlotId slotId)
|
||||||
|
: slot_(slot), currentId_(slotId) {
|
||||||
|
nextId_ = slot_ ? slot_->next() : NULL_SLOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void CollectionIterator::next(const ResourceManager* resources) {
|
||||||
|
ARDUINOJSON_ASSERT(currentId_ != NULL_SLOT);
|
||||||
|
slot_ = resources->getVariant(nextId_);
|
||||||
|
currentId_ = nextId_;
|
||||||
|
if (slot_)
|
||||||
|
nextId_ = slot_->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline CollectionData::iterator CollectionData::createIterator(
|
||||||
|
const ResourceManager* resources) const {
|
||||||
|
return iterator(resources->getVariant(head_), head_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void CollectionData::appendOne(Slot<VariantData> slot,
|
||||||
|
const ResourceManager* resources) {
|
||||||
|
if (tail_ != NULL_SLOT) {
|
||||||
|
auto tail = resources->getVariant(tail_);
|
||||||
|
tail->setNext(slot.id());
|
||||||
|
tail_ = slot.id();
|
||||||
|
} else {
|
||||||
|
head_ = slot.id();
|
||||||
|
tail_ = slot.id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void CollectionData::appendPair(Slot<VariantData> key,
|
||||||
|
Slot<VariantData> value,
|
||||||
|
const ResourceManager* resources) {
|
||||||
|
key->setNext(value.id());
|
||||||
|
|
||||||
|
if (tail_ != NULL_SLOT) {
|
||||||
|
auto tail = resources->getVariant(tail_);
|
||||||
|
tail->setNext(key.id());
|
||||||
|
tail_ = value.id();
|
||||||
|
} else {
|
||||||
|
head_ = key.id();
|
||||||
|
tail_ = value.id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void CollectionData::clear(ResourceManager* resources) {
|
||||||
|
auto next = head_;
|
||||||
|
while (next != NULL_SLOT) {
|
||||||
|
auto currId = next;
|
||||||
|
auto slot = resources->getVariant(next);
|
||||||
|
next = slot->next();
|
||||||
|
resources->freeVariant({slot, currId});
|
||||||
|
}
|
||||||
|
|
||||||
|
head_ = NULL_SLOT;
|
||||||
|
tail_ = NULL_SLOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Slot<VariantData> CollectionData::getPreviousSlot(
|
||||||
|
VariantData* target, const ResourceManager* resources) const {
|
||||||
|
auto prev = Slot<VariantData>();
|
||||||
|
auto currentId = head_;
|
||||||
|
while (currentId != NULL_SLOT) {
|
||||||
|
auto currentSlot = resources->getVariant(currentId);
|
||||||
|
if (currentSlot == target)
|
||||||
|
break;
|
||||||
|
prev = Slot<VariantData>(currentSlot, currentId);
|
||||||
|
currentId = currentSlot->next();
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void CollectionData::removeOne(iterator it, ResourceManager* resources) {
|
||||||
|
if (it.done())
|
||||||
|
return;
|
||||||
|
auto curr = it.slot_;
|
||||||
|
auto prev = getPreviousSlot(curr, resources);
|
||||||
|
auto next = curr->next();
|
||||||
|
if (prev)
|
||||||
|
prev->setNext(next);
|
||||||
|
else
|
||||||
|
head_ = next;
|
||||||
|
if (next == NULL_SLOT)
|
||||||
|
tail_ = prev.id();
|
||||||
|
resources->freeVariant({it.slot_, it.currentId_});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void CollectionData::removePair(ObjectData::iterator it,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
if (it.done())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto keySlot = it.slot_;
|
||||||
|
|
||||||
|
auto valueId = it.nextId_;
|
||||||
|
auto valueSlot = resources->getVariant(valueId);
|
||||||
|
|
||||||
|
// remove value slot
|
||||||
|
keySlot->setNext(valueSlot->next());
|
||||||
|
resources->freeVariant({valueSlot, valueId});
|
||||||
|
|
||||||
|
// remove key slot
|
||||||
|
removeOne(it, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t CollectionData::nesting(const ResourceManager* resources) const {
|
||||||
|
size_t maxChildNesting = 0;
|
||||||
|
for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
|
||||||
|
size_t childNesting = it->nesting(resources);
|
||||||
|
if (childNesting > maxChildNesting)
|
||||||
|
maxChildNesting = childNesting;
|
||||||
|
}
|
||||||
|
return maxChildNesting + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t CollectionData::size(const ResourceManager* resources) const {
|
||||||
|
size_t count = 0;
|
||||||
|
for (auto it = createIterator(resources); !it.done(); it.next(resources))
|
||||||
|
count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Support std::istream and std::ostream
|
||||||
|
// https://arduinojson.org/v7/config/enable_std_stream/
|
||||||
|
#ifndef ARDUINOJSON_ENABLE_STD_STREAM
|
||||||
|
# ifdef __has_include
|
||||||
|
# if __has_include(<istream>) && \
|
||||||
|
__has_include(<ostream>) && \
|
||||||
|
!defined(min) && \
|
||||||
|
!defined(max)
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STREAM 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STREAM 0
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# ifdef ARDUINO
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STREAM 0
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STREAM 1
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Support std::string
|
||||||
|
// https://arduinojson.org/v7/config/enable_std_string/
|
||||||
|
#ifndef ARDUINOJSON_ENABLE_STD_STRING
|
||||||
|
# ifdef __has_include
|
||||||
|
# if __has_include(<string>) && !defined(min) && !defined(max)
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STRING 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STRING 0
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# ifdef ARDUINO
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STRING 0
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_STD_STRING 1
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Support for std::string_view
|
||||||
|
#ifndef ARDUINOJSON_ENABLE_STRING_VIEW
|
||||||
|
# ifdef __has_include
|
||||||
|
# if __has_include(<string_view>) && __cplusplus >= 201703L
|
||||||
|
# define ARDUINOJSON_ENABLE_STRING_VIEW 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_STRING_VIEW 0
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_STRING_VIEW 0
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Pointer size: a heuristic to set sensible defaults
|
||||||
|
#ifndef ARDUINOJSON_SIZEOF_POINTER
|
||||||
|
# if defined(__SIZEOF_POINTER__)
|
||||||
|
# define ARDUINOJSON_SIZEOF_POINTER __SIZEOF_POINTER__
|
||||||
|
# elif defined(_WIN64) && _WIN64
|
||||||
|
# define ARDUINOJSON_SIZEOF_POINTER 8 // 64 bits
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_SIZEOF_POINTER 4 // assume 32 bits otherwise
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Store floating-point values with float (0) or double (1)
|
||||||
|
// https://arduinojson.org/v7/config/use_double/
|
||||||
|
#ifndef ARDUINOJSON_USE_DOUBLE
|
||||||
|
# if ARDUINOJSON_SIZEOF_POINTER >= 4 // 32 & 64 bits systems
|
||||||
|
# define ARDUINOJSON_USE_DOUBLE 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_USE_DOUBLE 0
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Store integral values with long (0) or long long (1)
|
||||||
|
// https://arduinojson.org/v7/config/use_long_long/
|
||||||
|
#ifndef ARDUINOJSON_USE_LONG_LONG
|
||||||
|
# if ARDUINOJSON_SIZEOF_POINTER >= 4 // 32 & 64 bits systems
|
||||||
|
# define ARDUINOJSON_USE_LONG_LONG 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_USE_LONG_LONG 0
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Limit nesting as the stack is likely to be small
|
||||||
|
// https://arduinojson.org/v7/config/default_nesting_limit/
|
||||||
|
#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT
|
||||||
|
# define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Number of bytes to store a slot id
|
||||||
|
// https://arduinojson.org/v7/config/slot_id_size/
|
||||||
|
#ifndef ARDUINOJSON_SLOT_ID_SIZE
|
||||||
|
# if ARDUINOJSON_SIZEOF_POINTER <= 2
|
||||||
|
// 8-bit and 16-bit archs => up to 255 slots
|
||||||
|
# define ARDUINOJSON_SLOT_ID_SIZE 1
|
||||||
|
# elif ARDUINOJSON_SIZEOF_POINTER == 4
|
||||||
|
// 32-bit arch => up to 65535 slots
|
||||||
|
# define ARDUINOJSON_SLOT_ID_SIZE 2
|
||||||
|
# else
|
||||||
|
// 64-bit arch => up to 4294967295 slots
|
||||||
|
# define ARDUINOJSON_SLOT_ID_SIZE 4
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Capacity of each variant pool (in slots)
|
||||||
|
#ifndef ARDUINOJSON_POOL_CAPACITY
|
||||||
|
# if ARDUINOJSON_SLOT_ID_SIZE == 1
|
||||||
|
# define ARDUINOJSON_POOL_CAPACITY 16 // 96 bytes
|
||||||
|
# elif ARDUINOJSON_SLOT_ID_SIZE == 2
|
||||||
|
# define ARDUINOJSON_POOL_CAPACITY 128 // 1024 bytes
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_POOL_CAPACITY 256 // 4096 bytes
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Initial capacity of the pool list
|
||||||
|
#ifndef ARDUINOJSON_INITIAL_POOL_COUNT
|
||||||
|
# define ARDUINOJSON_INITIAL_POOL_COUNT 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Automatically call shrinkToFit() from deserializeXxx()
|
||||||
|
// Disabled by default on 8-bit platforms because it's not worth the increase in
|
||||||
|
// code size
|
||||||
|
#ifndef ARDUINOJSON_AUTO_SHRINK
|
||||||
|
# if ARDUINOJSON_SIZEOF_POINTER <= 2
|
||||||
|
# define ARDUINOJSON_AUTO_SHRINK 0
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_AUTO_SHRINK 1
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Number of bytes to store the length of a string
|
||||||
|
// https://arduinojson.org/v7/config/string_length_size/
|
||||||
|
#ifndef ARDUINOJSON_STRING_LENGTH_SIZE
|
||||||
|
# if ARDUINOJSON_SIZEOF_POINTER <= 2
|
||||||
|
# define ARDUINOJSON_STRING_LENGTH_SIZE 1 // up to 255 characters
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_STRING_LENGTH_SIZE 2 // up to 65535 characters
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
|
||||||
|
// Enable support for Arduino's String class
|
||||||
|
// https://arduinojson.org/v7/config/enable_arduino_string/
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||||
|
# define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// Enable support for Arduino's Stream class
|
||||||
|
// https://arduinojson.org/v7/config/enable_arduino_stream/
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM
|
||||||
|
# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// Enable support for Arduino's Print class
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT
|
||||||
|
# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 1
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// Enable support for PROGMEM
|
||||||
|
// https://arduinojson.org/v7/config/enable_progmem/
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_PROGMEM
|
||||||
|
# define ARDUINOJSON_ENABLE_PROGMEM 1
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#else // ARDUINO
|
||||||
|
|
||||||
|
// Disable support for Arduino's String class
|
||||||
|
// https://arduinojson.org/v7/config/enable_arduino_string/
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||||
|
# define ARDUINOJSON_ENABLE_ARDUINO_STRING 0
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// Disable support for Arduino's Stream class
|
||||||
|
// https://arduinojson.org/v7/config/enable_arduino_stream/
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM
|
||||||
|
# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// Disable support for Arduino's Print class
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT
|
||||||
|
# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// Enable PROGMEM support on AVR only
|
||||||
|
// https://arduinojson.org/v7/config/enable_progmem/
|
||||||
|
# ifndef ARDUINOJSON_ENABLE_PROGMEM
|
||||||
|
# ifdef __AVR__
|
||||||
|
# define ARDUINOJSON_ENABLE_PROGMEM 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_PROGMEM 0
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#endif // ARDUINO
|
||||||
|
|
||||||
|
// Convert unicode escape sequence (\u0123) to UTF-8
|
||||||
|
// https://arduinojson.org/v7/config/decode_unicode/
|
||||||
|
#ifndef ARDUINOJSON_DECODE_UNICODE
|
||||||
|
# define ARDUINOJSON_DECODE_UNICODE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Ignore comments in input
|
||||||
|
// https://arduinojson.org/v7/config/enable_comments/
|
||||||
|
#ifndef ARDUINOJSON_ENABLE_COMMENTS
|
||||||
|
# define ARDUINOJSON_ENABLE_COMMENTS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Support NaN in JSON
|
||||||
|
// https://arduinojson.org/v7/config/enable_nan/
|
||||||
|
#ifndef ARDUINOJSON_ENABLE_NAN
|
||||||
|
# define ARDUINOJSON_ENABLE_NAN 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Support Infinity in JSON
|
||||||
|
// https://arduinojson.org/v7/config/enable_infinity/
|
||||||
|
#ifndef ARDUINOJSON_ENABLE_INFINITY
|
||||||
|
# define ARDUINOJSON_ENABLE_INFINITY 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Control the exponentiation threshold for big numbers
|
||||||
|
// CAUTION: cannot be more that 1e9 !!!!
|
||||||
|
// https://arduinojson.org/v7/config/positive_exponentiation_threshold/
|
||||||
|
#ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD
|
||||||
|
# define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Control the exponentiation threshold for small numbers
|
||||||
|
// https://arduinojson.org/v7/config/negative_exponentiation_threshold/
|
||||||
|
#ifndef ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD
|
||||||
|
# define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINOJSON_LITTLE_ENDIAN
|
||||||
|
# if defined(_MSC_VER) || \
|
||||||
|
(defined(__BYTE_ORDER__) && \
|
||||||
|
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \
|
||||||
|
defined(__LITTLE_ENDIAN__) || defined(__i386) || defined(__x86_64)
|
||||||
|
# define ARDUINOJSON_LITTLE_ENDIAN 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_LITTLE_ENDIAN 0
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINOJSON_ENABLE_ALIGNMENT
|
||||||
|
# if defined(__AVR)
|
||||||
|
# define ARDUINOJSON_ENABLE_ALIGNMENT 0
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_ALIGNMENT 1
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINOJSON_TAB
|
||||||
|
# define ARDUINOJSON_TAB " "
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINOJSON_STRING_BUFFER_SIZE
|
||||||
|
# define ARDUINOJSON_STRING_BUFFER_SIZE 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINOJSON_DEBUG
|
||||||
|
# ifdef __PLATFORMIO_BUILD_DEBUG__
|
||||||
|
# define ARDUINOJSON_DEBUG 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_DEBUG 0
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_LONG_LONG || ARDUINOJSON_USE_DOUBLE
|
||||||
|
# define ARDUINOJSON_USE_EXTENSIONS 1
|
||||||
|
#else
|
||||||
|
# define ARDUINOJSON_USE_EXTENSIONS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(nullptr)
|
||||||
|
# error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr
|
||||||
|
// See https://github.com/bblanchon/ArduinoJson/issues/1355
|
||||||
|
#endif
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/pgmspace_generic.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/preprocessor.hpp>
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_STD_STREAM
|
||||||
|
# include <ostream>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class DeserializationError {
|
||||||
|
public:
|
||||||
|
enum Code {
|
||||||
|
Ok,
|
||||||
|
EmptyInput,
|
||||||
|
IncompleteInput,
|
||||||
|
InvalidInput,
|
||||||
|
NoMemory,
|
||||||
|
TooDeep
|
||||||
|
};
|
||||||
|
|
||||||
|
DeserializationError() {}
|
||||||
|
DeserializationError(Code c) : code_(c) {}
|
||||||
|
|
||||||
|
// Compare with DeserializationError
|
||||||
|
friend bool operator==(const DeserializationError& lhs,
|
||||||
|
const DeserializationError& rhs) {
|
||||||
|
return lhs.code_ == rhs.code_;
|
||||||
|
}
|
||||||
|
friend bool operator!=(const DeserializationError& lhs,
|
||||||
|
const DeserializationError& rhs) {
|
||||||
|
return lhs.code_ != rhs.code_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare with Code
|
||||||
|
friend bool operator==(const DeserializationError& lhs, Code rhs) {
|
||||||
|
return lhs.code_ == rhs;
|
||||||
|
}
|
||||||
|
friend bool operator==(Code lhs, const DeserializationError& rhs) {
|
||||||
|
return lhs == rhs.code_;
|
||||||
|
}
|
||||||
|
friend bool operator!=(const DeserializationError& lhs, Code rhs) {
|
||||||
|
return lhs.code_ != rhs;
|
||||||
|
}
|
||||||
|
friend bool operator!=(Code lhs, const DeserializationError& rhs) {
|
||||||
|
return lhs != rhs.code_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if there is an error
|
||||||
|
explicit operator bool() const {
|
||||||
|
return code_ != Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns internal enum, useful for switch statement
|
||||||
|
Code code() const {
|
||||||
|
return code_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* c_str() const {
|
||||||
|
static const char* messages[] = {
|
||||||
|
"Ok", "EmptyInput", "IncompleteInput",
|
||||||
|
"InvalidInput", "NoMemory", "TooDeep"};
|
||||||
|
ARDUINOJSON_ASSERT(static_cast<size_t>(code_) <
|
||||||
|
sizeof(messages) / sizeof(messages[0]));
|
||||||
|
return messages[code_];
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_PROGMEM
|
||||||
|
const __FlashStringHelper* f_str() const {
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(char, s0, "Ok");
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(char, s1, "EmptyInput");
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(char, s2, "IncompleteInput");
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(char, s3, "InvalidInput");
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(char, s4, "NoMemory");
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(char, s5, "TooDeep");
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(const char*, messages,
|
||||||
|
{s0, s1, s2, s3, s4, s5});
|
||||||
|
return reinterpret_cast<const __FlashStringHelper*>(
|
||||||
|
detail::pgm_read(messages + code_));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
Code code_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_STD_STREAM
|
||||||
|
inline std::ostream& operator<<(std::ostream& s,
|
||||||
|
const DeserializationError& e) {
|
||||||
|
s << e.c_str();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::ostream& operator<<(std::ostream& s, DeserializationError::Code c) {
|
||||||
|
s << DeserializationError(c).c_str();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Deserialization/Filter.hpp>
|
||||||
|
#include <ArduinoJson/Deserialization/NestingLimit.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
struct DeserializationOptions {
|
||||||
|
TFilter filter;
|
||||||
|
DeserializationOption::NestingLimit nestingLimit;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
inline DeserializationOptions<TFilter> makeDeserializationOptions(
|
||||||
|
TFilter filter, DeserializationOption::NestingLimit nestingLimit = {}) {
|
||||||
|
return {filter, nestingLimit};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
inline DeserializationOptions<TFilter> makeDeserializationOptions(
|
||||||
|
DeserializationOption::NestingLimit nestingLimit, TFilter filter) {
|
||||||
|
return {filter, nestingLimit};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DeserializationOptions<AllowAllFilter> makeDeserializationOptions(
|
||||||
|
DeserializationOption::NestingLimit nestingLimit = {}) {
|
||||||
|
return {{}, nestingLimit};
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Variant/JsonVariant.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantAttorney.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
namespace DeserializationOption {
|
||||||
|
class Filter {
|
||||||
|
public:
|
||||||
|
#if ARDUINOJSON_AUTO_SHRINK
|
||||||
|
explicit Filter(JsonDocument& doc) : variant_(doc) {
|
||||||
|
doc.shrinkToFit();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
explicit Filter(JsonVariantConst variant) : variant_(variant) {}
|
||||||
|
|
||||||
|
bool allow() const {
|
||||||
|
return variant_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowArray() const {
|
||||||
|
return variant_ == true || variant_.is<JsonArrayConst>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowObject() const {
|
||||||
|
return variant_ == true || variant_.is<JsonObjectConst>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowValue() const {
|
||||||
|
return variant_ == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TKey>
|
||||||
|
Filter operator[](const TKey& key) const {
|
||||||
|
if (variant_ == true) // "true" means "allow recursively"
|
||||||
|
return *this;
|
||||||
|
JsonVariantConst member = variant_[key];
|
||||||
|
return Filter(member.isNull() ? variant_["*"] : member);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JsonVariantConst variant_;
|
||||||
|
};
|
||||||
|
} // namespace DeserializationOption
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
struct AllowAllFilter {
|
||||||
|
bool allow() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowArray() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowObject() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowValue() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TKey>
|
||||||
|
AllowAllFilter operator[](const TKey&) const {
|
||||||
|
return AllowAllFilter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
namespace DeserializationOption {
|
||||||
|
class NestingLimit {
|
||||||
|
public:
|
||||||
|
NestingLimit() : value_(ARDUINOJSON_DEFAULT_NESTING_LIMIT) {}
|
||||||
|
explicit NestingLimit(uint8_t n) : value_(n) {}
|
||||||
|
|
||||||
|
NestingLimit decrement() const {
|
||||||
|
ARDUINOJSON_ASSERT(value_ > 0);
|
||||||
|
return NestingLimit(static_cast<uint8_t>(value_ - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reached() const {
|
||||||
|
return value_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t value_;
|
||||||
|
};
|
||||||
|
} // namespace DeserializationOption
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/utility.hpp>
|
||||||
|
|
||||||
|
#include <stdlib.h> // for size_t
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// The default reader is a simple wrapper for Readers that are not copyable
|
||||||
|
template <typename TSource, typename Enable = void>
|
||||||
|
struct Reader {
|
||||||
|
public:
|
||||||
|
Reader(TSource& source) : source_(&source) {}
|
||||||
|
|
||||||
|
int read() {
|
||||||
|
// clang-format off
|
||||||
|
return source_->read(); // Error here? See https://arduinojson.org/v7/invalid-input/
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
return source_->readBytes(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TSource* source_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TSource, typename Enable = void>
|
||||||
|
struct BoundedReader {
|
||||||
|
// no default implementation because we need to pass the size to the
|
||||||
|
// constructor
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#include <ArduinoJson/Deserialization/Readers/IteratorReader.hpp>
|
||||||
|
#include <ArduinoJson/Deserialization/Readers/RamReader.hpp>
|
||||||
|
#include <ArduinoJson/Deserialization/Readers/VariantReader.hpp>
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_ARDUINO_STREAM
|
||||||
|
# include <ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||||
|
# include <ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_PROGMEM
|
||||||
|
# include <ArduinoJson/Deserialization/Readers/FlashReader.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_STD_STREAM
|
||||||
|
# include <ArduinoJson/Deserialization/Readers/StdStreamReader.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TInput>
|
||||||
|
Reader<remove_reference_t<TInput>> makeReader(TInput&& input) {
|
||||||
|
return Reader<remove_reference_t<TInput>>{detail::forward<TInput>(input)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TChar>
|
||||||
|
BoundedReader<TChar*> makeReader(TChar* input, size_t inputSize) {
|
||||||
|
return BoundedReader<TChar*>{input, inputSize};
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
Vendored
+30
@@ -0,0 +1,30 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TSource>
|
||||||
|
struct Reader<TSource, enable_if_t<is_base_of<Stream, TSource>::value>> {
|
||||||
|
public:
|
||||||
|
explicit Reader(Stream& stream) : stream_(&stream) {}
|
||||||
|
|
||||||
|
int read() {
|
||||||
|
// don't use stream_->read() as it ignores the timeout
|
||||||
|
char c;
|
||||||
|
return stream_->readBytes(&c, 1) ? static_cast<unsigned char>(c) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
return stream_->readBytes(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Stream* stream_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
Vendored
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TSource>
|
||||||
|
struct Reader<TSource, enable_if_t<is_base_of<::String, TSource>::value>>
|
||||||
|
: BoundedReader<const char*> {
|
||||||
|
explicit Reader(const ::String& s)
|
||||||
|
: BoundedReader<const char*>(s.c_str(), s.length()) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Polyfills/pgmspace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct Reader<const __FlashStringHelper*, void> {
|
||||||
|
const char* ptr_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Reader(const __FlashStringHelper* ptr)
|
||||||
|
: ptr_(reinterpret_cast<const char*>(ptr)) {}
|
||||||
|
|
||||||
|
int read() {
|
||||||
|
return pgm_read_byte(ptr_++);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
memcpy_P(buffer, ptr_, length);
|
||||||
|
ptr_ += length;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct BoundedReader<const __FlashStringHelper*, void> {
|
||||||
|
const char* ptr_;
|
||||||
|
const char* end_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BoundedReader(const __FlashStringHelper* ptr, size_t size)
|
||||||
|
: ptr_(reinterpret_cast<const char*>(ptr)), end_(ptr_ + size) {}
|
||||||
|
|
||||||
|
int read() {
|
||||||
|
if (ptr_ < end_)
|
||||||
|
return pgm_read_byte(ptr_++);
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
size_t available = static_cast<size_t>(end_ - ptr_);
|
||||||
|
if (available < length)
|
||||||
|
length = available;
|
||||||
|
memcpy_P(buffer, ptr_, length);
|
||||||
|
ptr_ += length;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TIterator>
|
||||||
|
class IteratorReader {
|
||||||
|
TIterator ptr_, end_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit IteratorReader(TIterator begin, TIterator end)
|
||||||
|
: ptr_(begin), end_(end) {}
|
||||||
|
|
||||||
|
int read() {
|
||||||
|
if (ptr_ < end_)
|
||||||
|
return static_cast<unsigned char>(*ptr_++);
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < length && ptr_ < end_)
|
||||||
|
buffer[i++] = *ptr_++;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TSource>
|
||||||
|
struct Reader<TSource, void_t<typename TSource::const_iterator>>
|
||||||
|
: IteratorReader<typename TSource::const_iterator> {
|
||||||
|
explicit Reader(const TSource& source)
|
||||||
|
: IteratorReader<typename TSource::const_iterator>(source.begin(),
|
||||||
|
source.end()) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IsCharOrVoid {
|
||||||
|
static const bool value =
|
||||||
|
is_same<T, void>::value || is_same<T, char>::value ||
|
||||||
|
is_same<T, unsigned char>::value || is_same<T, signed char>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IsCharOrVoid<const T> : IsCharOrVoid<T> {};
|
||||||
|
|
||||||
|
template <typename TSource>
|
||||||
|
struct Reader<TSource*, enable_if_t<IsCharOrVoid<TSource>::value>> {
|
||||||
|
const char* ptr_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Reader(const void* ptr)
|
||||||
|
: ptr_(ptr ? reinterpret_cast<const char*>(ptr) : "") {}
|
||||||
|
|
||||||
|
int read() {
|
||||||
|
return static_cast<unsigned char>(*ptr_++);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
for (size_t i = 0; i < length; i++)
|
||||||
|
buffer[i] = *ptr_++;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TSource>
|
||||||
|
struct BoundedReader<TSource*, enable_if_t<IsCharOrVoid<TSource>::value>>
|
||||||
|
: public IteratorReader<const char*> {
|
||||||
|
public:
|
||||||
|
explicit BoundedReader(const void* ptr, size_t len)
|
||||||
|
: IteratorReader<const char*>(reinterpret_cast<const char*>(ptr),
|
||||||
|
reinterpret_cast<const char*>(ptr) + len) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
Vendored
+29
@@ -0,0 +1,29 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TSource>
|
||||||
|
struct Reader<TSource, enable_if_t<is_base_of<std::istream, TSource>::value>> {
|
||||||
|
public:
|
||||||
|
explicit Reader(std::istream& stream) : stream_(&stream) {}
|
||||||
|
|
||||||
|
int read() {
|
||||||
|
return stream_->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
stream_->read(buffer, static_cast<std::streamsize>(length));
|
||||||
|
return static_cast<size_t>(stream_->gcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::istream* stream_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Object/MemberProxy.hpp>
|
||||||
|
#include <ArduinoJson/Variant/JsonVariantConst.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TVariant>
|
||||||
|
struct Reader<TVariant, enable_if_t<IsVariant<TVariant>::value>>
|
||||||
|
: Reader<char*, void> {
|
||||||
|
explicit Reader(const TVariant& x)
|
||||||
|
: Reader<char*, void>(x.template as<const char*>()) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Deserialization/DeserializationError.hpp>
|
||||||
|
#include <ArduinoJson/Deserialization/DeserializationOptions.hpp>
|
||||||
|
#include <ArduinoJson/Deserialization/Reader.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/utility.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// A meta-function that returns the first type of the parameter pack
|
||||||
|
// or void if empty
|
||||||
|
template <typename...>
|
||||||
|
struct first_or_void {
|
||||||
|
using type = void;
|
||||||
|
};
|
||||||
|
template <typename T, typename... Rest>
|
||||||
|
struct first_or_void<T, Rest...> {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A meta-function that returns true if T is a valid destination type for
|
||||||
|
// deserialize()
|
||||||
|
template <class T>
|
||||||
|
using is_deserialize_destination =
|
||||||
|
bool_constant<is_base_of<JsonDocument, remove_cv_t<T>>::value ||
|
||||||
|
IsVariant<T>::value>;
|
||||||
|
|
||||||
|
template <typename TDestination>
|
||||||
|
inline void shrinkJsonDocument(TDestination&) {
|
||||||
|
// no-op by default
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_AUTO_SHRINK
|
||||||
|
inline void shrinkJsonDocument(JsonDocument& doc) {
|
||||||
|
doc.shrinkToFit();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <template <typename> class TDeserializer, typename TDestination,
|
||||||
|
typename TReader, typename TOptions>
|
||||||
|
DeserializationError doDeserialize(TDestination&& dst, TReader reader,
|
||||||
|
TOptions options) {
|
||||||
|
auto data = VariantAttorney::getOrCreateData(dst);
|
||||||
|
if (!data)
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
auto resources = VariantAttorney::getResourceManager(dst);
|
||||||
|
dst.clear();
|
||||||
|
auto err = TDeserializer<TReader>(resources, reader)
|
||||||
|
.parse(*data, options.filter, options.nestingLimit);
|
||||||
|
shrinkJsonDocument(dst);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
template <typename> class TDeserializer, typename TDestination,
|
||||||
|
typename TStream, typename... Args,
|
||||||
|
enable_if_t< // issue #1897
|
||||||
|
!is_integral<typename first_or_void<Args...>::type>::value, int> = 0>
|
||||||
|
DeserializationError deserialize(TDestination&& dst, TStream&& input,
|
||||||
|
Args... args) {
|
||||||
|
return doDeserialize<TDeserializer>(
|
||||||
|
dst, makeReader(detail::forward<TStream>(input)),
|
||||||
|
makeDeserializationOptions(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <template <typename> class TDeserializer, typename TDestination,
|
||||||
|
typename TChar, typename Size, typename... Args,
|
||||||
|
enable_if_t<is_integral<Size>::value, int> = 0>
|
||||||
|
DeserializationError deserialize(TDestination&& dst, TChar* input,
|
||||||
|
Size inputSize, Args... args) {
|
||||||
|
return doDeserialize<TDeserializer>(dst, makeReader(input, size_t(inputSize)),
|
||||||
|
makeDeserializationOptions(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Array/ElementProxy.hpp>
|
||||||
|
#include <ArduinoJson/Memory/Allocator.hpp>
|
||||||
|
#include <ArduinoJson/Memory/ResourceManager.hpp>
|
||||||
|
#include <ArduinoJson/Object/JsonObject.hpp>
|
||||||
|
#include <ArduinoJson/Object/MemberProxy.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/utility.hpp>
|
||||||
|
#include <ArduinoJson/Variant/JsonVariantConst.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantTo.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// A JSON document.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/
|
||||||
|
class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
|
||||||
|
friend class detail::VariantAttorney;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit JsonDocument(Allocator* alloc = detail::DefaultAllocator::instance())
|
||||||
|
: resources_(alloc) {}
|
||||||
|
|
||||||
|
// Copy-constructor
|
||||||
|
JsonDocument(const JsonDocument& src) : JsonDocument(src.allocator()) {
|
||||||
|
set(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move-constructor
|
||||||
|
JsonDocument(JsonDocument&& src)
|
||||||
|
: JsonDocument(detail::DefaultAllocator::instance()) {
|
||||||
|
swap(*this, src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct from variant, array, or object
|
||||||
|
template <typename T,
|
||||||
|
detail::enable_if_t<detail::IsVariant<T>::value ||
|
||||||
|
detail::is_same<T, JsonArray>::value ||
|
||||||
|
detail::is_same<T, JsonArrayConst>::value ||
|
||||||
|
detail::is_same<T, JsonObject>::value ||
|
||||||
|
detail::is_same<T, JsonObjectConst>::value,
|
||||||
|
int> = 0>
|
||||||
|
JsonDocument(const T& src,
|
||||||
|
Allocator* alloc = detail::DefaultAllocator::instance())
|
||||||
|
: JsonDocument(alloc) {
|
||||||
|
set(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDocument& operator=(JsonDocument src) {
|
||||||
|
swap(*this, src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
JsonDocument& operator=(const T& src) {
|
||||||
|
set(src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Allocator* allocator() const {
|
||||||
|
return resources_.allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduces the capacity of the memory pool to match the current usage.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/shrinktofit/
|
||||||
|
void shrinkToFit() {
|
||||||
|
resources_.shrinkToFit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Casts the root to the specified type.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/as/
|
||||||
|
template <typename T>
|
||||||
|
T as() {
|
||||||
|
return getVariant().template as<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Casts the root to the specified type.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/as/
|
||||||
|
template <typename T>
|
||||||
|
T as() const {
|
||||||
|
return getVariant().template as<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empties the document and resets the memory pool
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/clear/
|
||||||
|
void clear() {
|
||||||
|
resources_.clear();
|
||||||
|
data_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the root is of the specified type.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/is/
|
||||||
|
template <typename T>
|
||||||
|
bool is() {
|
||||||
|
return getVariant().template is<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the root is of the specified type.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/is/
|
||||||
|
template <typename T>
|
||||||
|
bool is() const {
|
||||||
|
return getVariant().template is<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the root is null.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/isnull/
|
||||||
|
bool isNull() const {
|
||||||
|
return getVariant().isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns trues if the memory pool was too small.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/overflowed/
|
||||||
|
bool overflowed() const {
|
||||||
|
return resources_.overflowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the depth (nesting level) of the array.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/nesting/
|
||||||
|
size_t nesting() const {
|
||||||
|
return data_.nesting(&resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of elements in the root array or object.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/size/
|
||||||
|
size_t size() const {
|
||||||
|
return data_.size(&resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies the specified document.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/set/
|
||||||
|
bool set(const JsonDocument& src) {
|
||||||
|
return to<JsonVariant>().set(src.as<JsonVariantConst>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replaces the root with the specified value.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/set/
|
||||||
|
template <
|
||||||
|
typename T,
|
||||||
|
detail::enable_if_t<!detail::is_base_of<JsonDocument, T>::value, int> = 0>
|
||||||
|
bool set(const T& src) {
|
||||||
|
return to<JsonVariant>().set(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replaces the root with the specified value.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/set/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<!detail::is_const<TChar>::value, int> = 0>
|
||||||
|
bool set(TChar* src) {
|
||||||
|
return to<JsonVariant>().set(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clears the document and converts it to the specified type.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/to/
|
||||||
|
template <typename T>
|
||||||
|
typename detail::VariantTo<T>::type to() {
|
||||||
|
clear();
|
||||||
|
return getVariant().template to<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj["key"].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/containskey/
|
||||||
|
template <typename TChar>
|
||||||
|
ARDUINOJSON_DEPRECATED("use doc[\"key\"].is<T>() instead")
|
||||||
|
bool containsKey(TChar* key) const {
|
||||||
|
return data_.getMember(detail::adaptString(key), &resources_) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/containskey/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
ARDUINOJSON_DEPRECATED("use doc[key].is<T>() instead")
|
||||||
|
bool containsKey(const TString& key) const {
|
||||||
|
return data_.getMember(detail::adaptString(key), &resources_) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/containskey/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
ARDUINOJSON_DEPRECATED("use doc[key].is<T>() instead")
|
||||||
|
bool containsKey(const TVariant& key) const {
|
||||||
|
return containsKey(key.template as<const char*>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets a root object's member.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/subscript/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
detail::MemberProxy<JsonDocument&, detail::AdaptedString<TString>> operator[](
|
||||||
|
const TString& key) {
|
||||||
|
return {*this, detail::adaptString(key)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets a root object's member.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/subscript/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<detail::IsString<TChar*>::value &&
|
||||||
|
!detail::is_const<TChar>::value,
|
||||||
|
int> = 0>
|
||||||
|
detail::MemberProxy<JsonDocument&, detail::AdaptedString<TChar*>> operator[](
|
||||||
|
TChar* key) {
|
||||||
|
return {*this, detail::adaptString(key)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a root object's member.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/subscript/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
JsonVariantConst operator[](const TString& key) const {
|
||||||
|
return JsonVariantConst(
|
||||||
|
data_.getMember(detail::adaptString(key), &resources_), &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a root object's member.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/subscript/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<detail::IsString<TChar*>::value &&
|
||||||
|
!detail::is_const<TChar>::value,
|
||||||
|
int> = 0>
|
||||||
|
JsonVariantConst operator[](TChar* key) const {
|
||||||
|
return JsonVariantConst(
|
||||||
|
data_.getMember(detail::adaptString(key), &resources_), &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets a root array's element.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/subscript/
|
||||||
|
template <typename T,
|
||||||
|
detail::enable_if_t<detail::is_integral<T>::value, int> = 0>
|
||||||
|
detail::ElementProxy<JsonDocument&> operator[](T index) {
|
||||||
|
return {*this, size_t(index)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a root array's member.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/subscript/
|
||||||
|
JsonVariantConst operator[](size_t index) const {
|
||||||
|
return JsonVariantConst(data_.getElement(index, &resources_), &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets a root object's member.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/subscript/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
JsonVariantConst operator[](const TVariant& key) const {
|
||||||
|
if (key.template is<JsonString>())
|
||||||
|
return operator[](key.template as<JsonString>());
|
||||||
|
if (key.template is<size_t>())
|
||||||
|
return operator[](key.template as<size_t>());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a new (empty) element to the root array.
|
||||||
|
// Returns a reference to the new element.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/add/
|
||||||
|
template <typename T, detail::enable_if_t<
|
||||||
|
!detail::is_same<T, JsonVariant>::value, int> = 0>
|
||||||
|
T add() {
|
||||||
|
return add<JsonVariant>().to<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a new (null) element to the root array.
|
||||||
|
// Returns a reference to the new element.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/add/
|
||||||
|
template <typename T, detail::enable_if_t<
|
||||||
|
detail::is_same<T, JsonVariant>::value, int> = 0>
|
||||||
|
JsonVariant add() {
|
||||||
|
return JsonVariant(data_.addElement(&resources_), &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a value to the root array.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/add/
|
||||||
|
template <typename TValue>
|
||||||
|
bool add(const TValue& value) {
|
||||||
|
return data_.addValue(value, &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends a value to the root array.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/add/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<!detail::is_const<TChar>::value, int> = 0>
|
||||||
|
bool add(TChar* value) {
|
||||||
|
return data_.addValue(value, &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes an element of the root array.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/remove/
|
||||||
|
template <typename T,
|
||||||
|
detail::enable_if_t<detail::is_integral<T>::value, int> = 0>
|
||||||
|
void remove(T index) {
|
||||||
|
detail::VariantData::removeElement(getData(), size_t(index),
|
||||||
|
getResourceManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a member of the root object.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/remove/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<detail::IsString<TChar*>::value &&
|
||||||
|
!detail::is_const<TChar>::value,
|
||||||
|
int> = 0>
|
||||||
|
void remove(TChar* key) {
|
||||||
|
detail::VariantData::removeMember(getData(), detail::adaptString(key),
|
||||||
|
getResourceManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a member of the root object.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/remove/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
void remove(const TString& key) {
|
||||||
|
detail::VariantData::removeMember(getData(), detail::adaptString(key),
|
||||||
|
getResourceManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a member of the root object or an element of the root array.
|
||||||
|
// https://arduinojson.org/v7/api/jsondocument/remove/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
void remove(const TVariant& key) {
|
||||||
|
if (key.template is<const char*>())
|
||||||
|
remove(key.template as<const char*>());
|
||||||
|
if (key.template is<size_t>())
|
||||||
|
remove(key.template as<size_t>());
|
||||||
|
}
|
||||||
|
|
||||||
|
operator JsonVariant() {
|
||||||
|
return getVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
operator JsonVariantConst() const {
|
||||||
|
return getVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend void swap(JsonDocument& a, JsonDocument& b) {
|
||||||
|
swap(a.resources_, b.resources_);
|
||||||
|
swap_(a.data_, b.data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use add<JsonVariant>() instead
|
||||||
|
ARDUINOJSON_DEPRECATED("use add<JsonVariant>() instead")
|
||||||
|
JsonVariant add() {
|
||||||
|
return add<JsonVariant>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use add<JsonArray>() instead
|
||||||
|
ARDUINOJSON_DEPRECATED("use add<JsonArray>() instead")
|
||||||
|
JsonArray createNestedArray() {
|
||||||
|
return add<JsonArray>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use doc[key].to<JsonArray>() instead
|
||||||
|
template <typename TChar>
|
||||||
|
ARDUINOJSON_DEPRECATED("use doc[key].to<JsonArray>() instead")
|
||||||
|
JsonArray createNestedArray(TChar* key) {
|
||||||
|
return operator[](key).template to<JsonArray>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use doc[key].to<JsonArray>() instead
|
||||||
|
template <typename TString>
|
||||||
|
ARDUINOJSON_DEPRECATED("use doc[key].to<JsonArray>() instead")
|
||||||
|
JsonArray createNestedArray(const TString& key) {
|
||||||
|
return operator[](key).template to<JsonArray>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use add<JsonObject>() instead
|
||||||
|
ARDUINOJSON_DEPRECATED("use add<JsonObject>() instead")
|
||||||
|
JsonObject createNestedObject() {
|
||||||
|
return add<JsonObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use doc[key].to<JsonObject>() instead
|
||||||
|
template <typename TChar>
|
||||||
|
ARDUINOJSON_DEPRECATED("use doc[key].to<JsonObject>() instead")
|
||||||
|
JsonObject createNestedObject(TChar* key) {
|
||||||
|
return operator[](key).template to<JsonObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use doc[key].to<JsonObject>() instead
|
||||||
|
template <typename TString>
|
||||||
|
ARDUINOJSON_DEPRECATED("use doc[key].to<JsonObject>() instead")
|
||||||
|
JsonObject createNestedObject(const TString& key) {
|
||||||
|
return operator[](key).template to<JsonObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: always returns zero
|
||||||
|
ARDUINOJSON_DEPRECATED("always returns zero")
|
||||||
|
size_t memoryUsage() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JsonVariant getVariant() {
|
||||||
|
return JsonVariant(&data_, &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonVariantConst getVariant() const {
|
||||||
|
return JsonVariantConst(&data_, &resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::ResourceManager* getResourceManager() {
|
||||||
|
return &resources_;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::VariantData* getData() {
|
||||||
|
return &data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detail::VariantData* getData() const {
|
||||||
|
return &data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::VariantData* getOrCreateData() {
|
||||||
|
return &data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::ResourceManager resources_;
|
||||||
|
detail::VariantData data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void convertToJson(const JsonDocument& src, JsonVariant dst) {
|
||||||
|
dst.set(src.as<JsonVariantConst>());
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class EscapeSequence {
|
||||||
|
public:
|
||||||
|
// Optimized for code size on a 8-bit AVR
|
||||||
|
static char escapeChar(char c) {
|
||||||
|
const char* p = escapeTable(true);
|
||||||
|
while (p[0] && p[1] != c) {
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
return p[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized for code size on a 8-bit AVR
|
||||||
|
static char unescapeChar(char c) {
|
||||||
|
const char* p = escapeTable(false);
|
||||||
|
for (;;) {
|
||||||
|
if (p[0] == '\0')
|
||||||
|
return 0;
|
||||||
|
if (p[0] == c)
|
||||||
|
return p[1];
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const char* escapeTable(bool isSerializing) {
|
||||||
|
return &"//''\"\"\\\\b\bf\fn\nr\rt\t"[isSerializing ? 4 : 0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,720 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Deserialization/deserialize.hpp>
|
||||||
|
#include <ArduinoJson/Json/EscapeSequence.hpp>
|
||||||
|
#include <ArduinoJson/Json/Latch.hpp>
|
||||||
|
#include <ArduinoJson/Json/Utf16.hpp>
|
||||||
|
#include <ArduinoJson/Json/Utf8.hpp>
|
||||||
|
#include <ArduinoJson/Memory/ResourceManager.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/parseNumber.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/utility.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TReader>
|
||||||
|
class JsonDeserializer {
|
||||||
|
public:
|
||||||
|
JsonDeserializer(ResourceManager* resources, TReader reader)
|
||||||
|
: stringBuilder_(resources),
|
||||||
|
foundSomething_(false),
|
||||||
|
latch_(reader),
|
||||||
|
resources_(resources) {}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError parse(VariantData& variant, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
err = parseVariant(variant, filter, nestingLimit);
|
||||||
|
|
||||||
|
if (!err && latch_.last() != 0 && variant.isFloat()) {
|
||||||
|
// We don't detect trailing characters earlier, so we need to check now
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
char current() {
|
||||||
|
return latch_.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
void move() {
|
||||||
|
latch_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eat(char charToSkip) {
|
||||||
|
if (current() != charToSkip)
|
||||||
|
return false;
|
||||||
|
move();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError::Code parseVariant(
|
||||||
|
VariantData& variant, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
switch (current()) {
|
||||||
|
case '[':
|
||||||
|
if (filter.allowArray())
|
||||||
|
return parseArray(variant.toArray(), filter, nestingLimit);
|
||||||
|
else
|
||||||
|
return skipArray(nestingLimit);
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
if (filter.allowObject())
|
||||||
|
return parseObject(variant.toObject(), filter, nestingLimit);
|
||||||
|
else
|
||||||
|
return skipObject(nestingLimit);
|
||||||
|
|
||||||
|
case '\"':
|
||||||
|
case '\'':
|
||||||
|
if (filter.allowValue())
|
||||||
|
return parseStringValue(variant);
|
||||||
|
else
|
||||||
|
return skipQuotedString();
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
if (filter.allowValue())
|
||||||
|
variant.setBoolean(true);
|
||||||
|
return skipKeyword("true");
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
if (filter.allowValue())
|
||||||
|
variant.setBoolean(false);
|
||||||
|
return skipKeyword("false");
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
// the variant should already by null, except if the same object key was
|
||||||
|
// used twice, as in {"a":1,"a":null}
|
||||||
|
return skipKeyword("null");
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (filter.allowValue())
|
||||||
|
return parseNumericValue(variant);
|
||||||
|
else
|
||||||
|
return skipNumericValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipVariant(
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
switch (current()) {
|
||||||
|
case '[':
|
||||||
|
return skipArray(nestingLimit);
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
return skipObject(nestingLimit);
|
||||||
|
|
||||||
|
case '\"':
|
||||||
|
case '\'':
|
||||||
|
return skipQuotedString();
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
return skipKeyword("true");
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
return skipKeyword("false");
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
return skipKeyword("null");
|
||||||
|
|
||||||
|
default:
|
||||||
|
return skipNumericValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError::Code parseArray(
|
||||||
|
ArrayData& array, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
if (nestingLimit.reached())
|
||||||
|
return DeserializationError::TooDeep;
|
||||||
|
|
||||||
|
// Skip opening braket
|
||||||
|
ARDUINOJSON_ASSERT(current() == '[');
|
||||||
|
move();
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Empty array?
|
||||||
|
if (eat(']'))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
|
||||||
|
TFilter elementFilter = filter[0UL];
|
||||||
|
|
||||||
|
// Read each value
|
||||||
|
for (;;) {
|
||||||
|
if (elementFilter.allow()) {
|
||||||
|
// Allocate slot in array
|
||||||
|
VariantData* value = array.addElement(resources_);
|
||||||
|
if (!value)
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
// 1 - Parse value
|
||||||
|
err = parseVariant(*value, elementFilter, nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
} else {
|
||||||
|
err = skipVariant(nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 - Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// 3 - More values?
|
||||||
|
if (eat(']'))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
if (!eat(','))
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipArray(
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
if (nestingLimit.reached())
|
||||||
|
return DeserializationError::TooDeep;
|
||||||
|
|
||||||
|
// Skip opening braket
|
||||||
|
ARDUINOJSON_ASSERT(current() == '[');
|
||||||
|
move();
|
||||||
|
|
||||||
|
// Read each value
|
||||||
|
for (;;) {
|
||||||
|
// 1 - Skip value
|
||||||
|
err = skipVariant(nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// 2 - Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// 3 - More values?
|
||||||
|
if (eat(']'))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
if (!eat(','))
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError::Code parseObject(
|
||||||
|
ObjectData& object, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
if (nestingLimit.reached())
|
||||||
|
return DeserializationError::TooDeep;
|
||||||
|
|
||||||
|
// Skip opening brace
|
||||||
|
ARDUINOJSON_ASSERT(current() == '{');
|
||||||
|
move();
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Empty object?
|
||||||
|
if (eat('}'))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
|
||||||
|
// Read each key value pair
|
||||||
|
for (;;) {
|
||||||
|
// Parse key
|
||||||
|
err = parseKey();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Colon
|
||||||
|
if (!eat(':'))
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
|
||||||
|
JsonString key = stringBuilder_.str();
|
||||||
|
|
||||||
|
TFilter memberFilter = filter[key];
|
||||||
|
|
||||||
|
if (memberFilter.allow()) {
|
||||||
|
auto member = object.getMember(adaptString(key), resources_);
|
||||||
|
if (!member) {
|
||||||
|
auto keyVariant = object.addPair(&member, resources_);
|
||||||
|
if (!keyVariant)
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
stringBuilder_.save(keyVariant);
|
||||||
|
} else {
|
||||||
|
member->clear(resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse value
|
||||||
|
err = parseVariant(*member, memberFilter, nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
} else {
|
||||||
|
err = skipVariant(nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// More keys/values?
|
||||||
|
if (eat('}'))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
if (!eat(','))
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipObject(
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
if (nestingLimit.reached())
|
||||||
|
return DeserializationError::TooDeep;
|
||||||
|
|
||||||
|
// Skip opening brace
|
||||||
|
ARDUINOJSON_ASSERT(current() == '{');
|
||||||
|
move();
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Empty object?
|
||||||
|
if (eat('}'))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
|
||||||
|
// Read each key value pair
|
||||||
|
for (;;) {
|
||||||
|
// Skip key
|
||||||
|
err = skipKey();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Colon
|
||||||
|
if (!eat(':'))
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
|
||||||
|
// Skip value
|
||||||
|
err = skipVariant(nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// Skip spaces
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// More keys/values?
|
||||||
|
if (eat('}'))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
if (!eat(','))
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
|
||||||
|
err = skipSpacesAndComments();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code parseKey() {
|
||||||
|
stringBuilder_.startString();
|
||||||
|
if (isQuote(current())) {
|
||||||
|
return parseQuotedString();
|
||||||
|
} else {
|
||||||
|
return parseNonQuotedString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code parseStringValue(VariantData& variant) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
stringBuilder_.startString();
|
||||||
|
|
||||||
|
err = parseQuotedString();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
stringBuilder_.save(&variant);
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code parseQuotedString() {
|
||||||
|
#if ARDUINOJSON_DECODE_UNICODE
|
||||||
|
Utf16::Codepoint codepoint;
|
||||||
|
DeserializationError::Code err;
|
||||||
|
#endif
|
||||||
|
const char stopChar = current();
|
||||||
|
|
||||||
|
move();
|
||||||
|
for (;;) {
|
||||||
|
char c = current();
|
||||||
|
move();
|
||||||
|
if (c == stopChar)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
|
||||||
|
if (c == '\\') {
|
||||||
|
c = current();
|
||||||
|
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
|
||||||
|
if (c == 'u') {
|
||||||
|
#if ARDUINOJSON_DECODE_UNICODE
|
||||||
|
move();
|
||||||
|
uint16_t codeunit;
|
||||||
|
err = parseHex4(codeunit);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
if (codepoint.append(codeunit))
|
||||||
|
Utf8::encodeCodepoint(codepoint.value(), stringBuilder_);
|
||||||
|
#else
|
||||||
|
stringBuilder_.append('\\');
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace char
|
||||||
|
c = EscapeSequence::unescapeChar(c);
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
|
||||||
|
stringBuilder_.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stringBuilder_.isValid())
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code parseNonQuotedString() {
|
||||||
|
char c = current();
|
||||||
|
ARDUINOJSON_ASSERT(c);
|
||||||
|
|
||||||
|
if (canBeInNonQuotedString(c)) { // no quotes
|
||||||
|
do {
|
||||||
|
move();
|
||||||
|
stringBuilder_.append(c);
|
||||||
|
c = current();
|
||||||
|
} while (canBeInNonQuotedString(c));
|
||||||
|
} else {
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stringBuilder_.isValid())
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipKey() {
|
||||||
|
if (isQuote(current())) {
|
||||||
|
return skipQuotedString();
|
||||||
|
} else {
|
||||||
|
return skipNonQuotedString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipQuotedString() {
|
||||||
|
const char stopChar = current();
|
||||||
|
|
||||||
|
move();
|
||||||
|
for (;;) {
|
||||||
|
char c = current();
|
||||||
|
move();
|
||||||
|
if (c == stopChar)
|
||||||
|
break;
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
if (c == '\\') {
|
||||||
|
if (current() != '\0')
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipNonQuotedString() {
|
||||||
|
char c = current();
|
||||||
|
while (canBeInNonQuotedString(c)) {
|
||||||
|
move();
|
||||||
|
c = current();
|
||||||
|
}
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code parseNumericValue(VariantData& result) {
|
||||||
|
uint8_t n = 0;
|
||||||
|
|
||||||
|
char c = current();
|
||||||
|
while (canBeInNumber(c) && n < 63) {
|
||||||
|
move();
|
||||||
|
buffer_[n++] = c;
|
||||||
|
c = current();
|
||||||
|
}
|
||||||
|
buffer_[n] = 0;
|
||||||
|
|
||||||
|
auto number = parseNumber(buffer_);
|
||||||
|
switch (number.type()) {
|
||||||
|
case NumberType::UnsignedInteger:
|
||||||
|
if (result.setInteger(number.asUnsignedInteger(), resources_))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
else
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
case NumberType::SignedInteger:
|
||||||
|
if (result.setInteger(number.asSignedInteger(), resources_))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
else
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
case NumberType::Float:
|
||||||
|
if (result.setFloat(number.asFloat(), resources_))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
else
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
case NumberType::Double:
|
||||||
|
if (result.setFloat(number.asDouble(), resources_))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
else
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipNumericValue() {
|
||||||
|
char c = current();
|
||||||
|
while (canBeInNumber(c)) {
|
||||||
|
move();
|
||||||
|
c = current();
|
||||||
|
}
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code parseHex4(uint16_t& result) {
|
||||||
|
result = 0;
|
||||||
|
for (uint8_t i = 0; i < 4; ++i) {
|
||||||
|
char digit = current();
|
||||||
|
if (!digit)
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
uint8_t value = decodeHex(digit);
|
||||||
|
if (value > 0x0F)
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
result = uint16_t((result << 4) | value);
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isBetween(char c, char min, char max) {
|
||||||
|
return min <= c && c <= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool canBeInNumber(char c) {
|
||||||
|
return isBetween(c, '0', '9') || c == '+' || c == '-' || c == '.' ||
|
||||||
|
#if ARDUINOJSON_ENABLE_NAN || ARDUINOJSON_ENABLE_INFINITY
|
||||||
|
isBetween(c, 'A', 'Z') || isBetween(c, 'a', 'z');
|
||||||
|
#else
|
||||||
|
c == 'e' || c == 'E';
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool canBeInNonQuotedString(char c) {
|
||||||
|
return isBetween(c, '0', '9') || isBetween(c, '_', 'z') ||
|
||||||
|
isBetween(c, 'A', 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isQuote(char c) {
|
||||||
|
return c == '\'' || c == '\"';
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t decodeHex(char c) {
|
||||||
|
if (c < 'A')
|
||||||
|
return uint8_t(c - '0');
|
||||||
|
c = char(c & ~0x20); // uppercase
|
||||||
|
return uint8_t(c - 'A' + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipSpacesAndComments() {
|
||||||
|
for (;;) {
|
||||||
|
switch (current()) {
|
||||||
|
// end of string
|
||||||
|
case '\0':
|
||||||
|
return foundSomething_ ? DeserializationError::IncompleteInput
|
||||||
|
: DeserializationError::EmptyInput;
|
||||||
|
|
||||||
|
// spaces
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
move();
|
||||||
|
continue;
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_COMMENTS
|
||||||
|
// comments
|
||||||
|
case '/':
|
||||||
|
move(); // skip '/'
|
||||||
|
switch (current()) {
|
||||||
|
// block comment
|
||||||
|
case '*': {
|
||||||
|
move(); // skip '*'
|
||||||
|
bool wasStar = false;
|
||||||
|
for (;;) {
|
||||||
|
char c = current();
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
if (c == '/' && wasStar) {
|
||||||
|
move();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wasStar = c == '*';
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trailing comment
|
||||||
|
case '/':
|
||||||
|
// no need to skip "//"
|
||||||
|
for (;;) {
|
||||||
|
move();
|
||||||
|
char c = current();
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// not a comment, just a '/'
|
||||||
|
default:
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
foundSomething_ = true;
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipKeyword(const char* s) {
|
||||||
|
while (*s) {
|
||||||
|
char c = current();
|
||||||
|
if (c == '\0')
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
if (*s != c)
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
++s;
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder stringBuilder_;
|
||||||
|
bool foundSomething_;
|
||||||
|
Latch<TReader> latch_;
|
||||||
|
ResourceManager* resources_;
|
||||||
|
char buffer_[64]; // using a member instead of a local variable because it
|
||||||
|
// ended in the recursive path after compiler inlined the
|
||||||
|
// code
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// Parses a JSON input, filters, and puts the result in a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/json/deserializejson/
|
||||||
|
template <typename TDestination, typename... Args,
|
||||||
|
detail::enable_if_t<
|
||||||
|
detail::is_deserialize_destination<TDestination>::value, int> = 0>
|
||||||
|
inline DeserializationError deserializeJson(TDestination&& dst,
|
||||||
|
Args&&... args) {
|
||||||
|
using namespace detail;
|
||||||
|
return deserialize<JsonDeserializer>(detail::forward<TDestination>(dst),
|
||||||
|
detail::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a JSON input, filters, and puts the result in a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/json/deserializejson/
|
||||||
|
template <typename TDestination, typename TChar, typename... Args,
|
||||||
|
detail::enable_if_t<
|
||||||
|
detail::is_deserialize_destination<TDestination>::value, int> = 0>
|
||||||
|
inline DeserializationError deserializeJson(TDestination&& dst, TChar* input,
|
||||||
|
Args&&... args) {
|
||||||
|
using namespace detail;
|
||||||
|
return deserialize<JsonDeserializer>(detail::forward<TDestination>(dst),
|
||||||
|
input, detail::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Json/TextFormatter.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/measure.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/serialize.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantDataVisitor.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TWriter>
|
||||||
|
class JsonSerializer : public VariantDataVisitor<size_t> {
|
||||||
|
public:
|
||||||
|
static const bool producesText = true;
|
||||||
|
|
||||||
|
JsonSerializer(TWriter writer, const ResourceManager* resources)
|
||||||
|
: formatter_(writer), resources_(resources) {}
|
||||||
|
|
||||||
|
size_t visit(const ArrayData& array) {
|
||||||
|
write('[');
|
||||||
|
|
||||||
|
auto slotId = array.head();
|
||||||
|
|
||||||
|
while (slotId != NULL_SLOT) {
|
||||||
|
auto slot = resources_->getVariant(slotId);
|
||||||
|
|
||||||
|
slot->accept(*this, resources_);
|
||||||
|
|
||||||
|
slotId = slot->next();
|
||||||
|
|
||||||
|
if (slotId != NULL_SLOT)
|
||||||
|
write(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
write(']');
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(const ObjectData& object) {
|
||||||
|
write('{');
|
||||||
|
|
||||||
|
auto slotId = object.head();
|
||||||
|
|
||||||
|
bool isKey = true;
|
||||||
|
|
||||||
|
while (slotId != NULL_SLOT) {
|
||||||
|
auto slot = resources_->getVariant(slotId);
|
||||||
|
slot->accept(*this, resources_);
|
||||||
|
|
||||||
|
slotId = slot->next();
|
||||||
|
|
||||||
|
if (slotId != NULL_SLOT)
|
||||||
|
write(isKey ? ':' : ',');
|
||||||
|
|
||||||
|
isKey = !isKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
write('}');
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
enable_if_t<is_floating_point<T>::value, size_t> visit(T value) {
|
||||||
|
formatter_.writeFloat(value);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(const char* value) {
|
||||||
|
formatter_.writeString(value);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(JsonString value) {
|
||||||
|
formatter_.writeString(value.c_str(), value.size());
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(RawString value) {
|
||||||
|
formatter_.writeRaw(value.data(), value.size());
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(JsonInteger value) {
|
||||||
|
formatter_.writeInteger(value);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(JsonUInt value) {
|
||||||
|
formatter_.writeInteger(value);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(bool value) {
|
||||||
|
formatter_.writeBoolean(value);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(nullptr_t) {
|
||||||
|
formatter_.writeRaw("null");
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t bytesWritten() const {
|
||||||
|
return formatter_.bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(char c) {
|
||||||
|
formatter_.writeRaw(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const char* s) {
|
||||||
|
formatter_.writeRaw(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TextFormatter<TWriter> formatter_;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// Produces a minified JSON document.
|
||||||
|
// https://arduinojson.org/v7/api/json/serializejson/
|
||||||
|
template <
|
||||||
|
typename TDestination,
|
||||||
|
detail::enable_if_t<!detail::is_pointer<TDestination>::value, int> = 0>
|
||||||
|
size_t serializeJson(JsonVariantConst source, TDestination& destination) {
|
||||||
|
using namespace detail;
|
||||||
|
return serialize<JsonSerializer>(source, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produces a minified JSON document.
|
||||||
|
// https://arduinojson.org/v7/api/json/serializejson/
|
||||||
|
inline size_t serializeJson(JsonVariantConst source, void* buffer,
|
||||||
|
size_t bufferSize) {
|
||||||
|
using namespace detail;
|
||||||
|
return serialize<JsonSerializer>(source, buffer, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the length of the document that serializeJson() produces.
|
||||||
|
// https://arduinojson.org/v7/api/json/measurejson/
|
||||||
|
inline size_t measureJson(JsonVariantConst source) {
|
||||||
|
using namespace detail;
|
||||||
|
return measure<JsonSerializer>(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_STD_STREAM
|
||||||
|
template <typename T,
|
||||||
|
detail::enable_if_t<
|
||||||
|
detail::is_convertible<T, JsonVariantConst>::value, int> = 0>
|
||||||
|
inline std::ostream& operator<<(std::ostream& os, const T& source) {
|
||||||
|
serializeJson(source, os);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TReader>
|
||||||
|
class Latch {
|
||||||
|
public:
|
||||||
|
Latch(TReader reader) : reader_(reader), loaded_(false) {
|
||||||
|
#if ARDUINOJSON_DEBUG
|
||||||
|
ended_ = false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
loaded_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int last() const {
|
||||||
|
return current_;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE char current() {
|
||||||
|
if (!loaded_) {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
return current_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load() {
|
||||||
|
ARDUINOJSON_ASSERT(!ended_);
|
||||||
|
int c = reader_.read();
|
||||||
|
#if ARDUINOJSON_DEBUG
|
||||||
|
if (c <= 0)
|
||||||
|
ended_ = true;
|
||||||
|
#endif
|
||||||
|
current_ = static_cast<char>(c > 0 ? c : 0);
|
||||||
|
loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TReader reader_;
|
||||||
|
char current_; // NOLINT(clang-analyzer-optin.cplusplus.UninitializedObject)
|
||||||
|
// Not initialized in constructor (+10 bytes on AVR)
|
||||||
|
bool loaded_;
|
||||||
|
#if ARDUINOJSON_DEBUG
|
||||||
|
bool ended_;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include <ArduinoJson/Json/JsonSerializer.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/measure.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/serialize.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TWriter>
|
||||||
|
class PrettyJsonSerializer : public JsonSerializer<TWriter> {
|
||||||
|
using base = JsonSerializer<TWriter>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PrettyJsonSerializer(TWriter writer, const ResourceManager* resources)
|
||||||
|
: base(writer, resources), nesting_(0) {}
|
||||||
|
|
||||||
|
size_t visit(const ArrayData& array) {
|
||||||
|
auto it = array.createIterator(base::resources_);
|
||||||
|
if (!it.done()) {
|
||||||
|
base::write("[\r\n");
|
||||||
|
nesting_++;
|
||||||
|
while (!it.done()) {
|
||||||
|
indent();
|
||||||
|
it->accept(*this, base::resources_);
|
||||||
|
|
||||||
|
it.next(base::resources_);
|
||||||
|
base::write(it.done() ? "\r\n" : ",\r\n");
|
||||||
|
}
|
||||||
|
nesting_--;
|
||||||
|
indent();
|
||||||
|
base::write("]");
|
||||||
|
} else {
|
||||||
|
base::write("[]");
|
||||||
|
}
|
||||||
|
return this->bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(const ObjectData& object) {
|
||||||
|
auto it = object.createIterator(base::resources_);
|
||||||
|
if (!it.done()) {
|
||||||
|
base::write("{\r\n");
|
||||||
|
nesting_++;
|
||||||
|
bool isKey = true;
|
||||||
|
while (!it.done()) {
|
||||||
|
if (isKey)
|
||||||
|
indent();
|
||||||
|
it->accept(*this, base::resources_);
|
||||||
|
it.next(base::resources_);
|
||||||
|
if (isKey)
|
||||||
|
base::write(": ");
|
||||||
|
else
|
||||||
|
base::write(it.done() ? "\r\n" : ",\r\n");
|
||||||
|
isKey = !isKey;
|
||||||
|
}
|
||||||
|
nesting_--;
|
||||||
|
indent();
|
||||||
|
base::write("}");
|
||||||
|
} else {
|
||||||
|
base::write("{}");
|
||||||
|
}
|
||||||
|
return this->bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
using base::visit;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void indent() {
|
||||||
|
for (uint8_t i = 0; i < nesting_; i++)
|
||||||
|
base::write(ARDUINOJSON_TAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t nesting_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// Produces JsonDocument to create a prettified JSON document.
|
||||||
|
// https://arduinojson.org/v7/api/json/serializejsonpretty/
|
||||||
|
template <
|
||||||
|
typename TDestination,
|
||||||
|
detail::enable_if_t<!detail::is_pointer<TDestination>::value, int> = 0>
|
||||||
|
inline size_t serializeJsonPretty(JsonVariantConst source,
|
||||||
|
TDestination& destination) {
|
||||||
|
using namespace ArduinoJson::detail;
|
||||||
|
return serialize<PrettyJsonSerializer>(source, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produces JsonDocument to create a prettified JSON document.
|
||||||
|
// https://arduinojson.org/v7/api/json/serializejsonpretty/
|
||||||
|
inline size_t serializeJsonPretty(JsonVariantConst source, void* buffer,
|
||||||
|
size_t bufferSize) {
|
||||||
|
using namespace ArduinoJson::detail;
|
||||||
|
return serialize<PrettyJsonSerializer>(source, buffer, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the length of the document that serializeJsonPretty() produces.
|
||||||
|
// https://arduinojson.org/v7/api/json/measurejsonpretty/
|
||||||
|
inline size_t measureJsonPretty(JsonVariantConst source) {
|
||||||
|
using namespace ArduinoJson::detail;
|
||||||
|
return measure<PrettyJsonSerializer>(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h> // for strlen
|
||||||
|
|
||||||
|
#include <ArduinoJson/Json/EscapeSequence.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/FloatParts.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/JsonInteger.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/attributes.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/CountingDecorator.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TWriter>
|
||||||
|
class TextFormatter {
|
||||||
|
public:
|
||||||
|
explicit TextFormatter(TWriter writer) : writer_(writer) {}
|
||||||
|
|
||||||
|
TextFormatter& operator=(const TextFormatter&) = delete;
|
||||||
|
|
||||||
|
// Returns the number of bytes sent to the TWriter implementation.
|
||||||
|
size_t bytesWritten() const {
|
||||||
|
return writer_.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeBoolean(bool value) {
|
||||||
|
if (value)
|
||||||
|
writeRaw("true");
|
||||||
|
else
|
||||||
|
writeRaw("false");
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeString(const char* value) {
|
||||||
|
ARDUINOJSON_ASSERT(value != NULL);
|
||||||
|
writeRaw('\"');
|
||||||
|
while (*value)
|
||||||
|
writeChar(*value++);
|
||||||
|
writeRaw('\"');
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeString(const char* value, size_t n) {
|
||||||
|
ARDUINOJSON_ASSERT(value != NULL);
|
||||||
|
writeRaw('\"');
|
||||||
|
while (n--)
|
||||||
|
writeChar(*value++);
|
||||||
|
writeRaw('\"');
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeChar(char c) {
|
||||||
|
char specialChar = EscapeSequence::escapeChar(c);
|
||||||
|
if (specialChar) {
|
||||||
|
writeRaw('\\');
|
||||||
|
writeRaw(specialChar);
|
||||||
|
} else if (c) {
|
||||||
|
writeRaw(c);
|
||||||
|
} else {
|
||||||
|
writeRaw("\\u0000");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void writeFloat(T value) {
|
||||||
|
writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeFloat(JsonFloat value, int8_t decimalPlaces) {
|
||||||
|
if (isnan(value))
|
||||||
|
return writeRaw(ARDUINOJSON_ENABLE_NAN ? "NaN" : "null");
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_INFINITY
|
||||||
|
if (value < 0.0) {
|
||||||
|
writeRaw('-');
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isinf(value))
|
||||||
|
return writeRaw("Infinity");
|
||||||
|
#else
|
||||||
|
if (isinf(value))
|
||||||
|
return writeRaw("null");
|
||||||
|
|
||||||
|
if (value < 0.0) {
|
||||||
|
writeRaw('-');
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto parts = decomposeFloat(value, decimalPlaces);
|
||||||
|
|
||||||
|
writeInteger(parts.integral);
|
||||||
|
if (parts.decimalPlaces)
|
||||||
|
writeDecimals(parts.decimal, parts.decimalPlaces);
|
||||||
|
|
||||||
|
if (parts.exponent) {
|
||||||
|
writeRaw('e');
|
||||||
|
writeInteger(parts.exponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
enable_if_t<is_signed<T>::value> writeInteger(T value) {
|
||||||
|
using unsigned_type = make_unsigned_t<T>;
|
||||||
|
unsigned_type unsigned_value;
|
||||||
|
if (value < 0) {
|
||||||
|
writeRaw('-');
|
||||||
|
unsigned_value = unsigned_type(unsigned_type(~value) + 1);
|
||||||
|
} else {
|
||||||
|
unsigned_value = unsigned_type(value);
|
||||||
|
}
|
||||||
|
writeInteger(unsigned_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
enable_if_t<is_unsigned<T>::value> writeInteger(T value) {
|
||||||
|
char buffer[22];
|
||||||
|
char* end = buffer + sizeof(buffer);
|
||||||
|
char* begin = end;
|
||||||
|
|
||||||
|
// write the string in reverse order
|
||||||
|
do {
|
||||||
|
*--begin = char(value % 10 + '0');
|
||||||
|
value = T(value / 10);
|
||||||
|
} while (value);
|
||||||
|
|
||||||
|
// and dump it in the right order
|
||||||
|
writeRaw(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeDecimals(uint32_t value, int8_t width) {
|
||||||
|
// buffer should be big enough for all digits and the dot
|
||||||
|
char buffer[16];
|
||||||
|
char* end = buffer + sizeof(buffer);
|
||||||
|
char* begin = end;
|
||||||
|
|
||||||
|
// write the string in reverse order
|
||||||
|
while (width--) {
|
||||||
|
*--begin = char(value % 10 + '0');
|
||||||
|
value /= 10;
|
||||||
|
}
|
||||||
|
*--begin = '.';
|
||||||
|
|
||||||
|
// and dump it in the right order
|
||||||
|
writeRaw(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeRaw(const char* s) {
|
||||||
|
writer_.write(reinterpret_cast<const uint8_t*>(s), strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeRaw(const char* s, size_t n) {
|
||||||
|
writer_.write(reinterpret_cast<const uint8_t*>(s), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeRaw(const char* begin, const char* end) {
|
||||||
|
writer_.write(reinterpret_cast<const uint8_t*>(begin),
|
||||||
|
static_cast<size_t>(end - begin));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
void writeRaw(const char (&s)[N]) {
|
||||||
|
writer_.write(reinterpret_cast<const uint8_t*>(s), N - 1);
|
||||||
|
}
|
||||||
|
void writeRaw(char c) {
|
||||||
|
writer_.write(static_cast<uint8_t>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CountingDecorator<TWriter> writer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
#include <stdint.h> // uint16_t, uint32_t
|
||||||
|
|
||||||
|
// The high surrogate may be uninitialized if the pair is invalid,
|
||||||
|
// we choose to ignore the problem to reduce the size of the code
|
||||||
|
// Garbage in => Garbage out
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
# if __GNUC__ >= 7
|
||||||
|
# pragma GCC diagnostic push
|
||||||
|
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
namespace Utf16 {
|
||||||
|
inline bool isHighSurrogate(uint16_t codeunit) {
|
||||||
|
return codeunit >= 0xD800 && codeunit < 0xDC00;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool isLowSurrogate(uint16_t codeunit) {
|
||||||
|
return codeunit >= 0xDC00 && codeunit < 0xE000;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Codepoint {
|
||||||
|
public:
|
||||||
|
Codepoint() : highSurrogate_(0), codepoint_(0) {}
|
||||||
|
|
||||||
|
bool append(uint16_t codeunit) {
|
||||||
|
if (isHighSurrogate(codeunit)) {
|
||||||
|
highSurrogate_ = codeunit & 0x3FF;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLowSurrogate(codeunit)) {
|
||||||
|
codepoint_ =
|
||||||
|
uint32_t(0x10000 + ((highSurrogate_ << 10) | (codeunit & 0x3FF)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
codepoint_ = codeunit;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t value() const {
|
||||||
|
return codepoint_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t highSurrogate_;
|
||||||
|
uint32_t codepoint_;
|
||||||
|
};
|
||||||
|
} // namespace Utf16
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
# if __GNUC__ >= 8
|
||||||
|
# pragma GCC diagnostic pop
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
namespace Utf8 {
|
||||||
|
template <typename TStringBuilder>
|
||||||
|
inline void encodeCodepoint(uint32_t codepoint32, TStringBuilder& str) {
|
||||||
|
// this function was optimize for code size on AVR
|
||||||
|
|
||||||
|
if (codepoint32 < 0x80) {
|
||||||
|
str.append(char(codepoint32));
|
||||||
|
} else {
|
||||||
|
// a buffer to store the string in reverse
|
||||||
|
char buf[5];
|
||||||
|
char* p = buf;
|
||||||
|
|
||||||
|
*(p++) = 0;
|
||||||
|
*(p++) = char((codepoint32 | 0x80) & 0xBF);
|
||||||
|
uint16_t codepoint16 = uint16_t(codepoint32 >> 6);
|
||||||
|
if (codepoint16 < 0x20) { // 0x800
|
||||||
|
*(p++) = char(codepoint16 | 0xC0);
|
||||||
|
} else {
|
||||||
|
*(p++) = char((codepoint16 | 0x80) & 0xBF);
|
||||||
|
codepoint16 = uint16_t(codepoint16 >> 6);
|
||||||
|
if (codepoint16 < 0x10) { // 0x10000
|
||||||
|
*(p++) = char(codepoint16 | 0xE0);
|
||||||
|
} else {
|
||||||
|
*(p++) = char((codepoint16 | 0x80) & 0xBF);
|
||||||
|
codepoint16 = uint16_t(codepoint16 >> 6);
|
||||||
|
*(p++) = char(codepoint16 | 0xF0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*(--p)) {
|
||||||
|
str.append(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Utf8
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
#include <stddef.h> // size_t
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_ALIGNMENT
|
||||||
|
|
||||||
|
inline bool isAligned(size_t value) {
|
||||||
|
const size_t mask = sizeof(void*) - 1;
|
||||||
|
size_t addr = value;
|
||||||
|
return (addr & mask) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t addPadding(size_t bytes) {
|
||||||
|
const size_t mask = sizeof(void*) - 1;
|
||||||
|
return (bytes + mask) & ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t bytes>
|
||||||
|
struct AddPadding {
|
||||||
|
static const size_t mask = sizeof(void*) - 1;
|
||||||
|
static const size_t value = (bytes + mask) & ~mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
inline bool isAligned(size_t) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t addPadding(size_t bytes) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t bytes>
|
||||||
|
struct AddPadding {
|
||||||
|
static const size_t value = bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline bool isAligned(T* ptr) {
|
||||||
|
return isAligned(reinterpret_cast<size_t>(ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T* addPadding(T* p) {
|
||||||
|
size_t address = addPadding(reinterpret_cast<size_t>(p));
|
||||||
|
return reinterpret_cast<T*>(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
#include <stdlib.h> // malloc, free
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class Allocator {
|
||||||
|
public:
|
||||||
|
virtual void* allocate(size_t size) = 0;
|
||||||
|
virtual void deallocate(void* ptr) = 0;
|
||||||
|
virtual void* reallocate(void* ptr, size_t new_size) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
~Allocator() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
class DefaultAllocator : public Allocator {
|
||||||
|
public:
|
||||||
|
void* allocate(size_t size) override {
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(void* ptr) override {
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* reallocate(void* ptr, size_t new_size) override {
|
||||||
|
return realloc(ptr, new_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Allocator* instance() {
|
||||||
|
static DefaultAllocator allocator;
|
||||||
|
return &allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DefaultAllocator() = default;
|
||||||
|
~DefaultAllocator() = default;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/Allocator.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/integer.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
using SlotId = uint_t<ARDUINOJSON_SLOT_ID_SIZE * 8>;
|
||||||
|
using SlotCount = SlotId;
|
||||||
|
const SlotId NULL_SLOT = SlotId(-1);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Slot {
|
||||||
|
public:
|
||||||
|
Slot() : ptr_(nullptr), id_(NULL_SLOT) {}
|
||||||
|
Slot(T* p, SlotId id) : ptr_(p), id_(id) {
|
||||||
|
ARDUINOJSON_ASSERT((p == nullptr) == (id == NULL_SLOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return ptr_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotId id() const {
|
||||||
|
return id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* ptr() const {
|
||||||
|
return ptr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* operator->() const {
|
||||||
|
ARDUINOJSON_ASSERT(ptr_ != nullptr);
|
||||||
|
return ptr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T* ptr_;
|
||||||
|
SlotId id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class MemoryPool {
|
||||||
|
public:
|
||||||
|
void create(SlotCount cap, Allocator* allocator) {
|
||||||
|
ARDUINOJSON_ASSERT(cap > 0);
|
||||||
|
slots_ = reinterpret_cast<T*>(allocator->allocate(slotsToBytes(cap)));
|
||||||
|
capacity_ = slots_ ? cap : 0;
|
||||||
|
usage_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(Allocator* allocator) {
|
||||||
|
if (slots_)
|
||||||
|
allocator->deallocate(slots_);
|
||||||
|
slots_ = nullptr;
|
||||||
|
capacity_ = 0;
|
||||||
|
usage_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot<T> allocSlot() {
|
||||||
|
if (!slots_)
|
||||||
|
return {};
|
||||||
|
if (usage_ >= capacity_)
|
||||||
|
return {};
|
||||||
|
auto index = usage_++;
|
||||||
|
return {slots_ + index, SlotId(index)};
|
||||||
|
}
|
||||||
|
|
||||||
|
T* getSlot(SlotId id) const {
|
||||||
|
ARDUINOJSON_ASSERT(id < usage_);
|
||||||
|
return slots_ + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
usage_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shrinkToFit(Allocator* allocator) {
|
||||||
|
auto newSlots = reinterpret_cast<T*>(
|
||||||
|
allocator->reallocate(slots_, slotsToBytes(usage_)));
|
||||||
|
if (newSlots) {
|
||||||
|
slots_ = newSlots;
|
||||||
|
capacity_ = usage_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotCount usage() const {
|
||||||
|
return usage_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SlotCount bytesToSlots(size_t n) {
|
||||||
|
return static_cast<SlotCount>(n / sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t slotsToBytes(SlotCount n) {
|
||||||
|
return n * sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SlotCount capacity_;
|
||||||
|
SlotCount usage_;
|
||||||
|
T* slots_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/MemoryPool.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/utility.hpp>
|
||||||
|
|
||||||
|
#include <string.h> // memcpy
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
using PoolCount = SlotId;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class MemoryPoolList {
|
||||||
|
struct FreeSlot {
|
||||||
|
SlotId next;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(FreeSlot) <= sizeof(T), "T is too small");
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Pool = MemoryPool<T>;
|
||||||
|
|
||||||
|
MemoryPoolList() = default;
|
||||||
|
|
||||||
|
~MemoryPoolList() {
|
||||||
|
ARDUINOJSON_ASSERT(count_ == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend void swap(MemoryPoolList& a, MemoryPoolList& b) {
|
||||||
|
bool aUsedPreallocated = a.pools_ == a.preallocatedPools_;
|
||||||
|
bool bUsedPreallocated = b.pools_ == b.preallocatedPools_;
|
||||||
|
|
||||||
|
// Who is using preallocated pools?
|
||||||
|
if (aUsedPreallocated && bUsedPreallocated) {
|
||||||
|
// both of us => swap preallocated pools
|
||||||
|
for (PoolCount i = 0; i < ARDUINOJSON_INITIAL_POOL_COUNT; i++)
|
||||||
|
swap_(a.preallocatedPools_[i], b.preallocatedPools_[i]);
|
||||||
|
} else if (bUsedPreallocated) {
|
||||||
|
// only b => copy b's preallocated pools and give him a's pointer
|
||||||
|
for (PoolCount i = 0; i < b.count_; i++)
|
||||||
|
a.preallocatedPools_[i] = b.preallocatedPools_[i];
|
||||||
|
b.pools_ = a.pools_;
|
||||||
|
a.pools_ = a.preallocatedPools_;
|
||||||
|
} else if (aUsedPreallocated) {
|
||||||
|
// only a => copy a's preallocated pools and give him b's pointer
|
||||||
|
for (PoolCount i = 0; i < a.count_; i++)
|
||||||
|
b.preallocatedPools_[i] = a.preallocatedPools_[i];
|
||||||
|
a.pools_ = b.pools_;
|
||||||
|
b.pools_ = b.preallocatedPools_;
|
||||||
|
} else {
|
||||||
|
// neither => swap pointers
|
||||||
|
swap_(a.pools_, b.pools_);
|
||||||
|
}
|
||||||
|
|
||||||
|
swap_(a.count_, b.count_);
|
||||||
|
swap_(a.capacity_, b.capacity_);
|
||||||
|
swap_(a.freeList_, b.freeList_);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryPoolList& operator=(MemoryPoolList&& src) {
|
||||||
|
ARDUINOJSON_ASSERT(count_ == 0);
|
||||||
|
if (src.pools_ == src.preallocatedPools_) {
|
||||||
|
memcpy(preallocatedPools_, src.preallocatedPools_,
|
||||||
|
sizeof(preallocatedPools_));
|
||||||
|
pools_ = preallocatedPools_;
|
||||||
|
} else {
|
||||||
|
pools_ = src.pools_;
|
||||||
|
src.pools_ = nullptr;
|
||||||
|
}
|
||||||
|
count_ = src.count_;
|
||||||
|
capacity_ = src.capacity_;
|
||||||
|
src.count_ = 0;
|
||||||
|
src.capacity_ = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot<T> allocSlot(Allocator* allocator) {
|
||||||
|
// try to allocate from free list
|
||||||
|
if (freeList_ != NULL_SLOT) {
|
||||||
|
return allocFromFreeList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to allocate from last pool (other pools are full)
|
||||||
|
if (count_) {
|
||||||
|
auto slot = allocFromLastPool();
|
||||||
|
if (slot)
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new pool and try again
|
||||||
|
auto pool = addPool(allocator);
|
||||||
|
if (!pool)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return allocFromLastPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeSlot(Slot<T> slot) {
|
||||||
|
reinterpret_cast<FreeSlot*>(slot.ptr())->next = freeList_;
|
||||||
|
freeList_ = slot.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
T* getSlot(SlotId id) const {
|
||||||
|
if (id == NULL_SLOT)
|
||||||
|
return nullptr;
|
||||||
|
auto poolIndex = SlotId(id / ARDUINOJSON_POOL_CAPACITY);
|
||||||
|
auto indexInPool = SlotId(id % ARDUINOJSON_POOL_CAPACITY);
|
||||||
|
ARDUINOJSON_ASSERT(poolIndex < count_);
|
||||||
|
return pools_[poolIndex].getSlot(indexInPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear(Allocator* allocator) {
|
||||||
|
for (PoolCount i = 0; i < count_; i++)
|
||||||
|
pools_[i].destroy(allocator);
|
||||||
|
count_ = 0;
|
||||||
|
freeList_ = NULL_SLOT;
|
||||||
|
if (pools_ != preallocatedPools_) {
|
||||||
|
allocator->deallocate(pools_);
|
||||||
|
pools_ = preallocatedPools_;
|
||||||
|
capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotCount usage() const {
|
||||||
|
SlotCount total = 0;
|
||||||
|
for (PoolCount i = 0; i < count_; i++)
|
||||||
|
total = SlotCount(total + pools_[i].usage());
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return Pool::slotsToBytes(usage());
|
||||||
|
}
|
||||||
|
|
||||||
|
void shrinkToFit(Allocator* allocator) {
|
||||||
|
if (count_ > 0)
|
||||||
|
pools_[count_ - 1].shrinkToFit(allocator);
|
||||||
|
if (pools_ != preallocatedPools_ && count_ != capacity_) {
|
||||||
|
pools_ = static_cast<Pool*>(
|
||||||
|
allocator->reallocate(pools_, count_ * sizeof(Pool)));
|
||||||
|
ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail
|
||||||
|
capacity_ = count_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Slot<T> allocFromFreeList() {
|
||||||
|
ARDUINOJSON_ASSERT(freeList_ != NULL_SLOT);
|
||||||
|
auto id = freeList_;
|
||||||
|
auto slot = getSlot(freeList_);
|
||||||
|
freeList_ = reinterpret_cast<FreeSlot*>(slot)->next;
|
||||||
|
return {slot, id};
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot<T> allocFromLastPool() {
|
||||||
|
ARDUINOJSON_ASSERT(count_ > 0);
|
||||||
|
auto poolIndex = SlotId(count_ - 1);
|
||||||
|
auto slot = pools_[poolIndex].allocSlot();
|
||||||
|
if (!slot)
|
||||||
|
return {};
|
||||||
|
return {slot.ptr(),
|
||||||
|
SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())};
|
||||||
|
}
|
||||||
|
|
||||||
|
Pool* addPool(Allocator* allocator) {
|
||||||
|
if (count_ == capacity_ && !increaseCapacity(allocator))
|
||||||
|
return nullptr;
|
||||||
|
auto pool = &pools_[count_++];
|
||||||
|
SlotCount poolCapacity = ARDUINOJSON_POOL_CAPACITY;
|
||||||
|
if (count_ == maxPools) // last pool is smaller because of NULL_SLOT
|
||||||
|
poolCapacity--;
|
||||||
|
pool->create(poolCapacity, allocator);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool increaseCapacity(Allocator* allocator) {
|
||||||
|
if (capacity_ == maxPools)
|
||||||
|
return false;
|
||||||
|
void* newPools;
|
||||||
|
auto newCapacity = PoolCount(capacity_ * 2);
|
||||||
|
|
||||||
|
if (pools_ == preallocatedPools_) {
|
||||||
|
newPools = allocator->allocate(newCapacity * sizeof(Pool));
|
||||||
|
if (!newPools)
|
||||||
|
return false;
|
||||||
|
memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_));
|
||||||
|
} else {
|
||||||
|
newPools = allocator->reallocate(pools_, newCapacity * sizeof(Pool));
|
||||||
|
if (!newPools)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pools_ = static_cast<Pool*>(newPools);
|
||||||
|
capacity_ = newCapacity;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT];
|
||||||
|
Pool* pools_ = preallocatedPools_;
|
||||||
|
PoolCount count_ = 0;
|
||||||
|
PoolCount capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT;
|
||||||
|
SlotId freeList_ = NULL_SLOT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const PoolCount maxPools =
|
||||||
|
PoolCount(NULL_SLOT / ARDUINOJSON_POOL_CAPACITY + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/Allocator.hpp>
|
||||||
|
#include <ArduinoJson/Memory/MemoryPoolList.hpp>
|
||||||
|
#include <ArduinoJson/Memory/StringPool.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/utility.hpp>
|
||||||
|
#include <ArduinoJson/Strings/StringAdapters.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class VariantData;
|
||||||
|
class VariantWithId;
|
||||||
|
|
||||||
|
class ResourceManager {
|
||||||
|
union SlotData {
|
||||||
|
VariantData variant;
|
||||||
|
#if ARDUINOJSON_USE_EXTENSIONS
|
||||||
|
VariantExtension extension;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr static size_t slotSize = sizeof(SlotData);
|
||||||
|
|
||||||
|
ResourceManager(Allocator* allocator = DefaultAllocator::instance())
|
||||||
|
: allocator_(allocator), overflowed_(false) {}
|
||||||
|
|
||||||
|
~ResourceManager() {
|
||||||
|
stringPool_.clear(allocator_);
|
||||||
|
variantPools_.clear(allocator_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager(const ResourceManager&) = delete;
|
||||||
|
ResourceManager& operator=(const ResourceManager& src) = delete;
|
||||||
|
|
||||||
|
friend void swap(ResourceManager& a, ResourceManager& b) {
|
||||||
|
swap(a.stringPool_, b.stringPool_);
|
||||||
|
swap(a.variantPools_, b.variantPools_);
|
||||||
|
swap_(a.allocator_, b.allocator_);
|
||||||
|
swap_(a.overflowed_, b.overflowed_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Allocator* allocator() const {
|
||||||
|
return allocator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return variantPools_.size() + stringPool_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool overflowed() const {
|
||||||
|
return overflowed_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot<VariantData> allocVariant();
|
||||||
|
void freeVariant(Slot<VariantData> slot);
|
||||||
|
VariantData* getVariant(SlotId id) const;
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_EXTENSIONS
|
||||||
|
Slot<VariantExtension> allocExtension();
|
||||||
|
void freeExtension(SlotId slot);
|
||||||
|
VariantExtension* getExtension(SlotId id) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
StringNode* saveString(TAdaptedString str) {
|
||||||
|
if (str.isNull())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto node = stringPool_.add(str, allocator_);
|
||||||
|
if (!node)
|
||||||
|
overflowed_ = true;
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveString(StringNode* node) {
|
||||||
|
stringPool_.add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
StringNode* getString(const TAdaptedString& str) const {
|
||||||
|
return stringPool_.get(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringNode* createString(size_t length) {
|
||||||
|
auto node = StringNode::create(length, allocator_);
|
||||||
|
if (!node)
|
||||||
|
overflowed_ = true;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringNode* resizeString(StringNode* node, size_t length) {
|
||||||
|
node = StringNode::resize(node, length, allocator_);
|
||||||
|
if (!node)
|
||||||
|
overflowed_ = true;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyString(StringNode* node) {
|
||||||
|
StringNode::destroy(node, allocator_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dereferenceString(const char* s) {
|
||||||
|
stringPool_.dereference(s, allocator_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
variantPools_.clear(allocator_);
|
||||||
|
overflowed_ = false;
|
||||||
|
stringPool_.clear(allocator_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shrinkToFit() {
|
||||||
|
variantPools_.shrinkToFit(allocator_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Allocator* allocator_;
|
||||||
|
bool overflowed_;
|
||||||
|
StringPool stringPool_;
|
||||||
|
MemoryPoolList<SlotData> variantPools_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Collection/CollectionData.hpp>
|
||||||
|
#include <ArduinoJson/Memory/ResourceManager.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/alias_cast.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
inline Slot<VariantData> ResourceManager::allocVariant() {
|
||||||
|
auto p = variantPools_.allocSlot(allocator_);
|
||||||
|
if (!p) {
|
||||||
|
overflowed_ = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {new (&p->variant) VariantData, p.id()};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ResourceManager::freeVariant(Slot<VariantData> variant) {
|
||||||
|
variant->clear(this);
|
||||||
|
variantPools_.freeSlot({alias_cast<SlotData*>(variant.ptr()), variant.id()});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VariantData* ResourceManager::getVariant(SlotId id) const {
|
||||||
|
return reinterpret_cast<VariantData*>(variantPools_.getSlot(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_EXTENSIONS
|
||||||
|
inline Slot<VariantExtension> ResourceManager::allocExtension() {
|
||||||
|
auto p = variantPools_.allocSlot(allocator_);
|
||||||
|
if (!p) {
|
||||||
|
overflowed_ = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {&p->extension, p.id()};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ResourceManager::freeExtension(SlotId id) {
|
||||||
|
auto p = getExtension(id);
|
||||||
|
variantPools_.freeSlot({reinterpret_cast<SlotData*>(p), id});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VariantExtension* ResourceManager::getExtension(SlotId id) const {
|
||||||
|
return &variantPools_.getSlot(id)->extension;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/ResourceManager.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class StringBuffer {
|
||||||
|
public:
|
||||||
|
StringBuffer(ResourceManager* resources) : resources_(resources) {}
|
||||||
|
|
||||||
|
~StringBuffer() {
|
||||||
|
if (node_)
|
||||||
|
resources_->destroyString(node_);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* reserve(size_t capacity) {
|
||||||
|
if (node_ && capacity > node_->length) {
|
||||||
|
// existing buffer is too small, we need to reallocate
|
||||||
|
resources_->destroyString(node_);
|
||||||
|
node_ = nullptr;
|
||||||
|
}
|
||||||
|
if (!node_)
|
||||||
|
node_ = resources_->createString(capacity);
|
||||||
|
if (!node_)
|
||||||
|
return nullptr;
|
||||||
|
size_ = capacity;
|
||||||
|
node_->data[capacity] = 0; // null-terminate the string
|
||||||
|
return node_->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonString str() const {
|
||||||
|
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||||
|
return JsonString(node_->data, node_->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(VariantData* data) {
|
||||||
|
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||||
|
const char* s = node_->data;
|
||||||
|
if (isTinyString(s, size_))
|
||||||
|
data->setTinyString(adaptString(s, size_));
|
||||||
|
else
|
||||||
|
data->setOwnedString(commitStringNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveRaw(VariantData* data) {
|
||||||
|
data->setRawString(commitStringNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
StringNode* commitStringNode() {
|
||||||
|
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||||
|
node_->data[size_] = 0;
|
||||||
|
auto node = resources_->getString(adaptString(node_->data, size_));
|
||||||
|
if (node) {
|
||||||
|
node->references++;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node_->length != size_) {
|
||||||
|
node = resources_->resizeString(node_, size_);
|
||||||
|
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
|
||||||
|
} else {
|
||||||
|
node = node_;
|
||||||
|
}
|
||||||
|
node_ = nullptr;
|
||||||
|
resources_->saveString(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager* resources_;
|
||||||
|
StringNode* node_ = nullptr;
|
||||||
|
size_t size_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/ResourceManager.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class StringBuilder {
|
||||||
|
public:
|
||||||
|
static const size_t initialCapacity = 31;
|
||||||
|
|
||||||
|
StringBuilder(ResourceManager* resources) : resources_(resources) {}
|
||||||
|
|
||||||
|
~StringBuilder() {
|
||||||
|
if (node_)
|
||||||
|
resources_->destroyString(node_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startString() {
|
||||||
|
size_ = 0;
|
||||||
|
if (!node_)
|
||||||
|
node_ = resources_->createString(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(VariantData* variant) {
|
||||||
|
ARDUINOJSON_ASSERT(variant != nullptr);
|
||||||
|
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||||
|
|
||||||
|
char* p = node_->data;
|
||||||
|
if (isTinyString(p, size_)) {
|
||||||
|
variant->setTinyString(adaptString(p, size_));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p[size_] = 0;
|
||||||
|
StringNode* node = resources_->getString(adaptString(p, size_));
|
||||||
|
if (!node) {
|
||||||
|
node = resources_->resizeString(node_, size_);
|
||||||
|
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
|
||||||
|
resources_->saveString(node);
|
||||||
|
node_ = nullptr; // next time we need a new string
|
||||||
|
} else {
|
||||||
|
node->references++;
|
||||||
|
}
|
||||||
|
variant->setOwnedString(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(const char* s) {
|
||||||
|
while (*s)
|
||||||
|
append(*s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(const char* s, size_t n) {
|
||||||
|
while (n-- > 0) // TODO: memcpy
|
||||||
|
append(*s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(char c) {
|
||||||
|
if (node_ && size_ == node_->length)
|
||||||
|
node_ = resources_->resizeString(node_, size_ * 2U + 1);
|
||||||
|
if (node_)
|
||||||
|
node_->data[size_++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValid() const {
|
||||||
|
return node_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonString str() const {
|
||||||
|
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||||
|
node_->data[size_] = 0;
|
||||||
|
return JsonString(node_->data, size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ResourceManager* resources_;
|
||||||
|
StringNode* node_ = nullptr;
|
||||||
|
size_t size_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/Allocator.hpp>
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/integer.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/limits.hpp>
|
||||||
|
|
||||||
|
#include <stddef.h> // offsetof
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
struct StringNode {
|
||||||
|
// Use the same type as SlotId to store the reference count
|
||||||
|
// (there can never be more references than slots)
|
||||||
|
using references_type = uint_t<ARDUINOJSON_SLOT_ID_SIZE * 8>;
|
||||||
|
|
||||||
|
using length_type = uint_t<ARDUINOJSON_STRING_LENGTH_SIZE * 8>;
|
||||||
|
|
||||||
|
struct StringNode* next;
|
||||||
|
references_type references;
|
||||||
|
length_type length;
|
||||||
|
char data[1];
|
||||||
|
|
||||||
|
static constexpr size_t maxLength = numeric_limits<length_type>::highest();
|
||||||
|
|
||||||
|
static constexpr size_t sizeForLength(size_t n) {
|
||||||
|
return n + 1 + offsetof(StringNode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static StringNode* create(size_t length, Allocator* allocator) {
|
||||||
|
if (length > maxLength)
|
||||||
|
return nullptr;
|
||||||
|
auto size = sizeForLength(length);
|
||||||
|
if (size < length) // integer overflow
|
||||||
|
return nullptr; // (not testable on 64-bit)
|
||||||
|
auto node = reinterpret_cast<StringNode*>(allocator->allocate(size));
|
||||||
|
if (node) {
|
||||||
|
node->length = length_type(length);
|
||||||
|
node->references = 1;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static StringNode* resize(StringNode* node, size_t length,
|
||||||
|
Allocator* allocator) {
|
||||||
|
ARDUINOJSON_ASSERT(node != nullptr);
|
||||||
|
StringNode* newNode;
|
||||||
|
if (length <= maxLength)
|
||||||
|
newNode = reinterpret_cast<StringNode*>(
|
||||||
|
allocator->reallocate(node, sizeForLength(length)));
|
||||||
|
else
|
||||||
|
newNode = nullptr;
|
||||||
|
if (newNode)
|
||||||
|
newNode->length = length_type(length);
|
||||||
|
else
|
||||||
|
allocator->deallocate(node);
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy(StringNode* node, Allocator* allocator) {
|
||||||
|
allocator->deallocate(node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the size (in bytes) of an string with n characters.
|
||||||
|
constexpr size_t sizeofString(size_t n) {
|
||||||
|
return StringNode::sizeForLength(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Memory/Allocator.hpp>
|
||||||
|
#include <ArduinoJson/Memory/StringNode.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/utility.hpp>
|
||||||
|
#include <ArduinoJson/Strings/StringAdapters.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class StringPool {
|
||||||
|
public:
|
||||||
|
StringPool() = default;
|
||||||
|
StringPool(const StringPool&) = delete;
|
||||||
|
void operator=(StringPool&& src) = delete;
|
||||||
|
|
||||||
|
~StringPool() {
|
||||||
|
ARDUINOJSON_ASSERT(strings_ == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend void swap(StringPool& a, StringPool& b) {
|
||||||
|
swap_(a.strings_, b.strings_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear(Allocator* allocator) {
|
||||||
|
while (strings_) {
|
||||||
|
auto node = strings_;
|
||||||
|
strings_ = node->next;
|
||||||
|
StringNode::destroy(node, allocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
size_t total = 0;
|
||||||
|
for (auto node = strings_; node; node = node->next)
|
||||||
|
total += sizeofString(node->length);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
StringNode* add(TAdaptedString str, Allocator* allocator) {
|
||||||
|
ARDUINOJSON_ASSERT(str.isNull() == false);
|
||||||
|
|
||||||
|
auto node = get(str);
|
||||||
|
if (node) {
|
||||||
|
node->references++;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t n = str.size();
|
||||||
|
|
||||||
|
node = StringNode::create(n, allocator);
|
||||||
|
if (!node)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
stringGetChars(str, node->data, n);
|
||||||
|
node->data[n] = 0; // force NUL terminator
|
||||||
|
add(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(StringNode* node) {
|
||||||
|
ARDUINOJSON_ASSERT(node != nullptr);
|
||||||
|
node->next = strings_;
|
||||||
|
strings_ = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
StringNode* get(const TAdaptedString& str) const {
|
||||||
|
for (auto node = strings_; node; node = node->next) {
|
||||||
|
if (stringEquals(str, adaptString(node->data, node->length)))
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dereference(const char* s, Allocator* allocator) {
|
||||||
|
StringNode* prev = nullptr;
|
||||||
|
for (auto node = strings_; node; node = node->next) {
|
||||||
|
if (node->data == s) {
|
||||||
|
if (--node->references == 0) {
|
||||||
|
if (prev)
|
||||||
|
prev->next = node->next;
|
||||||
|
else
|
||||||
|
strings_ = node->next;
|
||||||
|
StringNode::destroy(node, allocator);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prev = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
StringNode* strings_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Strings/StringAdapters.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// A special type of data that can be used to insert pregenerated JSON portions.
|
||||||
|
template <typename T>
|
||||||
|
class SerializedValue {
|
||||||
|
public:
|
||||||
|
explicit SerializedValue(T str) : str_(str) {}
|
||||||
|
operator T() const {
|
||||||
|
return str_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* data() const {
|
||||||
|
return str_.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
// CAUTION: the old Arduino String doesn't have size()
|
||||||
|
return str_.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T str_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TChar>
|
||||||
|
class SerializedValue<TChar*> {
|
||||||
|
public:
|
||||||
|
explicit SerializedValue(TChar* p, size_t n) : data_(p), size_(n) {}
|
||||||
|
operator TChar*() const {
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
TChar* data() const {
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TChar* data_;
|
||||||
|
size_t size_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using RawString = SerializedValue<const char*>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline SerializedValue<T> serialized(T str) {
|
||||||
|
return SerializedValue<T>(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TChar>
|
||||||
|
inline SerializedValue<TChar*> serialized(TChar* p) {
|
||||||
|
return SerializedValue<TChar*>(p, detail::adaptString(p).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TChar>
|
||||||
|
inline SerializedValue<TChar*> serialized(TChar* p, size_t n) {
|
||||||
|
return SerializedValue<TChar*>(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Variant/Converter.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class MsgPackBinary {
|
||||||
|
public:
|
||||||
|
MsgPackBinary() : data_(nullptr), size_(0) {}
|
||||||
|
explicit MsgPackBinary(const void* c, size_t size) : data_(c), size_(size) {}
|
||||||
|
|
||||||
|
const void* data() const {
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const void* data_;
|
||||||
|
size_t size_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct Converter<MsgPackBinary> : private detail::VariantAttorney {
|
||||||
|
static void toJson(MsgPackBinary src, JsonVariant dst) {
|
||||||
|
auto data = VariantAttorney::getData(dst);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
auto resources = getResourceManager(dst);
|
||||||
|
data->clear(resources);
|
||||||
|
if (src.data()) {
|
||||||
|
size_t headerSize = src.size() >= 0x10000 ? 5
|
||||||
|
: src.size() >= 0x100 ? 3
|
||||||
|
: 2;
|
||||||
|
auto str = resources->createString(src.size() + headerSize);
|
||||||
|
if (str) {
|
||||||
|
resources->saveString(str);
|
||||||
|
auto ptr = reinterpret_cast<uint8_t*>(str->data);
|
||||||
|
switch (headerSize) {
|
||||||
|
case 2:
|
||||||
|
ptr[0] = uint8_t(0xc4);
|
||||||
|
ptr[1] = uint8_t(src.size() & 0xff);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ptr[0] = uint8_t(0xc5);
|
||||||
|
ptr[1] = uint8_t(src.size() >> 8 & 0xff);
|
||||||
|
ptr[2] = uint8_t(src.size() & 0xff);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
ptr[0] = uint8_t(0xc6);
|
||||||
|
ptr[1] = uint8_t(src.size() >> 24 & 0xff);
|
||||||
|
ptr[2] = uint8_t(src.size() >> 16 & 0xff);
|
||||||
|
ptr[3] = uint8_t(src.size() >> 8 & 0xff);
|
||||||
|
ptr[4] = uint8_t(src.size() & 0xff);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ARDUINOJSON_ASSERT(false);
|
||||||
|
}
|
||||||
|
memcpy(ptr + headerSize, src.data(), src.size());
|
||||||
|
data->setRawString(str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MsgPackBinary fromJson(JsonVariantConst src) {
|
||||||
|
auto data = getData(src);
|
||||||
|
if (!data)
|
||||||
|
return {};
|
||||||
|
auto rawstr = data->asRawString();
|
||||||
|
auto p = reinterpret_cast<const uint8_t*>(rawstr.c_str());
|
||||||
|
auto n = rawstr.size();
|
||||||
|
if (n >= 2 && p[0] == 0xc4) { // bin 8
|
||||||
|
size_t size = p[1];
|
||||||
|
if (size + 2 == n)
|
||||||
|
return MsgPackBinary(p + 2, size);
|
||||||
|
} else if (n >= 3 && p[0] == 0xc5) { // bin 16
|
||||||
|
size_t size = size_t(p[1] << 8) | p[2];
|
||||||
|
if (size + 3 == n)
|
||||||
|
return MsgPackBinary(p + 3, size);
|
||||||
|
} else if (n >= 5 && p[0] == 0xc6) { // bin 32
|
||||||
|
size_t size =
|
||||||
|
size_t(p[1] << 24) | size_t(p[2] << 16) | size_t(p[3] << 8) | p[4];
|
||||||
|
if (size + 5 == n)
|
||||||
|
return MsgPackBinary(p + 5, size);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkJson(JsonVariantConst src) {
|
||||||
|
return fromJson(src).data() != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
+487
@@ -0,0 +1,487 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Deserialization/deserialize.hpp>
|
||||||
|
#include <ArduinoJson/Memory/ResourceManager.hpp>
|
||||||
|
#include <ArduinoJson/Memory/StringBuffer.hpp>
|
||||||
|
#include <ArduinoJson/MsgPack/endianness.hpp>
|
||||||
|
#include <ArduinoJson/MsgPack/ieee754.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TReader>
|
||||||
|
class MsgPackDeserializer {
|
||||||
|
public:
|
||||||
|
MsgPackDeserializer(ResourceManager* resources, TReader reader)
|
||||||
|
: resources_(resources),
|
||||||
|
reader_(reader),
|
||||||
|
stringBuffer_(resources),
|
||||||
|
foundSomething_(false) {}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError parse(VariantData& variant, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
err = parseVariant(&variant, filter, nestingLimit);
|
||||||
|
return foundSomething_ ? err : DeserializationError::EmptyInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError::Code parseVariant(
|
||||||
|
VariantData* variant, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
uint8_t header[5];
|
||||||
|
err = readBytes(header, 1);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
const uint8_t& code = header[0];
|
||||||
|
|
||||||
|
foundSomething_ = true;
|
||||||
|
|
||||||
|
bool allowValue = filter.allowValue();
|
||||||
|
|
||||||
|
if (allowValue) {
|
||||||
|
// callers pass a null pointer only when value must be ignored
|
||||||
|
ARDUINOJSON_ASSERT(variant != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code >= 0xcc && code <= 0xd3) {
|
||||||
|
auto width = uint8_t(1U << ((code - 0xcc) % 4));
|
||||||
|
if (allowValue)
|
||||||
|
return readInteger(variant, width, code >= 0xd0);
|
||||||
|
else
|
||||||
|
return skipBytes(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 0xc0:
|
||||||
|
// already null
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
|
||||||
|
case 0xc1:
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
|
||||||
|
case 0xc2:
|
||||||
|
case 0xc3:
|
||||||
|
if (allowValue)
|
||||||
|
variant->setBoolean(code == 0xc3);
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
|
||||||
|
case 0xca:
|
||||||
|
if (allowValue)
|
||||||
|
return readFloat<float>(variant);
|
||||||
|
else
|
||||||
|
return skipBytes(4);
|
||||||
|
|
||||||
|
case 0xcb:
|
||||||
|
if (allowValue)
|
||||||
|
return readDouble<double>(variant);
|
||||||
|
else
|
||||||
|
return skipBytes(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code <= 0x7f || code >= 0xe0) { // fixint
|
||||||
|
if (allowValue)
|
||||||
|
variant->setInteger(static_cast<int8_t>(code), resources_);
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sizeBytes = 0;
|
||||||
|
size_t size = 0;
|
||||||
|
bool isExtension = code >= 0xc7 && code <= 0xc9;
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 0xc4: // bin 8
|
||||||
|
case 0xc7: // ext 8
|
||||||
|
case 0xd9: // str 8
|
||||||
|
sizeBytes = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xc5: // bin 16
|
||||||
|
case 0xc8: // ext 16
|
||||||
|
case 0xda: // str 16
|
||||||
|
case 0xdc: // array 16
|
||||||
|
case 0xde: // map 16
|
||||||
|
sizeBytes = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xc6: // bin 32
|
||||||
|
case 0xc9: // ext 32
|
||||||
|
case 0xdb: // str 32
|
||||||
|
case 0xdd: // array 32
|
||||||
|
case 0xdf: // map 32
|
||||||
|
sizeBytes = 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code >= 0xd4 && code <= 0xd8) { // fixext
|
||||||
|
size = size_t(1) << (code - 0xd4);
|
||||||
|
isExtension = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (code & 0xf0) {
|
||||||
|
case 0x90: // fixarray
|
||||||
|
case 0x80: // fixmap
|
||||||
|
size = code & 0x0F;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (code & 0xe0) {
|
||||||
|
case 0xa0: // fixstr
|
||||||
|
size = code & 0x1f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeBytes) {
|
||||||
|
err = readBytes(header + 1, sizeBytes);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
uint32_t size32 = 0;
|
||||||
|
for (uint8_t i = 0; i < sizeBytes; i++)
|
||||||
|
size32 = (size32 << 8) | header[i + 1];
|
||||||
|
|
||||||
|
size = size_t(size32);
|
||||||
|
if (size < size32) // integer overflow
|
||||||
|
return DeserializationError::NoMemory; // (not testable on 32/64-bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// array 16, 32 and fixarray
|
||||||
|
if (code == 0xdc || code == 0xdd || (code & 0xf0) == 0x90)
|
||||||
|
return readArray(variant, size, filter, nestingLimit);
|
||||||
|
|
||||||
|
// map 16, 32 and fixmap
|
||||||
|
if (code == 0xde || code == 0xdf || (code & 0xf0) == 0x80)
|
||||||
|
return readObject(variant, size, filter, nestingLimit);
|
||||||
|
|
||||||
|
// str 8, 16, 32 and fixstr
|
||||||
|
if (code == 0xd9 || code == 0xda || code == 0xdb || (code & 0xe0) == 0xa0) {
|
||||||
|
if (allowValue)
|
||||||
|
return readString(variant, size);
|
||||||
|
else
|
||||||
|
return skipBytes(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExtension)
|
||||||
|
size++; // to include the type
|
||||||
|
|
||||||
|
if (allowValue)
|
||||||
|
return readRawString(variant, header, uint8_t(1 + sizeBytes), size);
|
||||||
|
else
|
||||||
|
return skipBytes(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code readByte(uint8_t& value) {
|
||||||
|
int c = reader_.read();
|
||||||
|
if (c < 0)
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
value = static_cast<uint8_t>(c);
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code readBytes(void* p, size_t n) {
|
||||||
|
if (reader_.readBytes(reinterpret_cast<char*>(p), n) == n)
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
DeserializationError::Code readBytes(T& value) {
|
||||||
|
return readBytes(&value, sizeof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code skipBytes(size_t n) {
|
||||||
|
for (; n; --n) {
|
||||||
|
if (reader_.read() < 0)
|
||||||
|
return DeserializationError::IncompleteInput;
|
||||||
|
}
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code readInteger(VariantData* variant, uint8_t width,
|
||||||
|
bool isSigned) {
|
||||||
|
uint8_t buffer[8];
|
||||||
|
|
||||||
|
auto err = readBytes(buffer, width);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
union {
|
||||||
|
int64_t signedValue;
|
||||||
|
uint64_t unsignedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isSigned)
|
||||||
|
signedValue = static_cast<int8_t>(buffer[0]); // propagate sign bit
|
||||||
|
else
|
||||||
|
unsignedValue = static_cast<uint8_t>(buffer[0]);
|
||||||
|
|
||||||
|
for (uint8_t i = 1; i < width; i++)
|
||||||
|
unsignedValue = (unsignedValue << 8) | buffer[i];
|
||||||
|
|
||||||
|
if (isSigned) {
|
||||||
|
auto truncatedValue = static_cast<JsonInteger>(signedValue);
|
||||||
|
if (truncatedValue == signedValue) {
|
||||||
|
if (!variant->setInteger(truncatedValue, resources_))
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
}
|
||||||
|
// else set null on overflow
|
||||||
|
} else {
|
||||||
|
auto truncatedValue = static_cast<JsonUInt>(unsignedValue);
|
||||||
|
if (truncatedValue == unsignedValue)
|
||||||
|
if (!variant->setInteger(truncatedValue, resources_))
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
// else set null on overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
enable_if_t<sizeof(T) == 4, DeserializationError::Code> readFloat(
|
||||||
|
VariantData* variant) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
T value;
|
||||||
|
|
||||||
|
err = readBytes(value);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
fixEndianness(value);
|
||||||
|
variant->setFloat(value, resources_);
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
enable_if_t<sizeof(T) == 8, DeserializationError::Code> readDouble(
|
||||||
|
VariantData* variant) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
T value;
|
||||||
|
|
||||||
|
err = readBytes(value);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
fixEndianness(value);
|
||||||
|
if (variant->setFloat(value, resources_))
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
else
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
enable_if_t<sizeof(T) == 4, DeserializationError::Code> readDouble(
|
||||||
|
VariantData* variant) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
uint8_t i[8]; // input is 8 bytes
|
||||||
|
T value; // output is 4 bytes
|
||||||
|
uint8_t* o = reinterpret_cast<uint8_t*>(&value);
|
||||||
|
|
||||||
|
err = readBytes(i, 8);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
doubleToFloat(i, o);
|
||||||
|
fixEndianness(value);
|
||||||
|
variant->setFloat(value, resources_);
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code readString(VariantData* variant, size_t n) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
err = readString(n);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
stringBuffer_.save(variant);
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code readString(size_t n) {
|
||||||
|
char* p = stringBuffer_.reserve(n);
|
||||||
|
if (!p)
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
return readBytes(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code readRawString(VariantData* variant,
|
||||||
|
const void* header,
|
||||||
|
uint8_t headerSize, size_t n) {
|
||||||
|
auto totalSize = size_t(headerSize + n);
|
||||||
|
if (totalSize < n) // integer overflow
|
||||||
|
return DeserializationError::NoMemory; // (not testable on 64-bit)
|
||||||
|
|
||||||
|
char* p = stringBuffer_.reserve(totalSize);
|
||||||
|
if (!p)
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
memcpy(p, header, headerSize);
|
||||||
|
|
||||||
|
auto err = readBytes(p + headerSize, n);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
stringBuffer_.saveRaw(variant);
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError::Code readArray(
|
||||||
|
VariantData* variant, size_t n, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
if (nestingLimit.reached())
|
||||||
|
return DeserializationError::TooDeep;
|
||||||
|
|
||||||
|
bool allowArray = filter.allowArray();
|
||||||
|
|
||||||
|
ArrayData* array;
|
||||||
|
if (allowArray) {
|
||||||
|
ARDUINOJSON_ASSERT(variant != 0);
|
||||||
|
array = &variant->toArray();
|
||||||
|
} else {
|
||||||
|
array = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TFilter elementFilter = filter[0U];
|
||||||
|
|
||||||
|
for (; n; --n) {
|
||||||
|
VariantData* value;
|
||||||
|
|
||||||
|
if (elementFilter.allow()) {
|
||||||
|
ARDUINOJSON_ASSERT(array != 0);
|
||||||
|
value = array->addElement(resources_);
|
||||||
|
if (!value)
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
} else {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = parseVariant(value, elementFilter, nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TFilter>
|
||||||
|
DeserializationError::Code readObject(
|
||||||
|
VariantData* variant, size_t n, TFilter filter,
|
||||||
|
DeserializationOption::NestingLimit nestingLimit) {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
|
||||||
|
if (nestingLimit.reached())
|
||||||
|
return DeserializationError::TooDeep;
|
||||||
|
|
||||||
|
ObjectData* object;
|
||||||
|
if (filter.allowObject()) {
|
||||||
|
ARDUINOJSON_ASSERT(variant != 0);
|
||||||
|
object = &variant->toObject();
|
||||||
|
} else {
|
||||||
|
object = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; n; --n) {
|
||||||
|
err = readKey();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
JsonString key = stringBuffer_.str();
|
||||||
|
TFilter memberFilter = filter[key.c_str()];
|
||||||
|
VariantData* member = 0;
|
||||||
|
|
||||||
|
if (memberFilter.allow()) {
|
||||||
|
ARDUINOJSON_ASSERT(object != 0);
|
||||||
|
|
||||||
|
auto keyVariant = object->addPair(&member, resources_);
|
||||||
|
if (!keyVariant)
|
||||||
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
|
stringBuffer_.save(keyVariant);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = parseVariant(member, memberFilter, nestingLimit.decrement());
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeserializationError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError::Code readKey() {
|
||||||
|
DeserializationError::Code err;
|
||||||
|
uint8_t code;
|
||||||
|
|
||||||
|
err = readByte(code);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if ((code & 0xe0) == 0xa0)
|
||||||
|
return readString(code & 0x1f);
|
||||||
|
|
||||||
|
if (code >= 0xd9 && code <= 0xdb) {
|
||||||
|
uint8_t sizeBytes = uint8_t(1U << (code - 0xd9));
|
||||||
|
uint32_t size = 0;
|
||||||
|
for (uint8_t i = 0; i < sizeBytes; i++) {
|
||||||
|
err = readByte(code);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
size = (size << 8) | code;
|
||||||
|
}
|
||||||
|
return readString(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeserializationError::InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager* resources_;
|
||||||
|
TReader reader_;
|
||||||
|
StringBuffer stringBuffer_;
|
||||||
|
bool foundSomething_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// Parses a MessagePack input and puts the result in a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/msgpack/deserializemsgpack/
|
||||||
|
template <typename TDestination, typename... Args,
|
||||||
|
detail::enable_if_t<
|
||||||
|
detail::is_deserialize_destination<TDestination>::value, int> = 0>
|
||||||
|
inline DeserializationError deserializeMsgPack(TDestination&& dst,
|
||||||
|
Args&&... args) {
|
||||||
|
using namespace detail;
|
||||||
|
return deserialize<MsgPackDeserializer>(detail::forward<TDestination>(dst),
|
||||||
|
detail::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a MessagePack input and puts the result in a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/msgpack/deserializemsgpack/
|
||||||
|
template <typename TDestination, typename TChar, typename... Args,
|
||||||
|
detail::enable_if_t<
|
||||||
|
detail::is_deserialize_destination<TDestination>::value, int> = 0>
|
||||||
|
inline DeserializationError deserializeMsgPack(TDestination&& dst, TChar* input,
|
||||||
|
Args&&... args) {
|
||||||
|
using namespace detail;
|
||||||
|
return deserialize<MsgPackDeserializer>(detail::forward<TDestination>(dst),
|
||||||
|
input,
|
||||||
|
detail::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Variant/Converter.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class MsgPackExtension {
|
||||||
|
public:
|
||||||
|
MsgPackExtension() : data_(nullptr), size_(0), type_(0) {}
|
||||||
|
explicit MsgPackExtension(int8_t type, const void* data, size_t size)
|
||||||
|
: data_(data), size_(size), type_(type) {}
|
||||||
|
|
||||||
|
int8_t type() const {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* data() const {
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const void* data_;
|
||||||
|
size_t size_;
|
||||||
|
int8_t type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct Converter<MsgPackExtension> : private detail::VariantAttorney {
|
||||||
|
static void toJson(MsgPackExtension src, JsonVariant dst) {
|
||||||
|
auto data = VariantAttorney::getData(dst);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
auto resources = getResourceManager(dst);
|
||||||
|
data->clear(resources);
|
||||||
|
if (src.data()) {
|
||||||
|
uint8_t format, sizeBytes;
|
||||||
|
if (src.size() >= 0x10000) {
|
||||||
|
format = 0xc9; // ext 32
|
||||||
|
sizeBytes = 4;
|
||||||
|
} else if (src.size() >= 0x100) {
|
||||||
|
format = 0xc8; // ext 16
|
||||||
|
sizeBytes = 2;
|
||||||
|
} else if (src.size() == 16) {
|
||||||
|
format = 0xd8; // fixext 16
|
||||||
|
sizeBytes = 0;
|
||||||
|
} else if (src.size() == 8) {
|
||||||
|
format = 0xd7; // fixext 8
|
||||||
|
sizeBytes = 0;
|
||||||
|
} else if (src.size() == 4) {
|
||||||
|
format = 0xd6; // fixext 4
|
||||||
|
sizeBytes = 0;
|
||||||
|
} else if (src.size() == 2) {
|
||||||
|
format = 0xd5; // fixext 2
|
||||||
|
sizeBytes = 0;
|
||||||
|
} else if (src.size() == 1) {
|
||||||
|
format = 0xd4; // fixext 1
|
||||||
|
sizeBytes = 0;
|
||||||
|
} else {
|
||||||
|
format = 0xc7; // ext 8
|
||||||
|
sizeBytes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto str = resources->createString(src.size() + 2 + sizeBytes);
|
||||||
|
if (str) {
|
||||||
|
resources->saveString(str);
|
||||||
|
auto ptr = reinterpret_cast<uint8_t*>(str->data);
|
||||||
|
*ptr++ = uint8_t(format);
|
||||||
|
for (uint8_t i = 0; i < sizeBytes; i++)
|
||||||
|
*ptr++ = uint8_t(src.size() >> (sizeBytes - i - 1) * 8 & 0xff);
|
||||||
|
*ptr++ = uint8_t(src.type());
|
||||||
|
memcpy(ptr, src.data(), src.size());
|
||||||
|
data->setRawString(str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MsgPackExtension fromJson(JsonVariantConst src) {
|
||||||
|
auto data = getData(src);
|
||||||
|
if (!data)
|
||||||
|
return {};
|
||||||
|
auto rawstr = data->asRawString();
|
||||||
|
if (rawstr.size() == 0)
|
||||||
|
return {};
|
||||||
|
auto p = reinterpret_cast<const uint8_t*>(rawstr.c_str());
|
||||||
|
|
||||||
|
size_t payloadSize = 0;
|
||||||
|
uint8_t headerSize = 0;
|
||||||
|
|
||||||
|
const uint8_t& code = p[0];
|
||||||
|
|
||||||
|
if (code >= 0xd4 && code <= 0xd8) { // fixext 1
|
||||||
|
headerSize = 2;
|
||||||
|
payloadSize = size_t(1) << (code - 0xd4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code >= 0xc7 && code <= 0xc9) {
|
||||||
|
uint8_t sizeBytes = uint8_t(1 << (code - 0xc7));
|
||||||
|
for (uint8_t i = 0; i < sizeBytes; i++)
|
||||||
|
payloadSize = (payloadSize << 8) | p[1 + i];
|
||||||
|
headerSize = uint8_t(2 + sizeBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawstr.size() == headerSize + payloadSize)
|
||||||
|
return MsgPackExtension(int8_t(p[headerSize - 1]), p + headerSize,
|
||||||
|
payloadSize);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkJson(JsonVariantConst src) {
|
||||||
|
return fromJson(src).data() != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
+244
@@ -0,0 +1,244 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/MsgPack/endianness.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/CountingDecorator.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/measure.hpp>
|
||||||
|
#include <ArduinoJson/Serialization/serialize.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TWriter>
|
||||||
|
class MsgPackSerializer : public VariantDataVisitor<size_t> {
|
||||||
|
public:
|
||||||
|
static const bool producesText = false;
|
||||||
|
|
||||||
|
MsgPackSerializer(TWriter writer, const ResourceManager* resources)
|
||||||
|
: writer_(writer), resources_(resources) {}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
enable_if_t<is_floating_point<T>::value && sizeof(T) == 4, size_t> visit(
|
||||||
|
T value32) {
|
||||||
|
if (canConvertNumber<JsonInteger>(value32)) {
|
||||||
|
JsonInteger truncatedValue = JsonInteger(value32);
|
||||||
|
if (value32 == T(truncatedValue))
|
||||||
|
return visit(truncatedValue);
|
||||||
|
}
|
||||||
|
writeByte(0xCA);
|
||||||
|
writeInteger(value32);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ARDUINOJSON_NO_SANITIZE("float-cast-overflow")
|
||||||
|
enable_if_t<is_floating_point<T>::value && sizeof(T) == 8, size_t> visit(
|
||||||
|
T value64) {
|
||||||
|
float value32 = float(value64);
|
||||||
|
if (value32 == value64)
|
||||||
|
return visit(value32);
|
||||||
|
writeByte(0xCB);
|
||||||
|
writeInteger(value64);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(const ArrayData& array) {
|
||||||
|
size_t n = array.size(resources_);
|
||||||
|
if (n < 0x10) {
|
||||||
|
writeByte(uint8_t(0x90 + n));
|
||||||
|
} else if (n < 0x10000) {
|
||||||
|
writeByte(0xDC);
|
||||||
|
writeInteger(uint16_t(n));
|
||||||
|
} else {
|
||||||
|
writeByte(0xDD);
|
||||||
|
writeInteger(uint32_t(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto slotId = array.head();
|
||||||
|
while (slotId != NULL_SLOT) {
|
||||||
|
auto slot = resources_->getVariant(slotId);
|
||||||
|
slot->accept(*this, resources_);
|
||||||
|
slotId = slot->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(const ObjectData& object) {
|
||||||
|
size_t n = object.size(resources_);
|
||||||
|
if (n < 0x10) {
|
||||||
|
writeByte(uint8_t(0x80 + n));
|
||||||
|
} else if (n < 0x10000) {
|
||||||
|
writeByte(0xDE);
|
||||||
|
writeInteger(uint16_t(n));
|
||||||
|
} else {
|
||||||
|
writeByte(0xDF);
|
||||||
|
writeInteger(uint32_t(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto slotId = object.head();
|
||||||
|
while (slotId != NULL_SLOT) {
|
||||||
|
auto slot = resources_->getVariant(slotId);
|
||||||
|
slot->accept(*this, resources_);
|
||||||
|
slotId = slot->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(const char* value) {
|
||||||
|
return visit(JsonString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(JsonString value) {
|
||||||
|
ARDUINOJSON_ASSERT(!value.isNull());
|
||||||
|
|
||||||
|
auto n = value.size();
|
||||||
|
|
||||||
|
if (n < 0x20) {
|
||||||
|
writeByte(uint8_t(0xA0 + n));
|
||||||
|
} else if (n < 0x100) {
|
||||||
|
writeByte(0xD9);
|
||||||
|
writeInteger(uint8_t(n));
|
||||||
|
} else if (n < 0x10000) {
|
||||||
|
writeByte(0xDA);
|
||||||
|
writeInteger(uint16_t(n));
|
||||||
|
} else {
|
||||||
|
writeByte(0xDB);
|
||||||
|
writeInteger(uint32_t(n));
|
||||||
|
}
|
||||||
|
writeBytes(reinterpret_cast<const uint8_t*>(value.c_str()), n);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(RawString value) {
|
||||||
|
writeBytes(reinterpret_cast<const uint8_t*>(value.data()), value.size());
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(JsonInteger value) {
|
||||||
|
if (value > 0) {
|
||||||
|
visit(static_cast<JsonUInt>(value));
|
||||||
|
} else if (value >= -0x20) {
|
||||||
|
writeInteger(int8_t(value));
|
||||||
|
} else if (value >= -0x80) {
|
||||||
|
writeByte(0xD0);
|
||||||
|
writeInteger(int8_t(value));
|
||||||
|
} else if (value >= -0x8000) {
|
||||||
|
writeByte(0xD1);
|
||||||
|
writeInteger(int16_t(value));
|
||||||
|
}
|
||||||
|
#if ARDUINOJSON_USE_LONG_LONG
|
||||||
|
else if (value >= -0x80000000LL)
|
||||||
|
#else
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
writeByte(0xD2);
|
||||||
|
writeInteger(int32_t(value));
|
||||||
|
}
|
||||||
|
#if ARDUINOJSON_USE_LONG_LONG
|
||||||
|
else {
|
||||||
|
writeByte(0xD3);
|
||||||
|
writeInteger(int64_t(value));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(JsonUInt value) {
|
||||||
|
if (value <= 0x7F) {
|
||||||
|
writeInteger(uint8_t(value));
|
||||||
|
} else if (value <= 0xFF) {
|
||||||
|
writeByte(0xCC);
|
||||||
|
writeInteger(uint8_t(value));
|
||||||
|
} else if (value <= 0xFFFF) {
|
||||||
|
writeByte(0xCD);
|
||||||
|
writeInteger(uint16_t(value));
|
||||||
|
}
|
||||||
|
#if ARDUINOJSON_USE_LONG_LONG
|
||||||
|
else if (value <= 0xFFFFFFFF)
|
||||||
|
#else
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
writeByte(0xCE);
|
||||||
|
writeInteger(uint32_t(value));
|
||||||
|
}
|
||||||
|
#if ARDUINOJSON_USE_LONG_LONG
|
||||||
|
else {
|
||||||
|
writeByte(0xCF);
|
||||||
|
writeInteger(uint64_t(value));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(bool value) {
|
||||||
|
writeByte(value ? 0xC3 : 0xC2);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t visit(nullptr_t) {
|
||||||
|
writeByte(0xC0);
|
||||||
|
return bytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t bytesWritten() const {
|
||||||
|
return writer_.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeByte(uint8_t c) {
|
||||||
|
writer_.write(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeBytes(const uint8_t* p, size_t n) {
|
||||||
|
writer_.write(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void writeInteger(T value) {
|
||||||
|
fixEndianness(value);
|
||||||
|
writeBytes(reinterpret_cast<uint8_t*>(&value), sizeof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
CountingDecorator<TWriter> writer_;
|
||||||
|
const ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// Produces a MessagePack document.
|
||||||
|
// https://arduinojson.org/v7/api/msgpack/serializemsgpack/
|
||||||
|
template <
|
||||||
|
typename TDestination,
|
||||||
|
detail::enable_if_t<!detail::is_pointer<TDestination>::value, int> = 0>
|
||||||
|
inline size_t serializeMsgPack(JsonVariantConst source, TDestination& output) {
|
||||||
|
using namespace ArduinoJson::detail;
|
||||||
|
return serialize<MsgPackSerializer>(source, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produces a MessagePack document.
|
||||||
|
// https://arduinojson.org/v7/api/msgpack/serializemsgpack/
|
||||||
|
inline size_t serializeMsgPack(JsonVariantConst source, void* output,
|
||||||
|
size_t size) {
|
||||||
|
using namespace ArduinoJson::detail;
|
||||||
|
return serialize<MsgPackSerializer>(source, output, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the length of the document that serializeMsgPack() produces.
|
||||||
|
// https://arduinojson.org/v7/api/msgpack/measuremsgpack/
|
||||||
|
inline size_t measureMsgPack(JsonVariantConst source) {
|
||||||
|
using namespace ArduinoJson::detail;
|
||||||
|
return measure<MsgPackSerializer>(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#if ARDUINOJSON_LITTLE_ENDIAN
|
||||||
|
inline void swapBytes(uint8_t& a, uint8_t& b) {
|
||||||
|
uint8_t t(a);
|
||||||
|
a = b;
|
||||||
|
b = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void fixEndianness(uint8_t* p, integral_constant<size_t, 8>) {
|
||||||
|
swapBytes(p[0], p[7]);
|
||||||
|
swapBytes(p[1], p[6]);
|
||||||
|
swapBytes(p[2], p[5]);
|
||||||
|
swapBytes(p[3], p[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void fixEndianness(uint8_t* p, integral_constant<size_t, 4>) {
|
||||||
|
swapBytes(p[0], p[3]);
|
||||||
|
swapBytes(p[1], p[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void fixEndianness(uint8_t* p, integral_constant<size_t, 2>) {
|
||||||
|
swapBytes(p[0], p[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void fixEndianness(uint8_t*, integral_constant<size_t, 1>) {}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void fixEndianness(T& value) {
|
||||||
|
fixEndianness(reinterpret_cast<uint8_t*>(&value),
|
||||||
|
integral_constant<size_t, sizeof(T)>());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
template <typename T>
|
||||||
|
inline void fixEndianness(T&) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
inline void doubleToFloat(const uint8_t d[8], uint8_t f[4]) {
|
||||||
|
f[0] = uint8_t((d[0] & 0xC0) | (d[0] << 3 & 0x3f) | (d[1] >> 5));
|
||||||
|
f[1] = uint8_t((d[1] << 3) | (d[2] >> 5));
|
||||||
|
f[2] = uint8_t((d[2] << 3) | (d[3] >> 5));
|
||||||
|
f[3] = uint8_t((d[3] << 3) | (d[4] >> 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/preprocessor.hpp>
|
||||||
|
#include <ArduinoJson/version.hpp>
|
||||||
|
|
||||||
|
#ifndef ARDUINOJSON_VERSION_NAMESPACE
|
||||||
|
|
||||||
|
# define ARDUINOJSON_VERSION_NAMESPACE \
|
||||||
|
ARDUINOJSON_CONCAT5( \
|
||||||
|
ARDUINOJSON_VERSION_MACRO, \
|
||||||
|
ARDUINOJSON_BIN2ALPHA(ARDUINOJSON_ENABLE_PROGMEM, \
|
||||||
|
ARDUINOJSON_USE_LONG_LONG, \
|
||||||
|
ARDUINOJSON_USE_DOUBLE, 1), \
|
||||||
|
ARDUINOJSON_BIN2ALPHA( \
|
||||||
|
ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY, \
|
||||||
|
ARDUINOJSON_ENABLE_COMMENTS, ARDUINOJSON_DECODE_UNICODE), \
|
||||||
|
ARDUINOJSON_SLOT_ID_SIZE, ARDUINOJSON_STRING_LENGTH_SIZE)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE \
|
||||||
|
namespace ArduinoJson { \
|
||||||
|
inline namespace ARDUINOJSON_VERSION_NAMESPACE {
|
||||||
|
|
||||||
|
#define ARDUINOJSON_END_PUBLIC_NAMESPACE \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE \
|
||||||
|
namespace ArduinoJson { \
|
||||||
|
inline namespace ARDUINOJSON_VERSION_NAMESPACE { \
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
#define ARDUINOJSON_END_PRIVATE_NAMESPACE \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/FloatTraits.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/JsonFloat.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/math.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
struct FloatParts {
|
||||||
|
uint32_t integral;
|
||||||
|
uint32_t decimal;
|
||||||
|
int16_t exponent;
|
||||||
|
int8_t decimalPlaces;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TFloat>
|
||||||
|
inline int16_t normalize(TFloat& value) {
|
||||||
|
using traits = FloatTraits<TFloat>;
|
||||||
|
int16_t powersOf10 = 0;
|
||||||
|
|
||||||
|
int8_t index = sizeof(TFloat) == 8 ? 8 : 5;
|
||||||
|
int bit = 1 << index;
|
||||||
|
|
||||||
|
if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) {
|
||||||
|
for (; index >= 0; index--) {
|
||||||
|
if (value >= traits::positiveBinaryPowersOfTen()[index]) {
|
||||||
|
value *= traits::negativeBinaryPowersOfTen()[index];
|
||||||
|
powersOf10 = int16_t(powersOf10 + bit);
|
||||||
|
}
|
||||||
|
bit >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) {
|
||||||
|
for (; index >= 0; index--) {
|
||||||
|
if (value < traits::negativeBinaryPowersOfTen()[index] * 10) {
|
||||||
|
value *= traits::positiveBinaryPowersOfTen()[index];
|
||||||
|
powersOf10 = int16_t(powersOf10 - bit);
|
||||||
|
}
|
||||||
|
bit >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return powersOf10;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint32_t pow10(int exponent) {
|
||||||
|
return (exponent == 0) ? 1 : 10 * pow10(exponent - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) {
|
||||||
|
uint32_t maxDecimalPart = pow10(decimalPlaces);
|
||||||
|
|
||||||
|
int16_t exponent = normalize(value);
|
||||||
|
|
||||||
|
uint32_t integral = uint32_t(value);
|
||||||
|
// reduce number of decimal places by the number of integral places
|
||||||
|
for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) {
|
||||||
|
maxDecimalPart /= 10;
|
||||||
|
decimalPlaces--;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonFloat remainder =
|
||||||
|
(value - JsonFloat(integral)) * JsonFloat(maxDecimalPart);
|
||||||
|
|
||||||
|
uint32_t decimal = uint32_t(remainder);
|
||||||
|
remainder = remainder - JsonFloat(decimal);
|
||||||
|
|
||||||
|
// rounding:
|
||||||
|
// increment by 1 if remainder >= 0.5
|
||||||
|
decimal += uint32_t(remainder * 2);
|
||||||
|
if (decimal >= maxDecimalPart) {
|
||||||
|
decimal = 0;
|
||||||
|
integral++;
|
||||||
|
if (exponent && integral >= 10) {
|
||||||
|
exponent++;
|
||||||
|
integral = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove trailing zeros
|
||||||
|
while (decimal % 10 == 0 && decimalPlaces > 0) {
|
||||||
|
decimal /= 10;
|
||||||
|
decimalPlaces--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {integral, decimal, exponent, decimalPlaces};
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h> // for size_t
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/alias_cast.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/math.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/pgmspace_generic.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/preprocessor.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename T, size_t = sizeof(T)>
|
||||||
|
struct FloatTraits {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FloatTraits<T, 8 /*64bits*/> {
|
||||||
|
using mantissa_type = uint64_t;
|
||||||
|
static const short mantissa_bits = 52;
|
||||||
|
static const mantissa_type mantissa_max =
|
||||||
|
(mantissa_type(1) << mantissa_bits) - 1;
|
||||||
|
|
||||||
|
using exponent_type = int16_t;
|
||||||
|
static const exponent_type exponent_max = 308;
|
||||||
|
|
||||||
|
static const size_t binaryPowersOfTen = 9;
|
||||||
|
|
||||||
|
static pgm_ptr<T> positiveBinaryPowersOfTen() {
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY( //
|
||||||
|
uint64_t, factors,
|
||||||
|
{
|
||||||
|
0x4024000000000000, // 1e1
|
||||||
|
0x4059000000000000, // 1e2
|
||||||
|
0x40C3880000000000, // 1e4
|
||||||
|
0x4197D78400000000, // 1e8
|
||||||
|
0x4341C37937E08000, // 1e16
|
||||||
|
0x4693B8B5B5056E17, // 1e32
|
||||||
|
0x4D384F03E93FF9F5, // 1e64
|
||||||
|
0x5A827748F9301D32, // 1e128
|
||||||
|
0x75154FDD7F73BF3C, // 1e256
|
||||||
|
});
|
||||||
|
return pgm_ptr<T>(reinterpret_cast<const T*>(factors));
|
||||||
|
}
|
||||||
|
|
||||||
|
static pgm_ptr<T> negativeBinaryPowersOfTen() {
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY( //
|
||||||
|
uint64_t, factors,
|
||||||
|
{
|
||||||
|
0x3FB999999999999A, // 1e-1
|
||||||
|
0x3F847AE147AE147B, // 1e-2
|
||||||
|
0x3F1A36E2EB1C432D, // 1e-4
|
||||||
|
0x3E45798EE2308C3A, // 1e-8
|
||||||
|
0x3C9CD2B297D889BC, // 1e-16
|
||||||
|
0x3949F623D5A8A733, // 1e-32
|
||||||
|
0x32A50FFD44F4A73D, // 1e-64
|
||||||
|
0x255BBA08CF8C979D, // 1e-128
|
||||||
|
0x0AC8062864AC6F43 // 1e-256
|
||||||
|
});
|
||||||
|
return pgm_ptr<T>(reinterpret_cast<const T*>(factors));
|
||||||
|
}
|
||||||
|
|
||||||
|
static T nan() {
|
||||||
|
return forge(0x7ff8000000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static T inf() {
|
||||||
|
return forge(0x7ff0000000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static T highest() {
|
||||||
|
return forge(0x7FEFFFFFFFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TOut> // int64_t
|
||||||
|
static T highest_for(
|
||||||
|
enable_if_t<is_integral<TOut>::value && is_signed<TOut>::value &&
|
||||||
|
sizeof(TOut) == 8,
|
||||||
|
signed>* = 0) {
|
||||||
|
return forge(0x43DFFFFFFFFFFFFF); // 9.2233720368547748e+18
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TOut> // uint64_t
|
||||||
|
static T highest_for(
|
||||||
|
enable_if_t<is_integral<TOut>::value && is_unsigned<TOut>::value &&
|
||||||
|
sizeof(TOut) == 8,
|
||||||
|
unsigned>* = 0) {
|
||||||
|
return forge(0x43EFFFFFFFFFFFFF); // 1.8446744073709549568e+19
|
||||||
|
}
|
||||||
|
|
||||||
|
static T lowest() {
|
||||||
|
return forge(0xFFEFFFFFFFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructs a double floating point values from its binary representation
|
||||||
|
// we use this function to workaround platforms with single precision literals
|
||||||
|
// (for example, when -fsingle-precision-constant is passed to GCC)
|
||||||
|
static T forge(uint64_t bits) {
|
||||||
|
return alias_cast<T>(bits);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FloatTraits<T, 4 /*32bits*/> {
|
||||||
|
using mantissa_type = uint32_t;
|
||||||
|
static const short mantissa_bits = 23;
|
||||||
|
static const mantissa_type mantissa_max =
|
||||||
|
(mantissa_type(1) << mantissa_bits) - 1;
|
||||||
|
|
||||||
|
using exponent_type = int8_t;
|
||||||
|
static const exponent_type exponent_max = 38;
|
||||||
|
|
||||||
|
static const size_t binaryPowersOfTen = 6;
|
||||||
|
|
||||||
|
static pgm_ptr<T> positiveBinaryPowersOfTen() {
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors,
|
||||||
|
{
|
||||||
|
0x41200000, // 1e1f
|
||||||
|
0x42c80000, // 1e2f
|
||||||
|
0x461c4000, // 1e4f
|
||||||
|
0x4cbebc20, // 1e8f
|
||||||
|
0x5a0e1bca, // 1e16f
|
||||||
|
0x749dc5ae // 1e32f
|
||||||
|
});
|
||||||
|
return pgm_ptr<T>(reinterpret_cast<const T*>(factors));
|
||||||
|
}
|
||||||
|
|
||||||
|
static pgm_ptr<T> negativeBinaryPowersOfTen() {
|
||||||
|
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors,
|
||||||
|
{
|
||||||
|
0x3dcccccd, // 1e-1f
|
||||||
|
0x3c23d70a, // 1e-2f
|
||||||
|
0x38d1b717, // 1e-4f
|
||||||
|
0x322bcc77, // 1e-8f
|
||||||
|
0x24e69595, // 1e-16f
|
||||||
|
0x0a4fb11f // 1e-32f
|
||||||
|
});
|
||||||
|
return pgm_ptr<T>(reinterpret_cast<const T*>(factors));
|
||||||
|
}
|
||||||
|
|
||||||
|
static T forge(uint32_t bits) {
|
||||||
|
return alias_cast<T>(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
static T nan() {
|
||||||
|
return forge(0x7fc00000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static T inf() {
|
||||||
|
return forge(0x7f800000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static T highest() {
|
||||||
|
return forge(0x7f7fffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TOut> // int32_t
|
||||||
|
static T highest_for(
|
||||||
|
enable_if_t<is_integral<TOut>::value && is_signed<TOut>::value &&
|
||||||
|
sizeof(TOut) == 4,
|
||||||
|
signed>* = 0) {
|
||||||
|
return forge(0x4EFFFFFF); // 2.14748352E9
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TOut> // uint32_t
|
||||||
|
static T highest_for(
|
||||||
|
enable_if_t<is_integral<TOut>::value && is_unsigned<TOut>::value &&
|
||||||
|
sizeof(TOut) == 4,
|
||||||
|
unsigned>* = 0) {
|
||||||
|
return forge(0x4F7FFFFF); // 4.29496704E9
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TOut> // int64_t
|
||||||
|
static T highest_for(
|
||||||
|
enable_if_t<is_integral<TOut>::value && is_signed<TOut>::value &&
|
||||||
|
sizeof(TOut) == 8,
|
||||||
|
signed>* = 0) {
|
||||||
|
return forge(0x5EFFFFFF); // 9.22337148709896192E18
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TOut> // uint64_t
|
||||||
|
static T highest_for(
|
||||||
|
enable_if_t<is_integral<TOut>::value && is_unsigned<TOut>::value &&
|
||||||
|
sizeof(TOut) == 8,
|
||||||
|
unsigned>* = 0) {
|
||||||
|
return forge(0x5F7FFFFF); // 1.844674297419792384E19
|
||||||
|
}
|
||||||
|
|
||||||
|
static T lowest() {
|
||||||
|
return forge(0xFf7fffff);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TFloat, typename TExponent>
|
||||||
|
inline TFloat make_float(TFloat m, TExponent e) {
|
||||||
|
using traits = FloatTraits<TFloat>;
|
||||||
|
|
||||||
|
auto powersOfTen = e > 0 ? traits::positiveBinaryPowersOfTen()
|
||||||
|
: traits::negativeBinaryPowersOfTen();
|
||||||
|
auto count = traits::binaryPowersOfTen;
|
||||||
|
|
||||||
|
if (e <= 0)
|
||||||
|
e = TExponent(-e);
|
||||||
|
|
||||||
|
for (uint8_t index = 0; e != 0; index++) {
|
||||||
|
if (index >= count)
|
||||||
|
return traits::nan();
|
||||||
|
if (e & 1)
|
||||||
|
m *= powersOfTen[index];
|
||||||
|
e >>= 1;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
using JsonFloat = double;
|
||||||
|
#else
|
||||||
|
using JsonFloat = float;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
#include <stdint.h> // int64_t
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_LONG_LONG
|
||||||
|
using JsonInteger = int64_t;
|
||||||
|
using JsonUInt = uint64_t;
|
||||||
|
#else
|
||||||
|
using JsonInteger = long;
|
||||||
|
using JsonUInt = unsigned long;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
#define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) \
|
||||||
|
static_assert(sizeof(T) <= sizeof(ArduinoJson::JsonInteger), \
|
||||||
|
"To use 64-bit integers with ArduinoJson, you must set " \
|
||||||
|
"ARDUINOJSON_USE_LONG_LONG to 1. See " \
|
||||||
|
"https://arduinojson.org/v7/api/config/use_long_long/");
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Numbers/JsonInteger.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
enum CompareResult {
|
||||||
|
COMPARE_RESULT_DIFFER = 0,
|
||||||
|
COMPARE_RESULT_EQUAL = 1,
|
||||||
|
COMPARE_RESULT_GREATER = 2,
|
||||||
|
COMPARE_RESULT_LESS = 4,
|
||||||
|
|
||||||
|
COMPARE_RESULT_GREATER_OR_EQUAL = 3,
|
||||||
|
COMPARE_RESULT_LESS_OR_EQUAL = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
CompareResult arithmeticCompare(const T& lhs, const T& rhs) {
|
||||||
|
if (lhs < rhs)
|
||||||
|
return COMPARE_RESULT_LESS;
|
||||||
|
else if (lhs > rhs)
|
||||||
|
return COMPARE_RESULT_GREATER;
|
||||||
|
else
|
||||||
|
return COMPARE_RESULT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
CompareResult arithmeticCompare(
|
||||||
|
const T1& lhs, const T2& rhs,
|
||||||
|
enable_if_t<is_integral<T1>::value && is_integral<T2>::value &&
|
||||||
|
sizeof(T1) < sizeof(T2)>* = 0) {
|
||||||
|
return arithmeticCompare<T2>(static_cast<T2>(lhs), rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
CompareResult arithmeticCompare(
|
||||||
|
const T1& lhs, const T2& rhs,
|
||||||
|
enable_if_t<is_integral<T1>::value && is_integral<T2>::value &&
|
||||||
|
sizeof(T2) < sizeof(T1)>* = 0) {
|
||||||
|
return arithmeticCompare<T1>(lhs, static_cast<T1>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
CompareResult arithmeticCompare(
|
||||||
|
const T1& lhs, const T2& rhs,
|
||||||
|
enable_if_t<is_integral<T1>::value && is_integral<T2>::value &&
|
||||||
|
is_signed<T1>::value == is_signed<T2>::value &&
|
||||||
|
sizeof(T2) == sizeof(T1)>* = 0) {
|
||||||
|
return arithmeticCompare<T1>(lhs, static_cast<T1>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
CompareResult arithmeticCompare(
|
||||||
|
const T1& lhs, const T2& rhs,
|
||||||
|
enable_if_t<is_integral<T1>::value && is_integral<T2>::value &&
|
||||||
|
is_unsigned<T1>::value && is_signed<T2>::value &&
|
||||||
|
sizeof(T2) == sizeof(T1)>* = 0) {
|
||||||
|
if (rhs < 0)
|
||||||
|
return COMPARE_RESULT_GREATER;
|
||||||
|
return arithmeticCompare<T1>(lhs, static_cast<T1>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
CompareResult arithmeticCompare(
|
||||||
|
const T1& lhs, const T2& rhs,
|
||||||
|
enable_if_t<is_integral<T1>::value && is_integral<T2>::value &&
|
||||||
|
is_signed<T1>::value && is_unsigned<T2>::value &&
|
||||||
|
sizeof(T2) == sizeof(T1)>* = 0) {
|
||||||
|
if (lhs < 0)
|
||||||
|
return COMPARE_RESULT_LESS;
|
||||||
|
return arithmeticCompare<T2>(static_cast<T2>(lhs), rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
CompareResult arithmeticCompare(
|
||||||
|
const T1& lhs, const T2& rhs,
|
||||||
|
enable_if_t<is_floating_point<T1>::value || is_floating_point<T2>::value>* =
|
||||||
|
0) {
|
||||||
|
return arithmeticCompare<double>(static_cast<double>(lhs),
|
||||||
|
static_cast<double>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T2>
|
||||||
|
CompareResult arithmeticCompareNegateLeft(
|
||||||
|
JsonUInt, const T2&, enable_if_t<is_unsigned<T2>::value>* = 0) {
|
||||||
|
return COMPARE_RESULT_LESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T2>
|
||||||
|
CompareResult arithmeticCompareNegateLeft(
|
||||||
|
JsonUInt lhs, const T2& rhs, enable_if_t<is_signed<T2>::value>* = 0) {
|
||||||
|
if (rhs > 0)
|
||||||
|
return COMPARE_RESULT_LESS;
|
||||||
|
return arithmeticCompare(-rhs, static_cast<T2>(lhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
CompareResult arithmeticCompareNegateRight(
|
||||||
|
const T1&, JsonUInt, enable_if_t<is_unsigned<T1>::value>* = 0) {
|
||||||
|
return COMPARE_RESULT_GREATER;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1>
|
||||||
|
CompareResult arithmeticCompareNegateRight(
|
||||||
|
const T1& lhs, JsonUInt rhs, enable_if_t<is_signed<T1>::value>* = 0) {
|
||||||
|
if (lhs > 0)
|
||||||
|
return COMPARE_RESULT_GREATER;
|
||||||
|
return arithmeticCompare(static_cast<T1>(rhs), -lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
# pragma clang diagnostic push
|
||||||
|
# pragma clang diagnostic ignored "-Wconversion"
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
# pragma GCC diagnostic push
|
||||||
|
# pragma GCC diagnostic ignored "-Wconversion"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <ArduinoJson/Numbers/FloatTraits.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/JsonFloat.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/limits.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// uint32 -> int32
|
||||||
|
// uint64 -> int32
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_integral<TIn>::value && is_unsigned<TIn>::value &&
|
||||||
|
is_integral<TOut>::value && sizeof(TOut) <= sizeof(TIn),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn value) {
|
||||||
|
return value <= TIn(numeric_limits<TOut>::highest());
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint32 -> int64
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_integral<TIn>::value && is_unsigned<TIn>::value &&
|
||||||
|
is_integral<TOut>::value && sizeof(TIn) < sizeof(TOut),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint32 -> float
|
||||||
|
// int32 -> float
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_integral<TIn>::value && is_floating_point<TOut>::value, bool>
|
||||||
|
canConvertNumber(TIn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// int64 -> int32
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_integral<TIn>::value && is_signed<TIn>::value &&
|
||||||
|
is_integral<TOut>::value && is_signed<TOut>::value &&
|
||||||
|
sizeof(TOut) < sizeof(TIn),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn value) {
|
||||||
|
return value >= TIn(numeric_limits<TOut>::lowest()) &&
|
||||||
|
value <= TIn(numeric_limits<TOut>::highest());
|
||||||
|
}
|
||||||
|
|
||||||
|
// int32 -> int32
|
||||||
|
// int32 -> int64
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_integral<TIn>::value && is_signed<TIn>::value &&
|
||||||
|
is_integral<TOut>::value && is_signed<TOut>::value &&
|
||||||
|
sizeof(TIn) <= sizeof(TOut),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// int32 -> uint32
|
||||||
|
// int32 -> uint64
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_integral<TIn>::value && is_signed<TIn>::value &&
|
||||||
|
is_integral<TOut>::value && is_unsigned<TOut>::value &&
|
||||||
|
sizeof(TOut) >= sizeof(TIn),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn value) {
|
||||||
|
if (value < 0)
|
||||||
|
return false;
|
||||||
|
return TOut(value) <= numeric_limits<TOut>::highest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// int32 -> uint16
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_integral<TIn>::value && is_signed<TIn>::value &&
|
||||||
|
is_integral<TOut>::value && is_unsigned<TOut>::value &&
|
||||||
|
sizeof(TOut) < sizeof(TIn),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn value) {
|
||||||
|
if (value < 0)
|
||||||
|
return false;
|
||||||
|
return value <= TIn(numeric_limits<TOut>::highest());
|
||||||
|
}
|
||||||
|
|
||||||
|
// float32 -> int16
|
||||||
|
// float64 -> int32
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_floating_point<TIn>::value && is_integral<TOut>::value &&
|
||||||
|
sizeof(TOut) < sizeof(TIn),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn value) {
|
||||||
|
return value >= numeric_limits<TOut>::lowest() &&
|
||||||
|
value <= numeric_limits<TOut>::highest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// float32 -> int32
|
||||||
|
// float32 -> uint32
|
||||||
|
// float32 -> int64
|
||||||
|
// float32 -> uint64
|
||||||
|
// float64 -> int64
|
||||||
|
// float64 -> uint64
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_floating_point<TIn>::value && is_integral<TOut>::value &&
|
||||||
|
sizeof(TOut) >= sizeof(TIn),
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn value) {
|
||||||
|
// Avoid error "9.22337e+18 is outside the range of representable values of
|
||||||
|
// type 'long'"
|
||||||
|
return value >= numeric_limits<TOut>::lowest() &&
|
||||||
|
value <= FloatTraits<TIn>::template highest_for<TOut>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// float32 -> float32
|
||||||
|
// float64 -> float64
|
||||||
|
// float64 -> float32
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
enable_if_t<is_floating_point<TIn>::value && is_floating_point<TOut>::value,
|
||||||
|
bool>
|
||||||
|
canConvertNumber(TIn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TOut, typename TIn>
|
||||||
|
TOut convertNumber(TIn value) {
|
||||||
|
return canConvertNumber<TOut>(value) ? TOut(value) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
# pragma clang diagnostic pop
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
# pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Numbers/FloatTraits.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/JsonFloat.hpp>
|
||||||
|
#include <ArduinoJson/Numbers/convertNumber.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/ctype.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/math.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/type_traits.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename A, typename B>
|
||||||
|
using largest_type = conditional_t<(sizeof(A) > sizeof(B)), A, B>;
|
||||||
|
|
||||||
|
enum class NumberType : uint8_t {
|
||||||
|
Invalid,
|
||||||
|
Float,
|
||||||
|
SignedInteger,
|
||||||
|
UnsignedInteger,
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
Double,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
union NumberValue {
|
||||||
|
NumberValue() {}
|
||||||
|
NumberValue(float x) : asFloat(x) {}
|
||||||
|
NumberValue(JsonInteger x) : asSignedInteger(x) {}
|
||||||
|
NumberValue(JsonUInt x) : asUnsignedInteger(x) {}
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
NumberValue(double x) : asDouble(x) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonInteger asSignedInteger;
|
||||||
|
JsonUInt asUnsignedInteger;
|
||||||
|
float asFloat;
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
double asDouble;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class Number {
|
||||||
|
NumberType type_;
|
||||||
|
NumberValue value_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Number() : type_(NumberType::Invalid) {}
|
||||||
|
Number(float value) : type_(NumberType::Float), value_(value) {}
|
||||||
|
Number(JsonInteger value) : type_(NumberType::SignedInteger), value_(value) {}
|
||||||
|
Number(JsonUInt value) : type_(NumberType::UnsignedInteger), value_(value) {}
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
Number(double value) : type_(NumberType::Double), value_(value) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T convertTo() const {
|
||||||
|
switch (type_) {
|
||||||
|
case NumberType::Float:
|
||||||
|
return convertNumber<T>(value_.asFloat);
|
||||||
|
case NumberType::SignedInteger:
|
||||||
|
return convertNumber<T>(value_.asSignedInteger);
|
||||||
|
case NumberType::UnsignedInteger:
|
||||||
|
return convertNumber<T>(value_.asUnsignedInteger);
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
case NumberType::Double:
|
||||||
|
return convertNumber<T>(value_.asDouble);
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberType type() const {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonInteger asSignedInteger() const {
|
||||||
|
ARDUINOJSON_ASSERT(type_ == NumberType::SignedInteger);
|
||||||
|
return value_.asSignedInteger;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonUInt asUnsignedInteger() const {
|
||||||
|
ARDUINOJSON_ASSERT(type_ == NumberType::UnsignedInteger);
|
||||||
|
return value_.asUnsignedInteger;
|
||||||
|
}
|
||||||
|
|
||||||
|
float asFloat() const {
|
||||||
|
ARDUINOJSON_ASSERT(type_ == NumberType::Float);
|
||||||
|
return value_.asFloat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
double asDouble() const {
|
||||||
|
ARDUINOJSON_ASSERT(type_ == NumberType::Double);
|
||||||
|
return value_.asDouble;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Number parseNumber(const char* s) {
|
||||||
|
using traits = FloatTraits<JsonFloat>;
|
||||||
|
using mantissa_t = largest_type<traits::mantissa_type, JsonUInt>;
|
||||||
|
using exponent_t = traits::exponent_type;
|
||||||
|
|
||||||
|
ARDUINOJSON_ASSERT(s != 0);
|
||||||
|
|
||||||
|
bool is_negative = false;
|
||||||
|
switch (*s) {
|
||||||
|
case '-':
|
||||||
|
is_negative = true;
|
||||||
|
s++;
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
s++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_NAN
|
||||||
|
if (*s == 'n' || *s == 'N') {
|
||||||
|
return Number(traits::nan());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ARDUINOJSON_ENABLE_INFINITY
|
||||||
|
if (*s == 'i' || *s == 'I') {
|
||||||
|
return Number(is_negative ? -traits::inf() : traits::inf());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!isdigit(*s) && *s != '.')
|
||||||
|
return Number();
|
||||||
|
|
||||||
|
mantissa_t mantissa = 0;
|
||||||
|
exponent_t exponent_offset = 0;
|
||||||
|
const mantissa_t maxUint = JsonUInt(-1);
|
||||||
|
|
||||||
|
while (isdigit(*s)) {
|
||||||
|
uint8_t digit = uint8_t(*s - '0');
|
||||||
|
if (mantissa > maxUint / 10)
|
||||||
|
break;
|
||||||
|
mantissa *= 10;
|
||||||
|
if (mantissa > maxUint - digit)
|
||||||
|
break;
|
||||||
|
mantissa += digit;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*s == '\0') {
|
||||||
|
if (is_negative) {
|
||||||
|
const mantissa_t sintMantissaMax = mantissa_t(1)
|
||||||
|
<< (sizeof(JsonInteger) * 8 - 1);
|
||||||
|
if (mantissa <= sintMantissaMax) {
|
||||||
|
return Number(JsonInteger(~mantissa + 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Number(JsonUInt(mantissa));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid mantissa overflow
|
||||||
|
while (mantissa > traits::mantissa_max) {
|
||||||
|
mantissa /= 10;
|
||||||
|
exponent_offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remaing digits can't fit in the mantissa
|
||||||
|
while (isdigit(*s)) {
|
||||||
|
exponent_offset++;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*s == '.') {
|
||||||
|
s++;
|
||||||
|
while (isdigit(*s)) {
|
||||||
|
if (mantissa < traits::mantissa_max / 10) {
|
||||||
|
mantissa = mantissa * 10 + uint8_t(*s - '0');
|
||||||
|
exponent_offset--;
|
||||||
|
}
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int exponent = 0;
|
||||||
|
if (*s == 'e' || *s == 'E') {
|
||||||
|
s++;
|
||||||
|
bool negative_exponent = false;
|
||||||
|
if (*s == '-') {
|
||||||
|
negative_exponent = true;
|
||||||
|
s++;
|
||||||
|
} else if (*s == '+') {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isdigit(*s)) {
|
||||||
|
exponent = exponent * 10 + (*s - '0');
|
||||||
|
if (exponent + exponent_offset > traits::exponent_max) {
|
||||||
|
if (negative_exponent)
|
||||||
|
return Number(is_negative ? -0.0f : 0.0f);
|
||||||
|
else
|
||||||
|
return Number(is_negative ? -traits::inf() : traits::inf());
|
||||||
|
}
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
if (negative_exponent)
|
||||||
|
exponent = -exponent;
|
||||||
|
}
|
||||||
|
exponent += exponent_offset;
|
||||||
|
|
||||||
|
// we should be at the end of the string, otherwise it's an error
|
||||||
|
if (*s != '\0')
|
||||||
|
return Number();
|
||||||
|
|
||||||
|
#if ARDUINOJSON_USE_DOUBLE
|
||||||
|
bool isDouble = exponent < -FloatTraits<float>::exponent_max ||
|
||||||
|
exponent > FloatTraits<float>::exponent_max ||
|
||||||
|
mantissa > FloatTraits<float>::mantissa_max;
|
||||||
|
if (isDouble) {
|
||||||
|
auto final_result = make_float(double(mantissa), exponent);
|
||||||
|
return Number(is_negative ? -final_result : final_result);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
auto final_result = make_float(float(mantissa), exponent);
|
||||||
|
return Number(is_negative ? -final_result : final_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T parseNumber(const char* s) {
|
||||||
|
return parseNumber(s).convertTo<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Object/JsonObjectConst.hpp>
|
||||||
|
#include <ArduinoJson/Object/MemberProxy.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class JsonArray;
|
||||||
|
|
||||||
|
// A reference to an object in a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/
|
||||||
|
class JsonObject : public detail::VariantOperators<JsonObject> {
|
||||||
|
friend class detail::VariantAttorney;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator = JsonObjectIterator;
|
||||||
|
|
||||||
|
// Creates an unbound reference.
|
||||||
|
JsonObject() : data_(0), resources_(0) {}
|
||||||
|
|
||||||
|
// INTERNAL USE ONLY
|
||||||
|
JsonObject(detail::ObjectData* data, detail::ResourceManager* resource)
|
||||||
|
: data_(data), resources_(resource) {}
|
||||||
|
|
||||||
|
operator JsonVariant() const {
|
||||||
|
void* data = data_; // prevent warning cast-align
|
||||||
|
return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
operator JsonObjectConst() const {
|
||||||
|
return JsonObjectConst(data_, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
operator JsonVariantConst() const {
|
||||||
|
return JsonVariantConst(collectionToVariant(data_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is unbound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/isnull/
|
||||||
|
bool isNull() const {
|
||||||
|
return data_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is bound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/isnull/
|
||||||
|
operator bool() const {
|
||||||
|
return data_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the depth (nesting level) of the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/nesting/
|
||||||
|
size_t nesting() const {
|
||||||
|
return detail::VariantData::nesting(collectionToVariant(data_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of members in the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/size/
|
||||||
|
size_t size() const {
|
||||||
|
return data_ ? data_->size(resources_) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator to the first key-value pair of the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/begin/
|
||||||
|
iterator begin() const {
|
||||||
|
if (!data_)
|
||||||
|
return iterator();
|
||||||
|
return iterator(data_->createIterator(resources_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator following the last key-value pair of the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/end/
|
||||||
|
iterator end() const {
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes all the members of the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/clear/
|
||||||
|
void clear() const {
|
||||||
|
detail::ObjectData::clear(data_, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies an object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/set/
|
||||||
|
bool set(JsonObjectConst src) {
|
||||||
|
if (!data_ || !src.data_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
clear();
|
||||||
|
for (auto kvp : src) {
|
||||||
|
if (!operator[](kvp.key()).set(kvp.value()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets the member with specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/subscript/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
detail::MemberProxy<JsonObject, detail::AdaptedString<TString>> operator[](
|
||||||
|
const TString& key) const {
|
||||||
|
return {*this, detail::adaptString(key)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets the member with specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/subscript/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<detail::IsString<TChar*>::value &&
|
||||||
|
!detail::is_const<TChar>::value,
|
||||||
|
int> = 0>
|
||||||
|
detail::MemberProxy<JsonObject, detail::AdaptedString<TChar*>> operator[](
|
||||||
|
TChar* key) const {
|
||||||
|
return {*this, detail::adaptString(key)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets or sets the member with specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/subscript/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
detail::MemberProxy<JsonObject, detail::AdaptedString<JsonString>> operator[](
|
||||||
|
const TVariant& key) const {
|
||||||
|
return {*this, detail::adaptString(key.template as<JsonString>())};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the member at the specified iterator.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/remove/
|
||||||
|
FORCE_INLINE void remove(iterator it) const {
|
||||||
|
detail::ObjectData::remove(data_, it.iterator_, resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the member with the specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/remove/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
void remove(const TString& key) const {
|
||||||
|
detail::ObjectData::removeMember(data_, detail::adaptString(key),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the member with the specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/remove/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
void remove(const TVariant& key) const {
|
||||||
|
if (key.template is<const char*>())
|
||||||
|
remove(key.template as<const char*>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the member with the specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/remove/
|
||||||
|
template <typename TChar>
|
||||||
|
FORCE_INLINE void remove(TChar* key) const {
|
||||||
|
detail::ObjectData::removeMember(data_, detail::adaptString(key),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/containskey/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].is<T>() instead")
|
||||||
|
bool containsKey(const TString& key) const {
|
||||||
|
return detail::ObjectData::getMember(data_, detail::adaptString(key),
|
||||||
|
resources_) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj["key"].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/containskey/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<detail::IsString<TChar*>::value &&
|
||||||
|
!detail::is_const<TChar>::value,
|
||||||
|
int> = 0>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[\"key\"].is<T>() instead")
|
||||||
|
bool containsKey(TChar* key) const {
|
||||||
|
return detail::ObjectData::getMember(data_, detail::adaptString(key),
|
||||||
|
resources_) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/containskey/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].is<T>() instead")
|
||||||
|
bool containsKey(const TVariant& key) const {
|
||||||
|
return containsKey(key.template as<const char*>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].to<JsonArray>() instead
|
||||||
|
template <typename TChar>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].to<JsonArray>() instead")
|
||||||
|
JsonArray createNestedArray(TChar* key) const {
|
||||||
|
return operator[](key).template to<JsonArray>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].to<JsonArray>() instead
|
||||||
|
template <typename TString>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].to<JsonArray>() instead")
|
||||||
|
JsonArray createNestedArray(const TString& key) const {
|
||||||
|
return operator[](key).template to<JsonArray>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].to<JsonObject>() instead
|
||||||
|
template <typename TChar>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].to<JsonObject>() instead")
|
||||||
|
JsonObject createNestedObject(TChar* key) {
|
||||||
|
return operator[](key).template to<JsonObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].to<JsonObject>() instead
|
||||||
|
template <typename TString>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].to<JsonObject>() instead")
|
||||||
|
JsonObject createNestedObject(const TString& key) {
|
||||||
|
return operator[](key).template to<JsonObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: always returns zero
|
||||||
|
ARDUINOJSON_DEPRECATED("always returns zero")
|
||||||
|
size_t memoryUsage() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
detail::ResourceManager* getResourceManager() const {
|
||||||
|
return resources_;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::VariantData* getData() const {
|
||||||
|
return detail::collectionToVariant(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::VariantData* getOrCreateData() const {
|
||||||
|
return detail::collectionToVariant(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::ObjectData* data_;
|
||||||
|
detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Object/JsonObjectIterator.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantOperators.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// A read-only reference to an object in a JsonDocument.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/
|
||||||
|
class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
|
||||||
|
friend class JsonObject;
|
||||||
|
friend class detail::VariantAttorney;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator = JsonObjectConstIterator;
|
||||||
|
|
||||||
|
// Creates an unbound reference.
|
||||||
|
JsonObjectConst() : data_(0), resources_(0) {}
|
||||||
|
|
||||||
|
// INTERNAL USE ONLY
|
||||||
|
JsonObjectConst(const detail::ObjectData* data,
|
||||||
|
const detail::ResourceManager* resources)
|
||||||
|
: data_(data), resources_(resources) {}
|
||||||
|
|
||||||
|
operator JsonVariantConst() const {
|
||||||
|
return JsonVariantConst(getData(), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is unbound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/isnull/
|
||||||
|
bool isNull() const {
|
||||||
|
return data_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the reference is bound.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/isnull/
|
||||||
|
operator bool() const {
|
||||||
|
return data_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the depth (nesting level) of the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/nesting/
|
||||||
|
size_t nesting() const {
|
||||||
|
return detail::VariantData::nesting(getData(), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of members in the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/size/
|
||||||
|
size_t size() const {
|
||||||
|
return data_ ? data_->size(resources_) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator to the first key-value pair of the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/begin/
|
||||||
|
iterator begin() const {
|
||||||
|
if (!data_)
|
||||||
|
return iterator();
|
||||||
|
return iterator(data_->createIterator(resources_), resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator following the last key-value pair of the object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/end/
|
||||||
|
iterator end() const {
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/containskey/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].is<T>() instead")
|
||||||
|
bool containsKey(const TString& key) const {
|
||||||
|
return detail::ObjectData::getMember(data_, detail::adaptString(key),
|
||||||
|
resources_) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj["key"].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/containskey/
|
||||||
|
template <typename TChar>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[\"key\"].is<T>() instead")
|
||||||
|
bool containsKey(TChar* key) const {
|
||||||
|
return detail::ObjectData::getMember(data_, detail::adaptString(key),
|
||||||
|
resources_) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: use obj[key].is<T>() instead
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/containskey/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
ARDUINOJSON_DEPRECATED("use obj[key].is<T>() instead")
|
||||||
|
bool containsKey(const TVariant& key) const {
|
||||||
|
return containsKey(key.template as<const char*>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the member with specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/subscript/
|
||||||
|
template <typename TString,
|
||||||
|
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
|
||||||
|
JsonVariantConst operator[](const TString& key) const {
|
||||||
|
return JsonVariantConst(detail::ObjectData::getMember(
|
||||||
|
data_, detail::adaptString(key), resources_),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the member with specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/subscript/
|
||||||
|
template <typename TChar,
|
||||||
|
detail::enable_if_t<detail::IsString<TChar*>::value &&
|
||||||
|
!detail::is_const<TChar>::value,
|
||||||
|
int> = 0>
|
||||||
|
JsonVariantConst operator[](TChar* key) const {
|
||||||
|
return JsonVariantConst(detail::ObjectData::getMember(
|
||||||
|
data_, detail::adaptString(key), resources_),
|
||||||
|
resources_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the member with specified key.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/subscript/
|
||||||
|
template <typename TVariant,
|
||||||
|
detail::enable_if_t<detail::IsVariant<TVariant>::value, int> = 0>
|
||||||
|
JsonVariantConst operator[](const TVariant& key) const {
|
||||||
|
if (key.template is<JsonString>())
|
||||||
|
return operator[](key.template as<JsonString>());
|
||||||
|
else
|
||||||
|
return JsonVariantConst();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: always returns zero
|
||||||
|
ARDUINOJSON_DEPRECATED("always returns zero")
|
||||||
|
size_t memoryUsage() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const detail::VariantData* getData() const {
|
||||||
|
return collectionToVariant(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detail::ObjectData* data_;
|
||||||
|
const detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool operator==(JsonObjectConst lhs, JsonObjectConst rhs) {
|
||||||
|
if (!lhs && !rhs) // both are null
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!lhs || !rhs) // only one is null
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
for (auto kvp : lhs) {
|
||||||
|
auto rhsValue = rhs[kvp.key()];
|
||||||
|
if (rhsValue.isUnbound())
|
||||||
|
return false;
|
||||||
|
if (kvp.value() != rhsValue)
|
||||||
|
return false;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count == rhs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Object/JsonPair.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
class JsonObjectIterator {
|
||||||
|
friend class JsonObject;
|
||||||
|
|
||||||
|
public:
|
||||||
|
JsonObjectIterator() {}
|
||||||
|
|
||||||
|
explicit JsonObjectIterator(detail::ObjectData::iterator iterator,
|
||||||
|
detail::ResourceManager* resources)
|
||||||
|
: iterator_(iterator), resources_(resources) {}
|
||||||
|
|
||||||
|
JsonPair operator*() const {
|
||||||
|
return JsonPair(iterator_, resources_);
|
||||||
|
}
|
||||||
|
Ptr<JsonPair> operator->() {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const JsonObjectIterator& other) const {
|
||||||
|
return iterator_ == other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const JsonObjectIterator& other) const {
|
||||||
|
return iterator_ != other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObjectIterator& operator++() {
|
||||||
|
iterator_.next(resources_); // key
|
||||||
|
iterator_.next(resources_); // value
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
detail::ObjectData::iterator iterator_;
|
||||||
|
detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class JsonObjectConstIterator {
|
||||||
|
friend class JsonObject;
|
||||||
|
|
||||||
|
public:
|
||||||
|
JsonObjectConstIterator() {}
|
||||||
|
|
||||||
|
explicit JsonObjectConstIterator(detail::ObjectData::iterator iterator,
|
||||||
|
const detail::ResourceManager* resources)
|
||||||
|
: iterator_(iterator), resources_(resources) {}
|
||||||
|
|
||||||
|
JsonPairConst operator*() const {
|
||||||
|
return JsonPairConst(iterator_, resources_);
|
||||||
|
}
|
||||||
|
Ptr<JsonPairConst> operator->() {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const JsonObjectConstIterator& other) const {
|
||||||
|
return iterator_ == other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const JsonObjectConstIterator& other) const {
|
||||||
|
return iterator_ != other.iterator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObjectConstIterator& operator++() {
|
||||||
|
iterator_.next(resources_); // key
|
||||||
|
iterator_.next(resources_); // value
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
detail::ObjectData::iterator iterator_;
|
||||||
|
const detail::ResourceManager* resources_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Strings/JsonString.hpp>
|
||||||
|
#include <ArduinoJson/Variant/JsonVariant.hpp>
|
||||||
|
#include <ArduinoJson/Variant/JsonVariantConst.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
||||||
|
|
||||||
|
// A key-value pair.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/begin_end/
|
||||||
|
class JsonPair {
|
||||||
|
public:
|
||||||
|
// INTERNAL USE ONLY
|
||||||
|
JsonPair(detail::ObjectData::iterator iterator,
|
||||||
|
detail::ResourceManager* resources) {
|
||||||
|
if (!iterator.done()) {
|
||||||
|
key_ = iterator->asString();
|
||||||
|
iterator.next(resources);
|
||||||
|
value_ = JsonVariant(iterator.data(), resources);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the key.
|
||||||
|
JsonString key() const {
|
||||||
|
return key_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the value.
|
||||||
|
JsonVariant value() {
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JsonString key_;
|
||||||
|
JsonVariant value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A read-only key-value pair.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobjectconst/begin_end/
|
||||||
|
class JsonPairConst {
|
||||||
|
public:
|
||||||
|
JsonPairConst(detail::ObjectData::iterator iterator,
|
||||||
|
const detail::ResourceManager* resources) {
|
||||||
|
if (!iterator.done()) {
|
||||||
|
key_ = iterator->asString();
|
||||||
|
iterator.next(resources);
|
||||||
|
value_ = JsonVariantConst(iterator.data(), resources);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the key.
|
||||||
|
JsonString key() const {
|
||||||
|
return key_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the value.
|
||||||
|
JsonVariantConst value() const {
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JsonString key_;
|
||||||
|
JsonVariantConst value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Variant/VariantRefBase.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// A proxy class to get or set a member of an object.
|
||||||
|
// https://arduinojson.org/v7/api/jsonobject/subscript/
|
||||||
|
template <typename TUpstream, typename AdaptedString>
|
||||||
|
class MemberProxy
|
||||||
|
: public VariantRefBase<MemberProxy<TUpstream, AdaptedString>>,
|
||||||
|
public VariantOperators<MemberProxy<TUpstream, AdaptedString>> {
|
||||||
|
friend class VariantAttorney;
|
||||||
|
|
||||||
|
friend class VariantRefBase<MemberProxy<TUpstream, AdaptedString>>;
|
||||||
|
|
||||||
|
template <typename, typename>
|
||||||
|
friend class MemberProxy;
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
friend class ElementProxy;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MemberProxy(TUpstream upstream, AdaptedString key)
|
||||||
|
: upstream_(upstream), key_(key) {}
|
||||||
|
|
||||||
|
MemberProxy& operator=(const MemberProxy& src) {
|
||||||
|
this->set(src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
MemberProxy& operator=(const T& src) {
|
||||||
|
this->set(src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, enable_if_t<!is_const<T>::value, int> = 0>
|
||||||
|
MemberProxy& operator=(T* src) {
|
||||||
|
this->set(src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// clang-format off
|
||||||
|
MemberProxy(const MemberProxy& src) // Error here? See https://arduinojson.org/v7/proxy-non-copyable/
|
||||||
|
: upstream_(src.upstream_), key_(src.key_) {}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
ResourceManager* getResourceManager() const {
|
||||||
|
return VariantAttorney::getResourceManager(upstream_);
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantData* getData() const {
|
||||||
|
return VariantData::getMember(
|
||||||
|
VariantAttorney::getData(upstream_), key_,
|
||||||
|
VariantAttorney::getResourceManager(upstream_));
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantData* getOrCreateData() const {
|
||||||
|
auto data = VariantAttorney::getOrCreateData(upstream_);
|
||||||
|
if (!data)
|
||||||
|
return nullptr;
|
||||||
|
return data->getOrAddMember(key_,
|
||||||
|
VariantAttorney::getResourceManager(upstream_));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TUpstream upstream_;
|
||||||
|
AdaptedString key_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Collection/CollectionData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
class ObjectData : public CollectionData {
|
||||||
|
public:
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
VariantData* addMember(TAdaptedString key, ResourceManager* resources);
|
||||||
|
|
||||||
|
VariantData* addPair(VariantData** value, ResourceManager* resources);
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources);
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
VariantData* getMember(TAdaptedString key,
|
||||||
|
const ResourceManager* resources) const;
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
static VariantData* getMember(const ObjectData* object, TAdaptedString key,
|
||||||
|
const ResourceManager* resources) {
|
||||||
|
if (!object)
|
||||||
|
return nullptr;
|
||||||
|
return object->getMember(key, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
void removeMember(TAdaptedString key, ResourceManager* resources);
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
static void removeMember(ObjectData* obj, TAdaptedString key,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
if (!obj)
|
||||||
|
return;
|
||||||
|
obj->removeMember(key, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(iterator it, ResourceManager* resources) {
|
||||||
|
CollectionData::removePair(it, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove(ObjectData* obj, ObjectData::iterator it,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
if (!obj)
|
||||||
|
return;
|
||||||
|
obj->remove(it, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size(const ResourceManager* resources) const {
|
||||||
|
return CollectionData::size(resources) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t size(const ObjectData* obj, const ResourceManager* resources) {
|
||||||
|
if (!obj)
|
||||||
|
return 0;
|
||||||
|
return obj->size(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
iterator findKey(TAdaptedString key, const ResourceManager* resources) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Object/ObjectData.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantCompare.hpp>
|
||||||
|
#include <ArduinoJson/Variant/VariantData.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
inline VariantData* ObjectData::getMember(
|
||||||
|
TAdaptedString key, const ResourceManager* resources) const {
|
||||||
|
auto it = findKey(key, resources);
|
||||||
|
if (it.done())
|
||||||
|
return nullptr;
|
||||||
|
it.next(resources);
|
||||||
|
return it.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
VariantData* ObjectData::getOrAddMember(TAdaptedString key,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
auto data = getMember(key, resources);
|
||||||
|
if (data)
|
||||||
|
return data;
|
||||||
|
return addMember(key, resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
inline ObjectData::iterator ObjectData::findKey(
|
||||||
|
TAdaptedString key, const ResourceManager* resources) const {
|
||||||
|
if (key.isNull())
|
||||||
|
return iterator();
|
||||||
|
bool isKey = true;
|
||||||
|
for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
|
||||||
|
if (isKey && stringEquals(key, adaptString(it->asString())))
|
||||||
|
return it;
|
||||||
|
isKey = !isKey;
|
||||||
|
}
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
inline void ObjectData::removeMember(TAdaptedString key,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
remove(findKey(key, resources), resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TAdaptedString>
|
||||||
|
inline VariantData* ObjectData::addMember(TAdaptedString key,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
auto keySlot = resources->allocVariant();
|
||||||
|
if (!keySlot)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto valueSlot = resources->allocVariant();
|
||||||
|
if (!valueSlot)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
if (!keySlot->setString(key, resources))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
CollectionData::appendPair(keySlot, valueSlot, resources);
|
||||||
|
|
||||||
|
return valueSlot.ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VariantData* ObjectData::addPair(VariantData** value,
|
||||||
|
ResourceManager* resources) {
|
||||||
|
auto keySlot = resources->allocVariant();
|
||||||
|
if (!keySlot)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto valueSlot = resources->allocVariant();
|
||||||
|
if (!valueSlot)
|
||||||
|
return nullptr;
|
||||||
|
*value = valueSlot.ptr();
|
||||||
|
|
||||||
|
CollectionData::appendPair(keySlot, valueSlot, resources);
|
||||||
|
|
||||||
|
return keySlot.ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the size (in bytes) of an object with n members.
|
||||||
|
constexpr size_t sizeofObject(size_t n) {
|
||||||
|
return 2 * n * ResourceManager::slotSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h> // for size_t
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include "math.hpp"
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <typename T, typename F>
|
||||||
|
struct alias_cast_t {
|
||||||
|
union {
|
||||||
|
F raw;
|
||||||
|
T data;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename F>
|
||||||
|
T alias_cast(F raw_data) {
|
||||||
|
alias_cast_t<T, F> ac;
|
||||||
|
ac.raw = raw_data;
|
||||||
|
return ac.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
|
||||||
|
#if ARDUINOJSON_DEBUG
|
||||||
|
# include <assert.h>
|
||||||
|
# define ARDUINOJSON_ASSERT(X) assert(X)
|
||||||
|
#else
|
||||||
|
# define ARDUINOJSON_ASSERT(X) ((void)0)
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _MSC_VER // Visual Studio
|
||||||
|
|
||||||
|
# define FORCE_INLINE // __forceinline causes C4714 when returning std::string
|
||||||
|
|
||||||
|
# ifndef ARDUINOJSON_DEPRECATED
|
||||||
|
# define ARDUINOJSON_DEPRECATED(msg) __declspec(deprecated(msg))
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#elif defined(__GNUC__) // GCC or Clang
|
||||||
|
|
||||||
|
# define FORCE_INLINE __attribute__((always_inline))
|
||||||
|
|
||||||
|
# ifndef ARDUINOJSON_DEPRECATED
|
||||||
|
# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
|
||||||
|
# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated))
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#else // Other compilers
|
||||||
|
|
||||||
|
# define FORCE_INLINE
|
||||||
|
|
||||||
|
# ifndef ARDUINOJSON_DEPRECATED
|
||||||
|
# define ARDUINOJSON_DEPRECATED(msg)
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__has_attribute)
|
||||||
|
# if __has_attribute(no_sanitize)
|
||||||
|
# define ARDUINOJSON_NO_SANITIZE(check) __attribute__((no_sanitize(check)))
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_NO_SANITIZE(check)
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# define ARDUINOJSON_NO_SANITIZE(check)
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#ifndef isdigit
|
||||||
|
inline bool isdigit(char c) {
|
||||||
|
return '0' <= c && c <= '9';
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline bool issign(char c) {
|
||||||
|
return '-' == c || c == '+';
|
||||||
|
}
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h> // int8_t, int16_t
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
template <int Bits>
|
||||||
|
struct uint_;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct uint_<8> {
|
||||||
|
using type = uint8_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct uint_<16> {
|
||||||
|
using type = uint16_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct uint_<32> {
|
||||||
|
using type = uint32_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int Bits>
|
||||||
|
using uint_t = typename uint_<Bits>::type;
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "type_traits.hpp"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4310)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// Differs from standard because we can't use the symbols "min" and "max"
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct numeric_limits;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct numeric_limits<T, enable_if_t<is_unsigned<T>::value>> {
|
||||||
|
static constexpr T lowest() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
static constexpr T highest() {
|
||||||
|
return T(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct numeric_limits<
|
||||||
|
T, enable_if_t<is_integral<T>::value && is_signed<T>::value>> {
|
||||||
|
static constexpr T lowest() {
|
||||||
|
return T(T(1) << (sizeof(T) * 8 - 1));
|
||||||
|
}
|
||||||
|
static constexpr T highest() {
|
||||||
|
return T(~lowest());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// Some libraries #define isnan() and isinf() so we need to check before
|
||||||
|
// using this name
|
||||||
|
|
||||||
|
#ifndef isnan
|
||||||
|
template <typename T>
|
||||||
|
bool isnan(T x) {
|
||||||
|
return x != x;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef isinf
|
||||||
|
template <typename T>
|
||||||
|
bool isinf(T x) {
|
||||||
|
return x != 0.0 && x * 2 == x;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
|
||||||
|
#include <stddef.h> // for size_t
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
// A meta-function that returns the highest value
|
||||||
|
template <size_t X, size_t Y, bool MaxIsX = (X > Y)>
|
||||||
|
struct Max {};
|
||||||
|
|
||||||
|
template <size_t X, size_t Y>
|
||||||
|
struct Max<X, Y, true> {
|
||||||
|
static const size_t value = X;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t X, size_t Y>
|
||||||
|
struct Max<X, Y, false> {
|
||||||
|
static const size_t value = Y;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// ArduinoJson - https://arduinojson.org
|
||||||
|
// Copyright © 2014-2026, Benoit BLANCHON
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
# include <Arduino.h>
|
||||||
|
#else
|
||||||
|
// Allow using PROGMEM outside of Arduino (issue #1903)
|
||||||
|
class __FlashStringHelper;
|
||||||
|
# include <avr/pgmspace.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <ArduinoJson/Configuration.hpp>
|
||||||
|
#include <ArduinoJson/Namespace.hpp>
|
||||||
|
#include <ArduinoJson/Polyfills/assert.hpp>
|
||||||
|
|
||||||
|
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||||
|
// Wraps a const char* so that the our functions are picked only if the
|
||||||
|
// originals are missing
|
||||||
|
struct pgm_p {
|
||||||
|
pgm_p(const void* p) : address(reinterpret_cast<const char*>(p)) {}
|
||||||
|
const char* address;
|
||||||
|
};
|
||||||
|
|
||||||
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|
||||||
|
#ifndef strlen_P
|
||||||
|
inline size_t strlen_P(ArduinoJson::detail::pgm_p s) {
|
||||||
|
const char* p = s.address;
|
||||||
|
ARDUINOJSON_ASSERT(p != NULL);
|
||||||
|
while (pgm_read_byte(p))
|
||||||
|
p++;
|
||||||
|
return size_t(p - s.address);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef strncmp_P
|
||||||
|
inline int strncmp_P(const char* a, ArduinoJson::detail::pgm_p b, size_t n) {
|
||||||
|
const char* s1 = a;
|
||||||
|
const char* s2 = b.address;
|
||||||
|
ARDUINOJSON_ASSERT(s1 != NULL);
|
||||||
|
ARDUINOJSON_ASSERT(s2 != NULL);
|
||||||
|
while (n-- > 0) {
|
||||||
|
char c1 = *s1++;
|
||||||
|
char c2 = static_cast<char>(pgm_read_byte(s2++));
|
||||||
|
if (c1 < c2)
|
||||||
|
return -1;
|
||||||
|
if (c1 > c2)
|
||||||
|
return 1;
|
||||||
|
if (c1 == 0 /* and c2 as well */)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef strcmp_P
|
||||||
|
inline int strcmp_P(const char* a, ArduinoJson::detail::pgm_p b) {
|
||||||
|
const char* s1 = a;
|
||||||
|
const char* s2 = b.address;
|
||||||
|
ARDUINOJSON_ASSERT(s1 != NULL);
|
||||||
|
ARDUINOJSON_ASSERT(s2 != NULL);
|
||||||
|
for (;;) {
|
||||||
|
char c1 = *s1++;
|
||||||
|
char c2 = static_cast<char>(pgm_read_byte(s2++));
|
||||||
|
if (c1 < c2)
|
||||||
|
return -1;
|
||||||
|
if (c1 > c2)
|
||||||
|
return 1;
|
||||||
|
if (c1 == 0 /* and c2 as well */)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef memcmp_P
|
||||||
|
inline int memcmp_P(const void* a, ArduinoJson::detail::pgm_p b, size_t n) {
|
||||||
|
const uint8_t* p1 = reinterpret_cast<const uint8_t*>(a);
|
||||||
|
const char* p2 = b.address;
|
||||||
|
ARDUINOJSON_ASSERT(p1 != NULL);
|
||||||
|
ARDUINOJSON_ASSERT(p2 != NULL);
|
||||||
|
while (n-- > 0) {
|
||||||
|
uint8_t v1 = *p1++;
|
||||||
|
uint8_t v2 = pgm_read_byte(p2++);
|
||||||
|
if (v1 != v2)
|
||||||
|
return v1 - v2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef memcpy_P
|
||||||
|
inline void* memcpy_P(void* dst, ArduinoJson::detail::pgm_p src, size_t n) {
|
||||||
|
uint8_t* d = reinterpret_cast<uint8_t*>(dst);
|
||||||
|
const char* s = src.address;
|
||||||
|
ARDUINOJSON_ASSERT(d != NULL);
|
||||||
|
ARDUINOJSON_ASSERT(s != NULL);
|
||||||
|
while (n-- > 0) {
|
||||||
|
*d++ = pgm_read_byte(s++);
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef pgm_read_dword
|
||||||
|
inline uint32_t pgm_read_dword(ArduinoJson::detail::pgm_p p) {
|
||||||
|
uint32_t result;
|
||||||
|
memcpy_P(&result, p.address, 4);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef pgm_read_float
|
||||||
|
inline float pgm_read_float(ArduinoJson::detail::pgm_p p) {
|
||||||
|
float result;
|
||||||
|
memcpy_P(&result, p.address, sizeof(float));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef pgm_read_double
|
||||||
|
# if defined(__SIZEOF_DOUBLE__) && defined(__SIZEOF_FLOAT__) && \
|
||||||
|
__SIZEOF_DOUBLE__ == __SIZEOF_FLOAT__
|
||||||
|
inline double pgm_read_double(ArduinoJson::detail::pgm_p p) {
|
||||||
|
return pgm_read_float(p.address);
|
||||||
|
}
|
||||||
|
# else
|
||||||
|
inline double pgm_read_double(ArduinoJson::detail::pgm_p p) {
|
||||||
|
double result;
|
||||||
|
memcpy_P(&result, p.address, sizeof(double));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef pgm_read_ptr
|
||||||
|
inline void* pgm_read_ptr(ArduinoJson::detail::pgm_p p) {
|
||||||
|
void* result;
|
||||||
|
memcpy_P(&result, p.address, sizeof(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user