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
|
||||
tools/pngs/
|
||||
tools/gpx/
|
||||
sim_data/
|
||||
variants/sim/tools/emsdk/
|
||||
variants/sim/web/build/
|
||||
|
||||
# graphify
|
||||
graphify-out/
|
||||
|
||||
@@ -201,6 +201,8 @@ bool DataStore::formatFileSystem() {
|
||||
bool fs_success = ((fs::SPIFFSFS *)_fs)->format();
|
||||
esp_err_t nvs_err = nvs_flash_erase(); // no need to reinit, will be done by reboot
|
||||
return fs_success && (nvs_err == ESP_OK);
|
||||
#elif defined(SIM_PLATFORM)
|
||||
return _fs->format();
|
||||
#else
|
||||
#error "need to implement format()"
|
||||
#endif
|
||||
|
||||
@@ -2845,7 +2845,12 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
bool success = _store->formatFileSystem();
|
||||
if (success) {
|
||||
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);
|
||||
#endif
|
||||
board.reboot(); // doesn't return
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
|
||||
@@ -33,6 +33,13 @@ static uint32_t _atoi(const char* sp) {
|
||||
#elif defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
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
|
||||
|
||||
#ifdef ESP32
|
||||
@@ -88,6 +95,11 @@ static uint32_t _atoi(const char* sp) {
|
||||
#elif defined(STM32_PLATFORM)
|
||||
#include <helpers/ArduinoSerialInterface.h>
|
||||
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
|
||||
#error "need to define a serial interface"
|
||||
#endif
|
||||
@@ -239,6 +251,18 @@ void setup() {
|
||||
serial_interface.begin(Serial);
|
||||
#endif
|
||||
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
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,22 @@
|
||||
#ifdef WIFI_SSID
|
||||
#include <WiFi.h>
|
||||
#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
|
||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||
@@ -1361,6 +1377,10 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
uint32_t aoff = autoOffMillis();
|
||||
_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)
|
||||
// On the ENV_PIN_SDA/SCL path, CARDKB_I2C is Wire1, already brought up by
|
||||
// sensors.begin() (EnvironmentSensorManager), which runs before this. On
|
||||
@@ -2099,6 +2119,23 @@ void UITask::enqueueKey(char c) {
|
||||
_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) {
|
||||
if (_kq_tail == _kq_head) return false;
|
||||
c = _key_queue[_kq_tail];
|
||||
@@ -2364,6 +2401,66 @@ void UITask::loop() {
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
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
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
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, "Shutting down");
|
||||
_display->endFrame();
|
||||
#ifdef SIM_PLATFORM
|
||||
// Skip the pre-shutdown UX pause in the sim.
|
||||
#else
|
||||
if (_display->isEink() == false) { delay(2000); }
|
||||
#endif
|
||||
}
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@@ -208,6 +208,20 @@ class UITask : public AbstractUITask {
|
||||
void enqueueKey(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
|
||||
// definition near the top of this file for which bus it's on and why.
|
||||
#if defined(CARDKB_I2C)
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
#define FILESYSTEM Adafruit_LittleFS
|
||||
|
||||
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
|
||||
#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