diff --git a/ChangeLog.txt b/ChangeLog.txt
index 84f490deb17e22e4f76f79d4377140619d491ac7..a5c8217ecc37bd11a27485aa9bc8357c20726691 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -4,15 +4,18 @@
 #   devices BUT not set as globally available for firmware upgrades
 # (c) neOCampus / F.Thiebolt Université Toulouse3, Laboratoire IRIT
 
-=== TODO ===
-- retrieve NTP server name from DHCP answer and activate it !
+
+=== TODO
+- switch ESP32 Arduinoe core 3.X serie
+- switch to ArduinoMqttClient library https://github.com/arduino-libraries/ArduinoMqttClient
+  our current MQTT client lib is a really old one and protocol is evolving
+
 
 === Upcoming release / feature-calibration
 The main goal is to make use of a BLE enabled ambiant sensor to provide our
   our neOSensors two different measures points to compute the y= Ax + B
   parameters to achieve correction of uncalibrated sensors.
-Kept esp32 2.0.14
-Kept esp8266 3.1.2
+
 - added status mode with additional functionnalities set @boot time:
     press CLR for 5s        --> format SPIFFS (standard behaviour)
     press CLR,SW+ for 5s    --> format SPIFFS and CLEAR NVS namespaces
@@ -22,6 +25,16 @@ Note 'validation mode': ensure to already have the whole neOSensor configuration
     Roughly seaking, 'validation' mode is just about displaying the 'sharedRoot' json on serial link
     The 'sharedRoot' structure holds all of the sensors values our neOSensor features.
 
+=== Release 240712
+Bumps to ep32-2.0.17
+still latest esp8266 3.1.2
+updated ArduinoJson to rev. 7.1
+updated WiFiManager to rev. 2.0.17
+Detailed logs:
+- preparing for new ledc API from esp32 core 3.X serie
+- removed ESP32 ADC calibration in preperation to esp32 core 3.X serie
+- ArduinoJson 7 library --> no more sizing restrictions
+
 
 === Release 240306 for both esp32 and esp8266 (Arduino Core ESP32 2.0.14 and
 esp8266 3.1.2))
diff --git a/README.md b/README.md
index 6daebb60f806d6977aadfe6b19c3b41ba2f44a05..ee3815a486f4480084618fa07beb4df6f62e382f 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ First of all, you ought to install esp32, esp8266 or CubeCell support in your Ar
 | Device   | Release   | Arduino Board Manager json file                                                                            |
 |----------|-----------|------------------------------------------------------------------------------------------------------------|
 | esp8266  | 3.1.2     | https://arduino.esp8266.com/stable/package_esp8266com_index.json                                           |
-| esp32    | 2.0.14    | https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json                |
+| esp32    | 2.0.17    | https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json                |
 | CubeCell | *latest*  | https://github.com/HelTecAutomation/CubeCell-Arduino/releases/download/V1.3.0/package_CubeCell_index.json  |
 | stm32    | *latest*  | https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json             |
 
diff --git a/arduinoIDE_esp32_boards/2.0.15/liblwip.a b/arduinoIDE_esp32_boards/2.0.15/liblwip.a
new file mode 100644
index 0000000000000000000000000000000000000000..21fde8173ea5b5a2285b8993028a15826a03a739
Binary files /dev/null and b/arduinoIDE_esp32_boards/2.0.15/liblwip.a differ
diff --git a/arduinoIDE_esp32_boards/2.0.15/opt.h b/arduinoIDE_esp32_boards/2.0.15/opt.h
new file mode 100644
index 0000000000000000000000000000000000000000..df302dad33c5d2e8d731cb74b98b7e49ad2b509b
--- /dev/null
+++ b/arduinoIDE_esp32_boards/2.0.15/opt.h
@@ -0,0 +1,3588 @@
+/**
+ * @file
+ *
+ * lwIP Options Configuration
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@sics.se>
+ *
+ */
+
+/*
+ * NOTE: || defined __DOXYGEN__ is a workaround for doxygen bug -
+ * without this, doxygen does not see the actual #define
+ */
+
+#if !defined LWIP_HDR_OPT_H
+#define LWIP_HDR_OPT_H
+
+/*
+ * Include user defined options first. Anything not defined in these files
+ * will be set to standard values. Override anything you don't like!
+ */
+#include "lwipopts.h"
+#include "lwip/debug.h"
+
+/**
+ * @defgroup lwip_opts Options (lwipopts.h)
+ * @ingroup lwip
+ *
+ * @defgroup lwip_opts_debug Debugging
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_infrastructure Infrastructure
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_callback Callback-style APIs
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_threadsafe_apis Thread-safe APIs
+ * @ingroup lwip_opts
+ */
+
+ /*
+   ------------------------------------
+   -------------- NO SYS --------------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_nosys NO_SYS
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * NO_SYS==1: Use lwIP without OS-awareness (no thread, semaphores, mutexes or
+ * mboxes). This means threaded APIs cannot be used (socket, netconn,
+ * i.e. everything in the 'api' folder), only the callback-style raw API is
+ * available (and you have to watch out for yourself that you don't access
+ * lwIP functions/structures from more than one context at a time!)
+ */
+#if !defined NO_SYS || defined __DOXYGEN__
+#define NO_SYS                          0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_timers Timers
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_TIMERS==0: Drop support for sys_timeout and lwip-internal cyclic timers.
+ * (the array of lwip-internal cyclic timers is still provided)
+ * (check NO_SYS_NO_TIMERS for compatibility to old versions)
+ */
+#if !defined LWIP_TIMERS || defined __DOXYGEN__
+#ifdef NO_SYS_NO_TIMERS
+#define LWIP_TIMERS                     (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS))
+#else
+#define LWIP_TIMERS                     1
+#endif
+#endif
+
+/**
+ * LWIP_TIMERS_CUSTOM==1: Provide your own timer implementation.
+ * Function prototypes in timeouts.h and the array of lwip-internal cyclic timers
+ * are still included, but the implementation is not. The following functions
+ * will be required: sys_timeouts_init(), sys_timeout(), sys_untimeout(),
+ *                   sys_timeouts_mbox_fetch()
+ */
+#if !defined LWIP_TIMERS_CUSTOM || defined __DOXYGEN__
+#define LWIP_TIMERS_CUSTOM              0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_memcpy memcpy
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * MEMCPY: override this if you have a faster implementation at hand than the
+ * one included in your C library
+ */
+#if !defined MEMCPY || defined __DOXYGEN__
+#define MEMCPY(dst,src,len)             memcpy(dst,src,len)
+#endif
+
+/**
+ * SMEMCPY: override this with care! Some compilers (e.g. gcc) can inline a
+ * call to memcpy() if the length is known at compile time and is small.
+ */
+#if !defined SMEMCPY || defined __DOXYGEN__
+#define SMEMCPY(dst,src,len)            memcpy(dst,src,len)
+#endif
+
+/**
+ * MEMMOVE: override this if you have a faster implementation at hand than the
+ * one included in your C library.  lwIP currently uses MEMMOVE only when IPv6
+ * fragmentation support is enabled.
+ */
+#if !defined MEMMOVE || defined __DOXYGEN__
+#define MEMMOVE(dst,src,len)            memmove(dst,src,len)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ----------- Core locking -----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_lock Core locking and MPU
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MPU_COMPATIBLE: enables special memory management mechanism
+ * which makes lwip able to work on MPU (Memory Protection Unit) system
+ * by not passing stack-pointers to other threads
+ * (this decreases performance as memory is allocated from pools instead
+ * of keeping it on the stack)
+ */
+#if !defined LWIP_MPU_COMPATIBLE || defined __DOXYGEN__
+#define LWIP_MPU_COMPATIBLE             0
+#endif
+
+/**
+ * LWIP_TCPIP_CORE_LOCKING
+ * Creates a global mutex that is held during TCPIP thread operations.
+ * Can be locked by client code to perform lwIP operations without changing
+ * into TCPIP thread using callbacks. See LOCK_TCPIP_CORE() and
+ * UNLOCK_TCPIP_CORE().
+ * Your system should provide mutexes supporting priority inversion to use this.
+ */
+#if !defined LWIP_TCPIP_CORE_LOCKING || defined __DOXYGEN__
+#define LWIP_TCPIP_CORE_LOCKING         1
+#endif
+
+/**
+ * LWIP_TCPIP_CORE_LOCKING_INPUT: when LWIP_TCPIP_CORE_LOCKING is enabled,
+ * this lets tcpip_input() grab the mutex for input packets as well,
+ * instead of allocating a message and passing it to tcpip_thread.
+ *
+ * ATTENTION: this does not work when tcpip_input() is called from
+ * interrupt context!
+ */
+#if !defined LWIP_TCPIP_CORE_LOCKING_INPUT || defined __DOXYGEN__
+#define LWIP_TCPIP_CORE_LOCKING_INPUT   0
+#endif
+
+/**
+ * SYS_LIGHTWEIGHT_PROT==1: enable inter-task protection (and task-vs-interrupt
+ * protection) for certain critical regions during buffer allocation, deallocation
+ * and memory allocation and deallocation.
+ * ATTENTION: This is required when using lwIP from more than one context! If
+ * you disable this, you must be sure what you are doing!
+ */
+#if !defined SYS_LIGHTWEIGHT_PROT || defined __DOXYGEN__
+#define SYS_LIGHTWEIGHT_PROT            1
+#endif
+
+/**
+ * Macro/function to check whether lwIP's threading/locking
+ * requirements are satisfied during current function call.
+ * This macro usually calls a function that is implemented in the OS-dependent
+ * sys layer and performs the following checks:
+ * - Not in ISR (this should be checked for NO_SYS==1, too!)
+ * - If @ref LWIP_TCPIP_CORE_LOCKING = 1: TCPIP core lock is held
+ * - If @ref LWIP_TCPIP_CORE_LOCKING = 0: function is called from TCPIP thread
+ * @see @ref multithreading
+ */
+#if !defined LWIP_ASSERT_CORE_LOCKED || defined __DOXYGEN__
+#define LWIP_ASSERT_CORE_LOCKED()
+#endif
+
+/**
+ * Called as first thing in the lwIP TCPIP thread. Can be used in conjunction
+ * with @ref LWIP_ASSERT_CORE_LOCKED to check core locking.
+ * @see @ref multithreading
+ */
+#if !defined LWIP_MARK_TCPIP_THREAD || defined __DOXYGEN__
+#define LWIP_MARK_TCPIP_THREAD()
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- Memory options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_mem Heap and memory pools
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by your C-library
+ * instead of the lwip internal allocator. Can save code size if you
+ * already use it.
+ */
+#if !defined MEM_LIBC_MALLOC || defined __DOXYGEN__
+#define MEM_LIBC_MALLOC                 0
+#endif
+
+/**
+ * MEMP_MEM_MALLOC==1: Use mem_malloc/mem_free instead of the lwip pool allocator.
+ * Especially useful with MEM_LIBC_MALLOC but handle with care regarding execution
+ * speed (heap alloc can be much slower than pool alloc) and usage from interrupts
+ * (especially if your netif driver allocates PBUF_POOL pbufs for received frames
+ * from interrupt)!
+ * ATTENTION: Currently, this uses the heap for ALL pools (also for private pools,
+ * not only for internal pools defined in memp_std.h)!
+ */
+#if !defined MEMP_MEM_MALLOC || defined __DOXYGEN__
+#define MEMP_MEM_MALLOC                 0
+#endif
+
+/**
+ * MEMP_MEM_INIT==1: Force use of memset to initialize pool memory.
+ * Useful if pool are moved in uninitialized section of memory. This will ensure
+ * default values in pcbs struct are well initialized in all conditions.
+ */
+#if !defined MEMP_MEM_INIT || defined __DOXYGEN__
+#define MEMP_MEM_INIT                   0
+#endif
+
+/**
+ * MEM_ALIGNMENT: should be set to the alignment of the CPU
+ *    4 byte alignment -> \#define MEM_ALIGNMENT 4
+ *    2 byte alignment -> \#define MEM_ALIGNMENT 2
+ */
+#if !defined MEM_ALIGNMENT || defined __DOXYGEN__
+#define MEM_ALIGNMENT                   1
+#endif
+
+/**
+ * MEM_SIZE: the size of the heap memory. If the application will send
+ * a lot of data that needs to be copied, this should be set high.
+ */
+#if !defined MEM_SIZE || defined __DOXYGEN__
+#define MEM_SIZE                        1600
+#endif
+
+/**
+ * MEMP_OVERFLOW_CHECK: memp overflow protection reserves a configurable
+ * amount of bytes before and after each memp element in every pool and fills
+ * it with a prominent default value.
+ *    MEMP_OVERFLOW_CHECK == 0 no checking
+ *    MEMP_OVERFLOW_CHECK == 1 checks each element when it is freed
+ *    MEMP_OVERFLOW_CHECK >= 2 checks each element in every pool every time
+ *      memp_malloc() or memp_free() is called (useful but slow!)
+ */
+#if !defined MEMP_OVERFLOW_CHECK || defined __DOXYGEN__
+#define MEMP_OVERFLOW_CHECK             0
+#endif
+
+/**
+ * MEMP_SANITY_CHECK==1: run a sanity check after each memp_free() to make
+ * sure that there are no cycles in the linked lists.
+ */
+#if !defined MEMP_SANITY_CHECK || defined __DOXYGEN__
+#define MEMP_SANITY_CHECK               0
+#endif
+
+/**
+ * MEM_OVERFLOW_CHECK: mem overflow protection reserves a configurable
+ * amount of bytes before and after each heap allocation chunk and fills
+ * it with a prominent default value.
+ *    MEM_OVERFLOW_CHECK == 0 no checking
+ *    MEM_OVERFLOW_CHECK == 1 checks each element when it is freed
+ *    MEM_OVERFLOW_CHECK >= 2 checks all heap elements every time
+ *      mem_malloc() or mem_free() is called (useful but slow!)
+ */
+#if !defined MEM_OVERFLOW_CHECK || defined __DOXYGEN__
+#define MEM_OVERFLOW_CHECK              0
+#endif
+
+/**
+ * MEM_SANITY_CHECK==1: run a sanity check after each mem_free() to make
+ * sure that the linked list of heap elements is not corrupted.
+ */
+#if !defined MEM_SANITY_CHECK || defined __DOXYGEN__
+#define MEM_SANITY_CHECK                0
+#endif
+
+/**
+ * MEM_USE_POOLS==1: Use an alternative to malloc() by allocating from a set
+ * of memory pools of various sizes. When mem_malloc is called, an element of
+ * the smallest pool that can provide the length needed is returned.
+ * To use this, MEMP_USE_CUSTOM_POOLS also has to be enabled.
+ */
+#if !defined MEM_USE_POOLS || defined __DOXYGEN__
+#define MEM_USE_POOLS                   0
+#endif
+
+/**
+ * MEM_USE_POOLS_TRY_BIGGER_POOL==1: if one malloc-pool is empty, try the next
+ * bigger pool - WARNING: THIS MIGHT WASTE MEMORY but it can make a system more
+ * reliable. */
+#if !defined MEM_USE_POOLS_TRY_BIGGER_POOL || defined __DOXYGEN__
+#define MEM_USE_POOLS_TRY_BIGGER_POOL   0
+#endif
+
+/**
+ * MEMP_USE_CUSTOM_POOLS==1: whether to include a user file lwippools.h
+ * that defines additional pools beyond the "standard" ones required
+ * by lwIP. If you set this to 1, you must have lwippools.h in your
+ * include path somewhere.
+ */
+#if !defined MEMP_USE_CUSTOM_POOLS || defined __DOXYGEN__
+#define MEMP_USE_CUSTOM_POOLS           0
+#endif
+
+/**
+ * Set this to 1 if you want to free PBUF_RAM pbufs (or call mem_free()) from
+ * interrupt context (or another context that doesn't allow waiting for a
+ * semaphore).
+ * If set to 1, mem_malloc will be protected by a semaphore and SYS_ARCH_PROTECT,
+ * while mem_free will only use SYS_ARCH_PROTECT. mem_malloc SYS_ARCH_UNPROTECTs
+ * with each loop so that mem_free can run.
+ *
+ * ATTENTION: As you can see from the above description, this leads to dis-/
+ * enabling interrupts often, which can be slow! Also, on low memory, mem_malloc
+ * can need longer.
+ *
+ * If you don't want that, at least for NO_SYS=0, you can still use the following
+ * functions to enqueue a deallocation call which then runs in the tcpip_thread
+ * context:
+ * - pbuf_free_callback(p);
+ * - mem_free_callback(m);
+ */
+#if !defined LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT || defined __DOXYGEN__
+#define LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------------------
+   ---------- Internal Memory Pool Sizes ----------
+   ------------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_memp Internal memory pools
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * MEMP_NUM_PBUF: the number of memp struct pbufs (used for PBUF_ROM and PBUF_REF).
+ * If the application sends a lot of data out of ROM (or other static memory),
+ * this should be set high.
+ */
+#if !defined MEMP_NUM_PBUF || defined __DOXYGEN__
+#define MEMP_NUM_PBUF                   16
+#endif
+
+/**
+ * MEMP_NUM_RAW_PCB: Number of raw connection PCBs
+ * (requires the LWIP_RAW option)
+ */
+#if !defined MEMP_NUM_RAW_PCB || defined __DOXYGEN__
+#define MEMP_NUM_RAW_PCB                4
+#endif
+
+/**
+ * MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
+ * per active UDP "connection".
+ * (requires the LWIP_UDP option)
+ */
+#if !defined MEMP_NUM_UDP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_UDP_PCB                4
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB: the number of simultaneously active TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_TCP_PCB                5
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_PCB_LISTEN || defined __DOXYGEN__
+#define MEMP_NUM_TCP_PCB_LISTEN         8
+#endif
+
+/**
+ * MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP segments.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_SEG || defined __DOXYGEN__
+#define MEMP_NUM_TCP_SEG                16
+#endif
+
+/**
+ * MEMP_NUM_ALTCP_PCB: the number of simultaneously active altcp layer pcbs.
+ * (requires the LWIP_ALTCP option)
+ * Connections with multiple layers require more than one altcp_pcb (e.g. TLS
+ * over TCP requires 2 altcp_pcbs, one for TLS and one for TCP).
+ */
+#if !defined MEMP_NUM_ALTCP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_ALTCP_PCB              MEMP_NUM_TCP_PCB
+#endif
+
+/**
+ * MEMP_NUM_REASSDATA: the number of IP packets simultaneously queued for
+ * reassembly (whole packets, not fragments!)
+ */
+#if !defined MEMP_NUM_REASSDATA || defined __DOXYGEN__
+#define MEMP_NUM_REASSDATA              5
+#endif
+
+/**
+ * MEMP_NUM_FRAG_PBUF: the number of IP fragments simultaneously sent
+ * (fragments, not whole packets!).
+ * This is only used with LWIP_NETIF_TX_SINGLE_PBUF==0 and only has to be > 1
+ * with DMA-enabled MACs where the packet is not yet sent when netif->output
+ * returns.
+ */
+#if !defined MEMP_NUM_FRAG_PBUF || defined __DOXYGEN__
+#define MEMP_NUM_FRAG_PBUF              15
+#endif
+
+/**
+ * MEMP_NUM_ARP_QUEUE: the number of simultaneously queued outgoing
+ * packets (pbufs) that are waiting for an ARP request (to resolve
+ * their destination address) to finish.
+ * (requires the ARP_QUEUEING option)
+ */
+#if !defined MEMP_NUM_ARP_QUEUE || defined __DOXYGEN__
+#define MEMP_NUM_ARP_QUEUE              30
+#endif
+
+/**
+ * MEMP_NUM_IGMP_GROUP: The number of multicast groups whose network interfaces
+ * can be members at the same time (one per netif - allsystems group -, plus one
+ * per netif membership).
+ * (requires the LWIP_IGMP option)
+ */
+#if !defined MEMP_NUM_IGMP_GROUP || defined __DOXYGEN__
+#define MEMP_NUM_IGMP_GROUP             8
+#endif
+
+/**
+ * The number of sys timeouts used by the core stack (not apps)
+ * The default number of timeouts is calculated here for all enabled modules.
+ */
+#if ESP_LWIP
+#define LWIP_NUM_SYS_TIMEOUT_INTERNAL   (LWIP_TCP + IP_REASSEMBLY + (LWIP_ARP + (ESP_GRATUITOUS_ARP ? 1 : 0)) + (ESP_LWIP_DHCP_FINE_TIMERS_ONDEMAND ? LWIP_DHCP : 2*LWIP_DHCP + (ESP_DHCPS_TIMER ? 1 : 0)) + LWIP_AUTOIP + LWIP_IGMP + (ESP_LWIP_DNS_TIMERS_ONDEMAND ? 0 : LWIP_DNS) + PPP_NUM_TIMEOUTS + (LWIP_IPV6 * (1 + LWIP_IPV6_REASS + LWIP_IPV6_MLD)))
+#else
+#define LWIP_NUM_SYS_TIMEOUT_INTERNAL   (LWIP_TCP + IP_REASSEMBLY + LWIP_ARP + (2*LWIP_DHCP) + LWIP_AUTOIP + LWIP_IGMP + LWIP_DNS + PPP_NUM_TIMEOUTS + (LWIP_IPV6 * (1 + LWIP_IPV6_REASS + LWIP_IPV6_MLD)))
+#endif
+/**
+ * MEMP_NUM_SYS_TIMEOUT: the number of simultaneously active timeouts.
+ * The default number of timeouts is calculated here for all enabled modules.
+ * The formula expects settings to be either '0' or '1'.
+ */
+#if !defined MEMP_NUM_SYS_TIMEOUT || defined __DOXYGEN__
+#define MEMP_NUM_SYS_TIMEOUT            LWIP_NUM_SYS_TIMEOUT_INTERNAL
+#endif
+
+/**
+ * MEMP_NUM_NETBUF: the number of struct netbufs.
+ * (only needed if you use the sequential API, like api_lib.c)
+ */
+#if !defined MEMP_NUM_NETBUF || defined __DOXYGEN__
+#define MEMP_NUM_NETBUF                 2
+#endif
+
+/**
+ * MEMP_NUM_NETCONN: the number of struct netconns.
+ * (only needed if you use the sequential API, like api_lib.c)
+ */
+#if !defined MEMP_NUM_NETCONN || defined __DOXYGEN__
+#define MEMP_NUM_NETCONN                4
+#endif
+
+/**
+ * MEMP_NUM_SELECT_CB: the number of struct lwip_select_cb.
+ * (Only needed if you have LWIP_MPU_COMPATIBLE==1 and use the socket API.
+ * In that case, you need one per thread calling lwip_select.)
+ */
+#if !defined MEMP_NUM_SELECT_CB || defined __DOXYGEN__
+#define MEMP_NUM_SELECT_CB              4
+#endif
+
+/**
+ * MEMP_NUM_TCPIP_MSG_API: the number of struct tcpip_msg, which are used
+ * for callback/timeout API communication.
+ * (only needed if you use tcpip.c)
+ */
+#if !defined MEMP_NUM_TCPIP_MSG_API || defined __DOXYGEN__
+#define MEMP_NUM_TCPIP_MSG_API          8
+#endif
+
+/**
+ * MEMP_NUM_TCPIP_MSG_INPKT: the number of struct tcpip_msg, which are used
+ * for incoming packets.
+ * (only needed if you use tcpip.c)
+ */
+#if !defined MEMP_NUM_TCPIP_MSG_INPKT || defined __DOXYGEN__
+#define MEMP_NUM_TCPIP_MSG_INPKT        8
+#endif
+
+/**
+ * MEMP_NUM_NETDB: the number of concurrently running lwip_addrinfo() calls
+ * (before freeing the corresponding memory using lwip_freeaddrinfo()).
+ */
+#if !defined MEMP_NUM_NETDB || defined __DOXYGEN__
+#define MEMP_NUM_NETDB                  1
+#endif
+
+/**
+ * MEMP_NUM_LOCALHOSTLIST: the number of host entries in the local host list
+ * if DNS_LOCAL_HOSTLIST_IS_DYNAMIC==1.
+ */
+#if !defined MEMP_NUM_LOCALHOSTLIST || defined __DOXYGEN__
+#define MEMP_NUM_LOCALHOSTLIST          1
+#endif
+
+/**
+ * PBUF_POOL_SIZE: the number of buffers in the pbuf pool.
+ */
+#if !defined PBUF_POOL_SIZE || defined __DOXYGEN__
+#define PBUF_POOL_SIZE                  16
+#endif
+
+/** MEMP_NUM_API_MSG: the number of concurrently active calls to various
+ * socket, netconn, and tcpip functions
+ */
+#if !defined MEMP_NUM_API_MSG || defined __DOXYGEN__
+#define MEMP_NUM_API_MSG                MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_DNS_API_MSG: the number of concurrently active calls to netconn_gethostbyname
+ */
+#if !defined MEMP_NUM_DNS_API_MSG || defined __DOXYGEN__
+#define MEMP_NUM_DNS_API_MSG            MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA: the number of concurrently active calls
+ * to getsockopt/setsockopt
+ */
+#if !defined MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA || defined __DOXYGEN__
+#define MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_NETIFAPI_MSG: the number of concurrently active calls to the
+ * netifapi functions
+ */
+#if !defined MEMP_NUM_NETIFAPI_MSG || defined __DOXYGEN__
+#define MEMP_NUM_NETIFAPI_MSG           MEMP_NUM_TCPIP_MSG_API
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- ARP options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_arp ARP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_ARP==1: Enable ARP functionality.
+ */
+#if !defined LWIP_ARP || defined __DOXYGEN__
+#define LWIP_ARP                        1
+#endif
+
+/**
+ * ARP_TABLE_SIZE: Number of active MAC-IP address pairs cached.
+ */
+#if !defined ARP_TABLE_SIZE || defined __DOXYGEN__
+#define ARP_TABLE_SIZE                  10
+#endif
+
+/** the time an ARP entry stays valid after its last update,
+ *  for ARP_TMR_INTERVAL = 1000, this is
+ *  (60 * 5) seconds = 5 minutes.
+ */
+#if !defined ARP_MAXAGE || defined __DOXYGEN__
+#define ARP_MAXAGE                      300
+#endif
+
+/**
+ * ARP_QUEUEING==1: Multiple outgoing packets are queued during hardware address
+ * resolution. By default, only the most recent packet is queued per IP address.
+ * This is sufficient for most protocols and mainly reduces TCP connection
+ * startup time. Set this to 1 if you know your application sends more than one
+ * packet in a row to an IP address that is not in the ARP cache.
+ */
+#if !defined ARP_QUEUEING || defined __DOXYGEN__
+#define ARP_QUEUEING                    0
+#endif
+
+/** The maximum number of packets which may be queued for each
+ *  unresolved address by other network layers. Defaults to 3, 0 means disabled.
+ *  Old packets are dropped, new packets are queued.
+ */
+#if !defined ARP_QUEUE_LEN || defined __DOXYGEN__
+#define ARP_QUEUE_LEN                   3
+#endif
+
+/**
+ * ETHARP_SUPPORT_VLAN==1: support receiving and sending ethernet packets with
+ * VLAN header. See the description of LWIP_HOOK_VLAN_CHECK and
+ * LWIP_HOOK_VLAN_SET hooks to check/set VLAN headers.
+ * Additionally, you can define ETHARP_VLAN_CHECK to an u16_t VLAN ID to check.
+ * If ETHARP_VLAN_CHECK is defined, only VLAN-traffic for this VLAN is accepted.
+ * If ETHARP_VLAN_CHECK is not defined, all traffic is accepted.
+ * Alternatively, define a function/define ETHARP_VLAN_CHECK_FN(eth_hdr, vlan)
+ * that returns 1 to accept a packet or 0 to drop a packet.
+ */
+#if !defined ETHARP_SUPPORT_VLAN || defined __DOXYGEN__
+#define ETHARP_SUPPORT_VLAN             0
+#endif
+
+/** LWIP_ETHERNET==1: enable ethernet support even though ARP might be disabled
+ */
+#if !defined LWIP_ETHERNET || defined __DOXYGEN__
+#define LWIP_ETHERNET                   LWIP_ARP
+#endif
+
+/** ETH_PAD_SIZE: number of bytes added before the ethernet header to ensure
+ * alignment of payload after that header. Since the header is 14 bytes long,
+ * without this padding e.g. addresses in the IP header will not be aligned
+ * on a 32-bit boundary, so setting this to 2 can speed up 32-bit-platforms.
+ */
+#if !defined ETH_PAD_SIZE || defined __DOXYGEN__
+#define ETH_PAD_SIZE                    0
+#endif
+
+/** ETHARP_SUPPORT_STATIC_ENTRIES==1: enable code to support static ARP table
+ * entries (using etharp_add_static_entry/etharp_remove_static_entry).
+ */
+#if !defined ETHARP_SUPPORT_STATIC_ENTRIES || defined __DOXYGEN__
+#define ETHARP_SUPPORT_STATIC_ENTRIES   0
+#endif
+
+/** ETHARP_TABLE_MATCH_NETIF==1: Match netif for ARP table entries.
+ * If disabled, duplicate IP address on multiple netifs are not supported
+ * (but this should only occur for AutoIP).
+ */
+#if !defined ETHARP_TABLE_MATCH_NETIF || defined __DOXYGEN__
+#define ETHARP_TABLE_MATCH_NETIF        !LWIP_SINGLE_NETIF
+#endif
+/**
+ * @}
+ */
+
+/*
+   --------------------------------
+   ---------- IP options ----------
+   --------------------------------
+*/
+/**
+ * @defgroup lwip_opts_ipv4 IPv4
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * LWIP_IPV4==1: Enable IPv4
+ */
+#if !defined LWIP_IPV4 || defined __DOXYGEN__
+#define LWIP_IPV4                       1
+#endif
+
+/**
+ * IP_FORWARD==1: Enables the ability to forward IP packets across network
+ * interfaces. If you are going to run lwIP on a device with only one network
+ * interface, define this to 0.
+ */
+#if !defined IP_FORWARD || defined __DOXYGEN__
+#define IP_FORWARD                      0
+#endif
+
+/**
+ * IP_REASSEMBLY==1: Reassemble incoming fragmented IP packets. Note that
+ * this option does not affect outgoing packet sizes, which can be controlled
+ * via IP_FRAG.
+ */
+#if !defined IP_REASSEMBLY || defined __DOXYGEN__
+#define IP_REASSEMBLY                   1
+#endif
+
+/**
+ * IP_FRAG==1: Fragment outgoing IP packets if their size exceeds MTU. Note
+ * that this option does not affect incoming packet sizes, which can be
+ * controlled via IP_REASSEMBLY.
+ */
+#if !defined IP_FRAG || defined __DOXYGEN__
+#define IP_FRAG                         1
+#endif
+
+#if !LWIP_IPV4
+/* disable IPv4 extensions when IPv4 is disabled */
+#undef IP_FORWARD
+#define IP_FORWARD                      0
+#undef IP_REASSEMBLY
+#define IP_REASSEMBLY                   0
+#undef IP_FRAG
+#define IP_FRAG                         0
+#endif /* !LWIP_IPV4 */
+
+/**
+ * IP_NAPT==1: Enables IPv4 Network Address and Port Translation
+ * Note that IP_FORWARD needs to be enabled for NAPT to work
+ */
+#if !defined IP_NAPT || defined __DOXYGEN__
+#define IP_NAPT                      0
+#endif
+
+/**
+ * IP_OPTIONS_ALLOWED: Defines the behavior for IP options.
+ *      IP_OPTIONS_ALLOWED==0: All packets with IP options are dropped.
+ *      IP_OPTIONS_ALLOWED==1: IP options are allowed (but not parsed).
+ */
+#if !defined IP_OPTIONS_ALLOWED || defined __DOXYGEN__
+#define IP_OPTIONS_ALLOWED              1
+#endif
+
+/**
+ * IP_REASS_MAXAGE: Maximum time (in multiples of IP_TMR_INTERVAL - so seconds, normally)
+ * a fragmented IP packet waits for all fragments to arrive. If not all fragments arrived
+ * in this time, the whole packet is discarded.
+ */
+#if !defined IP_REASS_MAXAGE || defined __DOXYGEN__
+#define IP_REASS_MAXAGE                 15
+#endif
+
+/**
+ * IP_REASS_MAX_PBUFS: Total maximum amount of pbufs waiting to be reassembled.
+ * Since the received pbufs are enqueued, be sure to configure
+ * PBUF_POOL_SIZE > IP_REASS_MAX_PBUFS so that the stack is still able to receive
+ * packets even if the maximum amount of fragments is enqueued for reassembly!
+ * When IPv4 *and* IPv6 are enabled, this even changes to
+ * (PBUF_POOL_SIZE > 2 * IP_REASS_MAX_PBUFS)!
+ */
+#if !defined IP_REASS_MAX_PBUFS || defined __DOXYGEN__
+#define IP_REASS_MAX_PBUFS              10
+#endif
+
+/**
+ * IP_DEFAULT_TTL: Default value for Time-To-Live used by transport layers.
+ */
+#if !defined IP_DEFAULT_TTL || defined __DOXYGEN__
+#define IP_DEFAULT_TTL                  255
+#endif
+
+/**
+ * IP_SOF_BROADCAST=1: Use the SOF_BROADCAST field to enable broadcast
+ * filter per pcb on udp and raw send operations. To enable broadcast filter
+ * on recv operations, you also have to set IP_SOF_BROADCAST_RECV=1.
+ */
+#if !defined IP_SOF_BROADCAST || defined __DOXYGEN__
+#define IP_SOF_BROADCAST                0
+#endif
+
+/**
+ * IP_SOF_BROADCAST_RECV (requires IP_SOF_BROADCAST=1) enable the broadcast
+ * filter on recv operations.
+ */
+#if !defined IP_SOF_BROADCAST_RECV || defined __DOXYGEN__
+#define IP_SOF_BROADCAST_RECV           0
+#endif
+
+/**
+ * IP_FORWARD_ALLOW_TX_ON_RX_NETIF==1: allow ip_forward() to send packets back
+ * out on the netif where it was received. This should only be used for
+ * wireless networks.
+ * ATTENTION: When this is 1, make sure your netif driver correctly marks incoming
+ * link-layer-broadcast/multicast packets as such using the corresponding pbuf flags!
+ */
+#if !defined IP_FORWARD_ALLOW_TX_ON_RX_NETIF || defined __DOXYGEN__
+#define IP_FORWARD_ALLOW_TX_ON_RX_NETIF 0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- ICMP options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_icmp ICMP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_ICMP==1: Enable ICMP module inside the IP stack.
+ * Be careful, disable that make your product non-compliant to RFC1122
+ */
+#if !defined LWIP_ICMP || defined __DOXYGEN__
+#define LWIP_ICMP                       1
+#endif
+
+/**
+ * ICMP_TTL: Default value for Time-To-Live used by ICMP packets.
+ */
+#if !defined ICMP_TTL || defined __DOXYGEN__
+#define ICMP_TTL                        IP_DEFAULT_TTL
+#endif
+
+/**
+ * LWIP_BROADCAST_PING==1: respond to broadcast pings (default is unicast only)
+ */
+#if !defined LWIP_BROADCAST_PING || defined __DOXYGEN__
+#define LWIP_BROADCAST_PING             0
+#endif
+
+/**
+ * LWIP_MULTICAST_PING==1: respond to multicast pings (default is unicast only)
+ */
+#if !defined LWIP_MULTICAST_PING || defined __DOXYGEN__
+#define LWIP_MULTICAST_PING             0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- RAW options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_raw RAW
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
+ */
+#if !defined LWIP_RAW || defined __DOXYGEN__
+#define LWIP_RAW                        0
+#endif
+
+/**
+ * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
+ */
+#if !defined RAW_TTL || defined __DOXYGEN__
+#define RAW_TTL                         IP_DEFAULT_TTL
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- DHCP options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_dhcp DHCP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_DHCP==1: Enable DHCP module.
+ */
+#if !defined LWIP_DHCP || defined __DOXYGEN__
+#define LWIP_DHCP                       0
+#endif
+#if !LWIP_IPV4
+/* disable DHCP when IPv4 is disabled */
+#undef LWIP_DHCP
+#define LWIP_DHCP                       0
+#endif /* !LWIP_IPV4 */
+
+/**
+ * DHCP_DOES_ARP_CHECK==1: Do an ARP check on the offered address.
+ */
+#if !defined DHCP_DOES_ARP_CHECK || defined __DOXYGEN__
+#define DHCP_DOES_ARP_CHECK             (LWIP_DHCP && LWIP_ARP)
+#endif
+
+/**
+ * LWIP_DHCP_BOOTP_FILE==1: Store offered_si_addr and boot_file_name.
+ */
+#if !defined LWIP_DHCP_BOOTP_FILE || defined __DOXYGEN__
+#define LWIP_DHCP_BOOTP_FILE            0
+#endif
+
+/**
+ * LWIP_DHCP_GETS_NTP==1: Request NTP servers with discover/select. For each
+ * response packet, an callback is called, which has to be provided by the port:
+ * void dhcp_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs);
+*/
+#if !defined LWIP_DHCP_GET_NTP_SRV || defined __DOXYGEN__
+#define LWIP_DHCP_GET_NTP_SRV           1
+#endif
+
+/**
+ * The maximum of NTP servers requested
+ */
+#if !defined LWIP_DHCP_MAX_NTP_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP_MAX_NTP_SERVERS       3
+#endif
+
+/**
+ * LWIP_DHCP_MAX_DNS_SERVERS > 0: Request DNS servers with discover/select.
+ * DNS servers received in the response are passed to DNS via @ref dns_setserver()
+ * (up to the maximum limit defined here).
+ */
+#if !defined LWIP_DHCP_MAX_DNS_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP_MAX_DNS_SERVERS       DNS_MAX_SERVERS
+#endif
+/**
+ * @}
+ */
+
+#ifndef LWIP_DHCP_IP_ADDR_RESTORE
+#define LWIP_DHCP_IP_ADDR_RESTORE()       0
+#endif
+
+#ifndef LWIP_DHCP_IP_ADDR_STORE
+#define LWIP_DHCP_IP_ADDR_STORE()
+#endif
+
+#ifndef LWIP_DHCP_IP_ADDR_ERASE
+#define LWIP_DHCP_IP_ADDR_ERASE(esp_netif)
+#endif
+
+/*
+   ------------------------------------
+   ---------- AUTOIP options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_autoip AUTOIP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_AUTOIP==1: Enable AUTOIP module.
+ */
+#if !defined LWIP_AUTOIP || defined __DOXYGEN__
+#define LWIP_AUTOIP                     0
+#endif
+#if !LWIP_IPV4
+/* disable AUTOIP when IPv4 is disabled */
+#undef LWIP_AUTOIP
+#define LWIP_AUTOIP                     0
+#endif /* !LWIP_IPV4 */
+
+/**
+ * LWIP_DHCP_AUTOIP_COOP==1: Allow DHCP and AUTOIP to be both enabled on
+ * the same interface at the same time.
+ */
+#if !defined LWIP_DHCP_AUTOIP_COOP || defined __DOXYGEN__
+#define LWIP_DHCP_AUTOIP_COOP           0
+#endif
+
+/**
+ * ESP_IPV6_AUTOCONFIG==1: Enable stateless address autoconfiguration as per RFC 4862.
+ */
+#if !defined ESP_IPV6_AUTOCONFIG
+#define ESP_IPV6_AUTOCONFIG 0
+#endif
+
+/**
+ * LWIP_DHCP_AUTOIP_COOP_TRIES: Set to the number of DHCP DISCOVER probes
+ * that should be sent before falling back on AUTOIP (the DHCP client keeps
+ * running in this case). This can be set as low as 1 to get an AutoIP address
+ * very  quickly, but you should be prepared to handle a changing IP address
+ * when DHCP overrides AutoIP.
+ */
+#if !defined LWIP_DHCP_AUTOIP_COOP_TRIES || defined __DOXYGEN__
+#define LWIP_DHCP_AUTOIP_COOP_TRIES     9
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ----- SNMP MIB2 support      -----
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_mib2 SNMP MIB2 callbacks
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MIB2_CALLBACKS==1: Turn on SNMP MIB2 callbacks.
+ * Turn this on to get callbacks needed to implement MIB2.
+ * Usually MIB2_STATS should be enabled, too.
+ */
+#if !defined LWIP_MIB2_CALLBACKS || defined __DOXYGEN__
+#define LWIP_MIB2_CALLBACKS             0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   -------- Multicast options -------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_multicast Multicast
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MULTICAST_TX_OPTIONS==1: Enable multicast TX support like the socket options
+ * IP_MULTICAST_TTL/IP_MULTICAST_IF/IP_MULTICAST_LOOP, as well as (currently only)
+ * core support for the corresponding IPv6 options.
+ */
+#if !defined LWIP_MULTICAST_TX_OPTIONS || defined __DOXYGEN__
+#define LWIP_MULTICAST_TX_OPTIONS       ((LWIP_IGMP || LWIP_IPV6_MLD) && (LWIP_UDP || LWIP_RAW))
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- IGMP options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_igmp IGMP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_IGMP==1: Turn on IGMP module.
+ */
+#if !defined LWIP_IGMP || defined __DOXYGEN__
+#define LWIP_IGMP                       0
+#endif
+#if !LWIP_IPV4
+#undef LWIP_IGMP
+#define LWIP_IGMP                       0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- DNS options -----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_dns DNS
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_DNS==1: Turn on DNS module. UDP must be available for DNS
+ * transport.
+ */
+#if !defined LWIP_DNS || defined __DOXYGEN__
+#define LWIP_DNS                        0
+#endif
+
+/** DNS maximum number of entries to maintain locally. */
+#if !defined DNS_TABLE_SIZE || defined __DOXYGEN__
+#define DNS_TABLE_SIZE                  4
+#endif
+
+/** DNS maximum host name length supported in the name table. */
+#if !defined DNS_MAX_NAME_LENGTH || defined __DOXYGEN__
+#define DNS_MAX_NAME_LENGTH             256
+#endif
+
+/** The maximum of DNS servers
+ * The first server can be initialized automatically by defining
+ * DNS_SERVER_ADDRESS(ipaddr), where 'ipaddr' is an 'ip_addr_t*'
+ */
+#if !defined DNS_MAX_SERVERS || defined __DOXYGEN__
+#define DNS_MAX_SERVERS                 2
+#endif
+
+/** DNS maximum number of retries when asking for a name, before "timeout". */
+#if !defined DNS_MAX_RETRIES || defined __DOXYGEN__
+#define DNS_MAX_RETRIES                 4
+#endif
+
+/** DNS do a name checking between the query and the response. */
+#if !defined DNS_DOES_NAME_CHECK || defined __DOXYGEN__
+#define DNS_DOES_NAME_CHECK             1
+#endif
+
+/** LWIP_DNS_SECURE: controls the security level of the DNS implementation
+ * Use all DNS security features by default.
+ * This is overridable but should only be needed by very small targets
+ * or when using against non standard DNS servers. */
+#if !defined LWIP_DNS_SECURE || defined __DOXYGEN__
+#define LWIP_DNS_SECURE (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT)
+#endif
+
+/* A list of DNS security features follows */
+#define LWIP_DNS_SECURE_RAND_XID                1
+#define LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING 2
+#define LWIP_DNS_SECURE_RAND_SRC_PORT           4
+
+/** DNS_LOCAL_HOSTLIST: Implements a local host-to-address list. If enabled, you have to define an initializer:
+ *  \#define DNS_LOCAL_HOSTLIST_INIT {DNS_LOCAL_HOSTLIST_ELEM("host_ip4", IPADDR4_INIT_BYTES(1,2,3,4)), \
+ *                                    DNS_LOCAL_HOSTLIST_ELEM("host_ip6", IPADDR6_INIT_HOST(123, 234, 345, 456)}
+ *
+ *  Instead, you can also use an external function:
+ *  \#define DNS_LOOKUP_LOCAL_EXTERN(x) extern err_t my_lookup_function(const char *name, ip_addr_t *addr, u8_t dns_addrtype)
+ *  that looks up the IP address and returns ERR_OK if found (LWIP_DNS_ADDRTYPE_xxx is passed in dns_addrtype).
+ */
+#if !defined DNS_LOCAL_HOSTLIST || defined __DOXYGEN__
+#define DNS_LOCAL_HOSTLIST              0
+#endif /* DNS_LOCAL_HOSTLIST */
+
+/** If this is turned on, the local host-list can be dynamically changed
+ *  at runtime. */
+#if !defined DNS_LOCAL_HOSTLIST_IS_DYNAMIC || defined __DOXYGEN__
+#define DNS_LOCAL_HOSTLIST_IS_DYNAMIC   0
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+
+/** Set this to 1 to enable querying ".local" names via mDNS
+ *  using a One-Shot Multicast DNS Query */
+#if !defined LWIP_DNS_SUPPORT_MDNS_QUERIES || defined __DOXYGEN__
+#define LWIP_DNS_SUPPORT_MDNS_QUERIES   0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- UDP options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_udp UDP
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_UDP==1: Turn on UDP.
+ */
+#if !defined LWIP_UDP || defined __DOXYGEN__
+#define LWIP_UDP                        1
+#endif
+
+/**
+ * LWIP_UDPLITE==1: Turn on UDP-Lite. (Requires LWIP_UDP)
+ */
+#if !defined LWIP_UDPLITE || defined __DOXYGEN__
+#define LWIP_UDPLITE                    0
+#endif
+
+/**
+ * UDP_TTL: Default Time-To-Live value.
+ */
+#if !defined UDP_TTL || defined __DOXYGEN__
+#define UDP_TTL                         IP_DEFAULT_TTL
+#endif
+
+/**
+ * LWIP_NETBUF_RECVINFO==1: append destination addr and port to every netbuf.
+ */
+#if !defined LWIP_NETBUF_RECVINFO || defined __DOXYGEN__
+#define LWIP_NETBUF_RECVINFO            0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- TCP options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_tcp TCP
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_TCP==1: Turn on TCP.
+ */
+#if !defined LWIP_TCP || defined __DOXYGEN__
+#define LWIP_TCP                        1
+#endif
+
+/**
+ * TCP_TTL: Default Time-To-Live value.
+ */
+#if !defined TCP_TTL || defined __DOXYGEN__
+#define TCP_TTL                         IP_DEFAULT_TTL
+#endif
+
+/**
+ * TCP_WND: The size of a TCP window.  This must be at least
+ * (2 * TCP_MSS) for things to work well.
+ * ATTENTION: when using TCP_RCV_SCALE, TCP_WND is the total size
+ * with scaling applied. Maximum window value in the TCP header
+ * will be TCP_WND >> TCP_RCV_SCALE
+ */
+#if !defined TCP_WND || defined __DOXYGEN__
+#define TCP_WND                         (4 * TCP_MSS)
+#endif
+
+/**
+ * TCP_MAXRTX: Maximum number of retransmissions of data segments.
+ */
+#if !defined TCP_MAXRTX || defined __DOXYGEN__
+#define TCP_MAXRTX                      12
+#endif
+
+/**
+ * TCP_SYNMAXRTX: Maximum number of retransmissions of SYN segments.
+ */
+#if !defined TCP_SYNMAXRTX || defined __DOXYGEN__
+#define TCP_SYNMAXRTX                   6
+#endif
+
+/**
+ * TCP_QUEUE_OOSEQ==1: TCP will queue segments that arrive out of order.
+ * Define to 0 if your device is low on memory.
+ */
+#if !defined TCP_QUEUE_OOSEQ || defined __DOXYGEN__
+#define TCP_QUEUE_OOSEQ                 LWIP_TCP
+#endif
+
+/**
+ * LWIP_TCP_SACK_OUT==1: TCP will support sending selective acknowledgements (SACKs).
+ */
+#if !defined LWIP_TCP_SACK_OUT || defined __DOXYGEN__
+#define LWIP_TCP_SACK_OUT               0
+#endif
+
+/**
+ * LWIP_TCP_MAX_SACK_NUM: The maximum number of SACK values to include in TCP segments.
+ * Must be at least 1, but is only used if LWIP_TCP_SACK_OUT is enabled.
+ * NOTE: Even though we never send more than 3 or 4 SACK ranges in a single segment
+ * (depending on other options), setting this option to values greater than 4 is not pointless.
+ * This is basically the max number of SACK ranges we want to keep track of.
+ * As new data is delivered, some of the SACK ranges may be removed or merged.
+ * In that case some of those older SACK ranges may be used again.
+ * The amount of memory used to store SACK ranges is LWIP_TCP_MAX_SACK_NUM * 8 bytes for each TCP PCB.
+ */
+#if !defined LWIP_TCP_MAX_SACK_NUM || defined __DOXYGEN__
+#define LWIP_TCP_MAX_SACK_NUM           4
+#endif
+
+/**
+ * TCP_MSS: TCP Maximum segment size. (default is 536, a conservative default,
+ * you might want to increase this.)
+ * For the receive side, this MSS is advertised to the remote side
+ * when opening a connection. For the transmit size, this MSS sets
+ * an upper limit on the MSS advertised by the remote host.
+ */
+#if !defined TCP_MSS || defined __DOXYGEN__
+#define TCP_MSS                         536
+#endif
+
+/**
+ * TCP_CALCULATE_EFF_SEND_MSS: "The maximum size of a segment that TCP really
+ * sends, the 'effective send MSS,' MUST be the smaller of the send MSS (which
+ * reflects the available reassembly buffer size at the remote host) and the
+ * largest size permitted by the IP layer" (RFC 1122)
+ * Setting this to 1 enables code that checks TCP_MSS against the MTU of the
+ * netif used for a connection and limits the MSS if it would be too big otherwise.
+ */
+#if !defined TCP_CALCULATE_EFF_SEND_MSS || defined __DOXYGEN__
+#define TCP_CALCULATE_EFF_SEND_MSS      1
+#endif
+
+
+/**
+ * TCP_SND_BUF: TCP sender buffer space (bytes).
+ * To achieve good performance, this should be at least 2 * TCP_MSS.
+ */
+#if !defined TCP_SND_BUF || defined __DOXYGEN__
+#define TCP_SND_BUF                     (2 * TCP_MSS)
+#endif
+
+/**
+ * TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
+ * as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work.
+ */
+#if !defined TCP_SND_QUEUELEN || defined __DOXYGEN__
+#define TCP_SND_QUEUELEN                ((4 * (TCP_SND_BUF) + (TCP_MSS - 1))/(TCP_MSS))
+#endif
+
+/**
+ * TCP_SNDLOWAT: TCP writable space (bytes). This must be less than
+ * TCP_SND_BUF. It is the amount of space which must be available in the
+ * TCP snd_buf for select to return writable (combined with TCP_SNDQUEUELOWAT).
+ */
+#if !defined TCP_SNDLOWAT || defined __DOXYGEN__
+#define TCP_SNDLOWAT                    LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1)
+#endif
+
+/**
+ * TCP_SNDQUEUELOWAT: TCP writable bufs (pbuf count). This must be less
+ * than TCP_SND_QUEUELEN. If the number of pbufs queued on a pcb drops below
+ * this number, select returns writable (combined with TCP_SNDLOWAT).
+ */
+#if !defined TCP_SNDQUEUELOWAT || defined __DOXYGEN__
+#define TCP_SNDQUEUELOWAT               LWIP_MAX(((TCP_SND_QUEUELEN)/2), 5)
+#endif
+
+/**
+ * TCP_OOSEQ_MAX_BYTES: The default maximum number of bytes queued on ooseq per
+ * pcb if TCP_OOSEQ_BYTES_LIMIT is not defined. Default is 0 (no limit).
+ * Only valid for TCP_QUEUE_OOSEQ==1.
+ */
+#if !defined TCP_OOSEQ_MAX_BYTES || defined __DOXYGEN__
+#define TCP_OOSEQ_MAX_BYTES             0
+#endif
+
+/**
+ * TCP_OOSEQ_BYTES_LIMIT(pcb): Return the maximum number of bytes to be queued
+ * on ooseq per pcb, given the pcb. Only valid for TCP_QUEUE_OOSEQ==1 &&
+ * TCP_OOSEQ_MAX_BYTES==1.
+ * Use this to override TCP_OOSEQ_MAX_BYTES to a dynamic value per pcb.
+ */
+#if !defined TCP_OOSEQ_BYTES_LIMIT
+#if TCP_OOSEQ_MAX_BYTES
+#define TCP_OOSEQ_BYTES_LIMIT(pcb)      TCP_OOSEQ_MAX_BYTES
+#elif defined __DOXYGEN__
+#define TCP_OOSEQ_BYTES_LIMIT(pcb)
+#endif
+#endif
+
+/**
+ * TCP_OOSEQ_MAX_PBUFS: The default maximum number of pbufs queued on ooseq per
+ * pcb if TCP_OOSEQ_BYTES_LIMIT is not defined. Default is 0 (no limit).
+ * Only valid for TCP_QUEUE_OOSEQ==1.
+ */
+#if !defined TCP_OOSEQ_MAX_PBUFS || defined __DOXYGEN__
+#define TCP_OOSEQ_MAX_PBUFS             0
+#endif
+
+/**
+ * TCP_OOSEQ_PBUFS_LIMIT(pcb): Return the maximum number of pbufs to be queued
+ * on ooseq per pcb, given the pcb.  Only valid for TCP_QUEUE_OOSEQ==1 &&
+ * TCP_OOSEQ_MAX_PBUFS==1.
+ * Use this to override TCP_OOSEQ_MAX_PBUFS to a dynamic value per pcb.
+ */
+#if !defined TCP_OOSEQ_PBUFS_LIMIT
+#if TCP_OOSEQ_MAX_PBUFS
+#define TCP_OOSEQ_PBUFS_LIMIT(pcb)      TCP_OOSEQ_MAX_PBUFS
+#elif defined __DOXYGEN__
+#define TCP_OOSEQ_PBUFS_LIMIT(pcb)
+#endif
+#endif
+
+/**
+ * TCP_LISTEN_BACKLOG: Enable the backlog option for tcp listen pcb.
+ */
+#if !defined TCP_LISTEN_BACKLOG || defined __DOXYGEN__
+#define TCP_LISTEN_BACKLOG              0
+#endif
+
+/**
+ * The maximum allowed backlog for TCP listen netconns.
+ * This backlog is used unless another is explicitly specified.
+ * 0xff is the maximum (u8_t).
+ */
+#if !defined TCP_DEFAULT_LISTEN_BACKLOG || defined __DOXYGEN__
+#define TCP_DEFAULT_LISTEN_BACKLOG      0xff
+#endif
+
+/**
+ * TCP_OVERSIZE: The maximum number of bytes that tcp_write may
+ * allocate ahead of time in an attempt to create shorter pbuf chains
+ * for transmission. The meaningful range is 0 to TCP_MSS. Some
+ * suggested values are:
+ *
+ * 0:         Disable oversized allocation. Each tcp_write() allocates a new
+              pbuf (old behaviour).
+ * 1:         Allocate size-aligned pbufs with minimal excess. Use this if your
+ *            scatter-gather DMA requires aligned fragments.
+ * 128:       Limit the pbuf/memory overhead to 20%.
+ * TCP_MSS:   Try to create unfragmented TCP packets.
+ * TCP_MSS/4: Try to create 4 fragments or less per TCP packet.
+ */
+#if !defined TCP_OVERSIZE || defined __DOXYGEN__
+#define TCP_OVERSIZE                    TCP_MSS
+#endif
+
+/**
+ * LWIP_TCP_TIMESTAMPS==1: support the TCP timestamp option.
+ * The timestamp option is currently only used to help remote hosts, it is not
+ * really used locally. Therefore, it is only enabled when a TS option is
+ * received in the initial SYN packet from a remote host.
+ */
+#if !defined LWIP_TCP_TIMESTAMPS || defined __DOXYGEN__
+#define LWIP_TCP_TIMESTAMPS             0
+#endif
+
+/**
+ * TCP_WND_UPDATE_THRESHOLD: difference in window to trigger an
+ * explicit window update
+ */
+#if !defined TCP_WND_UPDATE_THRESHOLD || defined __DOXYGEN__
+#define TCP_WND_UPDATE_THRESHOLD        LWIP_MIN((TCP_WND / 4), (TCP_MSS * 4))
+#endif
+
+/**
+ * LWIP_EVENT_API and LWIP_CALLBACK_API: Only one of these should be set to 1.
+ *     LWIP_EVENT_API==1: The user defines lwip_tcp_event() to receive all
+ *         events (accept, sent, etc) that happen in the system.
+ *     LWIP_CALLBACK_API==1: The PCB callback function is called directly
+ *         for the event. This is the default.
+ */
+#if !defined(LWIP_EVENT_API) && !defined(LWIP_CALLBACK_API) || defined __DOXYGEN__
+#define LWIP_EVENT_API                  0
+#define LWIP_CALLBACK_API               1
+#else
+#ifndef LWIP_EVENT_API
+#define LWIP_EVENT_API                  0
+#endif
+#ifndef LWIP_CALLBACK_API
+#define LWIP_CALLBACK_API               0
+#endif
+#endif
+
+/**
+ * LWIP_WND_SCALE and TCP_RCV_SCALE:
+ * Set LWIP_WND_SCALE to 1 to enable window scaling.
+ * Set TCP_RCV_SCALE to the desired scaling factor (shift count in the
+ * range of [0..14]).
+ * When LWIP_WND_SCALE is enabled but TCP_RCV_SCALE is 0, we can use a large
+ * send window while having a small receive window only.
+ */
+#if !defined LWIP_WND_SCALE || defined __DOXYGEN__
+#define LWIP_WND_SCALE                  0
+#define TCP_RCV_SCALE                   0
+#endif
+
+/**
+ * LWIP_TCP_PCB_NUM_EXT_ARGS:
+ * When this is > 0, every tcp pcb (including listen pcb) includes a number of
+ * additional argument entries in an array (see tcp_ext_arg_alloc_id)
+ */
+#if !defined LWIP_TCP_PCB_NUM_EXT_ARGS || defined __DOXYGEN__
+#define LWIP_TCP_PCB_NUM_EXT_ARGS       0
+#endif
+
+/** LWIP_ALTCP==1: enable the altcp API.
+ * altcp is an abstraction layer that prevents applications linking against the
+ * tcp.h functions but provides the same functionality. It is used to e.g. add
+ * SSL/TLS or proxy-connect support to an application written for the tcp callback
+ * API without that application knowing the protocol details.
+ *
+ * With LWIP_ALTCP==0, applications written against the altcp API can still be
+ * compiled but are directly linked against the tcp.h callback API and then
+ * cannot use layered protocols.
+ *
+ * See @ref altcp_api
+ */
+#if !defined LWIP_ALTCP || defined __DOXYGEN__
+#define LWIP_ALTCP                      0
+#endif
+
+/** LWIP_ALTCP_TLS==1: enable TLS support for altcp API.
+ * This needs a port of the functions in altcp_tls.h to a TLS library.
+ * A port to ARM mbedtls is provided with lwIP, see apps/altcp_tls/ directory
+ * and LWIP_ALTCP_TLS_MBEDTLS option.
+ */
+#if !defined LWIP_ALTCP_TLS || defined __DOXYGEN__
+#define LWIP_ALTCP_TLS                  0
+#endif
+
+#if ESP_LWIP
+#if !defined LWIP_TCP_RTO_TIME || defined __DOXYGEN__
+#define LWIP_TCP_RTO_TIME             3000
+#endif
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- Pbuf options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_pbuf PBUF
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * PBUF_LINK_HLEN: the number of bytes that should be allocated for a
+ * link level header. The default is 14, the standard value for
+ * Ethernet.
+ */
+#if !defined PBUF_LINK_HLEN || defined __DOXYGEN__
+#if defined LWIP_HOOK_VLAN_SET && !defined __DOXYGEN__
+#define PBUF_LINK_HLEN                  (18 + ETH_PAD_SIZE)
+#else /* LWIP_HOOK_VLAN_SET */
+#define PBUF_LINK_HLEN                  (14 + ETH_PAD_SIZE)
+#endif /* LWIP_HOOK_VLAN_SET */
+#endif
+
+/**
+ * PBUF_LINK_ENCAPSULATION_HLEN: the number of bytes that should be allocated
+ * for an additional encapsulation header before ethernet headers (e.g. 802.11)
+ */
+#if !defined PBUF_LINK_ENCAPSULATION_HLEN || defined __DOXYGEN__
+#define PBUF_LINK_ENCAPSULATION_HLEN    0
+#endif
+
+/**
+ * PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. The default is
+ * designed to accommodate single full size TCP frame in one pbuf, including
+ * TCP_MSS, IP header, and link header.
+ */
+#if !defined PBUF_POOL_BUFSIZE || defined __DOXYGEN__
+#define PBUF_POOL_BUFSIZE               LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)
+#endif
+
+/**
+ * LWIP_PBUF_REF_T: Refcount type in pbuf.
+ * Default width of u8_t can be increased if 255 refs are not enough for you.
+ */
+#if !defined LWIP_PBUF_REF_T || defined __DOXYGEN__
+#define LWIP_PBUF_REF_T                 u8_t
+#endif
+
+/**
+ * LWIP_PBUF_CUSTOM_DATA: Store private data on pbufs (e.g. timestamps)
+ * This extends struct pbuf so user can store custom data on every pbuf.
+ */
+#if !defined LWIP_PBUF_CUSTOM_DATA || defined __DOXYGEN__
+#define LWIP_PBUF_CUSTOM_DATA
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------------------
+   ---------- Network Interfaces options ----------
+   ------------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_netif NETIF
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * LWIP_SINGLE_NETIF==1: use a single netif only. This is the common case for
+ * small real-life targets. Some code like routing etc. can be left out.
+ */
+#if !defined LWIP_SINGLE_NETIF || defined __DOXYGEN__
+#define LWIP_SINGLE_NETIF               0
+#endif
+
+/**
+ * LWIP_NETIF_HOSTNAME==1: use DHCP_OPTION_HOSTNAME with netif's hostname
+ * field.
+ */
+#if !defined LWIP_NETIF_HOSTNAME || defined __DOXYGEN__
+#define LWIP_NETIF_HOSTNAME             0
+#endif
+
+/**
+ * LWIP_NETIF_API==1: Support netif api (in netifapi.c)
+ */
+#if !defined LWIP_NETIF_API || defined __DOXYGEN__
+#define LWIP_NETIF_API                  0
+#endif
+
+/**
+ * LWIP_NETIF_STATUS_CALLBACK==1: Support a callback function whenever an interface
+ * changes its up/down status (i.e., due to DHCP IP acquisition)
+ */
+#if !defined LWIP_NETIF_STATUS_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_STATUS_CALLBACK      0
+#endif
+
+/**
+ * LWIP_NETIF_EXT_STATUS_CALLBACK==1: Support an extended callback function 
+ * for several netif related event that supports multiple subscribers.
+ * @see netif_ext_status_callback
+ */
+#if !defined LWIP_NETIF_EXT_STATUS_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_EXT_STATUS_CALLBACK  0
+#endif
+
+/**
+ * LWIP_NETIF_LINK_CALLBACK==1: Support a callback function from an interface
+ * whenever the link changes (i.e., link down)
+ */
+#if !defined LWIP_NETIF_LINK_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_LINK_CALLBACK        0
+#endif
+
+/**
+ * LWIP_NETIF_REMOVE_CALLBACK==1: Support a callback function that is called
+ * when a netif has been removed
+ */
+#if !defined LWIP_NETIF_REMOVE_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_REMOVE_CALLBACK      0
+#endif
+
+/**
+ * LWIP_NETIF_HWADDRHINT==1: Cache link-layer-address hints (e.g. table
+ * indices) in struct netif. TCP and UDP can make use of this to prevent
+ * scanning the ARP table for every sent packet. While this is faster for big
+ * ARP tables or many concurrent connections, it might be counterproductive
+ * if you have a tiny ARP table or if there never are concurrent connections.
+ */
+#if !defined LWIP_NETIF_HWADDRHINT || defined __DOXYGEN__
+#define LWIP_NETIF_HWADDRHINT           0
+#endif
+
+/**
+ * LWIP_NETIF_TX_SINGLE_PBUF: if this is set to 1, lwIP *tries* to put all data
+ * to be sent into one single pbuf. This is for compatibility with DMA-enabled
+ * MACs that do not support scatter-gather.
+ * Beware that this might involve CPU-memcpy before transmitting that would not
+ * be needed without this flag! Use this only if you need to!
+ *
+ * ATTENTION: a driver should *NOT* rely on getting single pbufs but check TX
+ * pbufs for being in one piece. If not, @ref pbuf_clone can be used to get
+ * a single pbuf:
+ *   if (p->next != NULL) {
+ *     struct pbuf *q = pbuf_clone(PBUF_RAW, PBUF_RAM, p);
+ *     if (q == NULL) {
+ *       return ERR_MEM;
+ *     }
+ *     p = q; ATTENTION: do NOT free the old 'p' as the ref belongs to the caller!
+ *   }
+ */
+#if !defined LWIP_NETIF_TX_SINGLE_PBUF || defined __DOXYGEN__
+#define LWIP_NETIF_TX_SINGLE_PBUF       0
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+/**
+ * LWIP_NUM_NETIF_CLIENT_DATA: Number of clients that may store
+ * data in client_data member array of struct netif (max. 256).
+ */
+#if !defined LWIP_NUM_NETIF_CLIENT_DATA || defined __DOXYGEN__
+#define LWIP_NUM_NETIF_CLIENT_DATA      0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- LOOPIF options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_loop Loopback interface
+ * @ingroup lwip_opts_netif
+ * @{
+ */
+/**
+ * LWIP_HAVE_LOOPIF==1: Support loop interface (127.0.0.1).
+ * This is only needed when no real netifs are available. If at least one other
+ * netif is available, loopback traffic uses this netif.
+ */
+#if !defined LWIP_HAVE_LOOPIF || defined __DOXYGEN__
+#define LWIP_HAVE_LOOPIF                (LWIP_NETIF_LOOPBACK && !LWIP_SINGLE_NETIF)
+#endif
+
+/**
+ * LWIP_LOOPIF_MULTICAST==1: Support multicast/IGMP on loop interface (127.0.0.1).
+ */
+#if !defined LWIP_LOOPIF_MULTICAST || defined __DOXYGEN__
+#define LWIP_LOOPIF_MULTICAST           0
+#endif
+
+/**
+ * LWIP_NETIF_LOOPBACK==1: Support sending packets with a destination IP
+ * address equal to the netif IP address, looping them back up the stack.
+ */
+#if !defined LWIP_NETIF_LOOPBACK || defined __DOXYGEN__
+#define LWIP_NETIF_LOOPBACK             0
+#endif
+
+/**
+ * LWIP_LOOPBACK_MAX_PBUFS: Maximum number of pbufs on queue for loopback
+ * sending for each netif (0 = disabled)
+ */
+#if !defined LWIP_LOOPBACK_MAX_PBUFS || defined __DOXYGEN__
+#define LWIP_LOOPBACK_MAX_PBUFS         0
+#endif
+
+/**
+ * LWIP_NETIF_LOOPBACK_MULTITHREADING: Indicates whether threading is enabled in
+ * the system, as netifs must change how they behave depending on this setting
+ * for the LWIP_NETIF_LOOPBACK option to work.
+ * Setting this is needed to avoid reentering non-reentrant functions like
+ * tcp_input().
+ *    LWIP_NETIF_LOOPBACK_MULTITHREADING==1: Indicates that the user is using a
+ *       multithreaded environment like tcpip.c. In this case, netif->input()
+ *       is called directly.
+ *    LWIP_NETIF_LOOPBACK_MULTITHREADING==0: Indicates a polling (or NO_SYS) setup.
+ *       The packets are put on a list and netif_poll() must be called in
+ *       the main application loop.
+ */
+#if !defined LWIP_NETIF_LOOPBACK_MULTITHREADING || defined __DOXYGEN__
+#define LWIP_NETIF_LOOPBACK_MULTITHREADING    (!NO_SYS)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- Thread options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_thread Threading
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * TCPIP_THREAD_NAME: The name assigned to the main tcpip thread.
+ */
+#if !defined TCPIP_THREAD_NAME || defined __DOXYGEN__
+#define TCPIP_THREAD_NAME               "tcpip_thread"
+#endif
+
+/**
+ * TCPIP_THREAD_STACKSIZE: The stack size used by the main tcpip thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined TCPIP_THREAD_STACKSIZE || defined __DOXYGEN__
+#define TCPIP_THREAD_STACKSIZE          0
+#endif
+
+/**
+ * TCPIP_THREAD_PRIO: The priority assigned to the main tcpip thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined TCPIP_THREAD_PRIO || defined __DOXYGEN__
+#define TCPIP_THREAD_PRIO               1
+#endif
+
+/**
+ * TCPIP_MBOX_SIZE: The mailbox size for the tcpip thread messages
+ * The queue size value itself is platform-dependent, but is passed to
+ * sys_mbox_new() when tcpip_init is called.
+ */
+#if !defined TCPIP_MBOX_SIZE || defined __DOXYGEN__
+#define TCPIP_MBOX_SIZE                 0
+#endif
+
+/**
+ * Define this to something that triggers a watchdog. This is called from
+ * tcpip_thread after processing a message.
+ */
+#if !defined LWIP_TCPIP_THREAD_ALIVE || defined __DOXYGEN__
+#define LWIP_TCPIP_THREAD_ALIVE()
+#endif
+
+/**
+ * SLIPIF_THREAD_NAME: The name assigned to the slipif_loop thread.
+ */
+#if !defined SLIPIF_THREAD_NAME || defined __DOXYGEN__
+#define SLIPIF_THREAD_NAME              "slipif_loop"
+#endif
+
+/**
+ * SLIP_THREAD_STACKSIZE: The stack size used by the slipif_loop thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined SLIPIF_THREAD_STACKSIZE || defined __DOXYGEN__
+#define SLIPIF_THREAD_STACKSIZE         0
+#endif
+
+/**
+ * SLIPIF_THREAD_PRIO: The priority assigned to the slipif_loop thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined SLIPIF_THREAD_PRIO || defined __DOXYGEN__
+#define SLIPIF_THREAD_PRIO              1
+#endif
+
+/**
+ * DEFAULT_THREAD_NAME: The name assigned to any other lwIP thread.
+ */
+#if !defined DEFAULT_THREAD_NAME || defined __DOXYGEN__
+#define DEFAULT_THREAD_NAME             "lwIP"
+#endif
+
+/**
+ * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined DEFAULT_THREAD_STACKSIZE || defined __DOXYGEN__
+#define DEFAULT_THREAD_STACKSIZE        0
+#endif
+
+/**
+ * DEFAULT_THREAD_PRIO: The priority assigned to any other lwIP thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined DEFAULT_THREAD_PRIO || defined __DOXYGEN__
+#define DEFAULT_THREAD_PRIO             1
+#endif
+
+/**
+ * DEFAULT_RAW_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_RAW. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#if !defined DEFAULT_RAW_RECVMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_RAW_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_UDP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_UDP. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#if !defined DEFAULT_UDP_RECVMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_UDP_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_TCP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_TCP. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#if !defined DEFAULT_TCP_RECVMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_TCP_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_ACCEPTMBOX_SIZE: The mailbox size for the incoming connections.
+ * The queue size value itself is platform-dependent, but is passed to
+ * sys_mbox_new() when the acceptmbox is created.
+ */
+#if !defined DEFAULT_ACCEPTMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_ACCEPTMBOX_SIZE         0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------------------
+   ---------- Sequential layer options ----------
+   ----------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_netconn Netconn
+ * @ingroup lwip_opts_threadsafe_apis
+ * @{
+ */
+/**
+ * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
+ */
+#if !defined LWIP_NETCONN || defined __DOXYGEN__
+#define LWIP_NETCONN                    1
+#endif
+
+/** LWIP_TCPIP_TIMEOUT==1: Enable tcpip_timeout/tcpip_untimeout to create
+ * timers running in tcpip_thread from another thread.
+ */
+#if !defined LWIP_TCPIP_TIMEOUT || defined __DOXYGEN__
+#define LWIP_TCPIP_TIMEOUT              0
+#endif
+
+/** LWIP_NETCONN_SEM_PER_THREAD==1: Use one (thread-local) semaphore per
+ * thread calling socket/netconn functions instead of allocating one
+ * semaphore per netconn (and per select etc.)
+ * ATTENTION: a thread-local semaphore for API calls is needed:
+ * - LWIP_NETCONN_THREAD_SEM_GET() returning a sys_sem_t*
+ * - LWIP_NETCONN_THREAD_SEM_ALLOC() creating the semaphore
+ * - LWIP_NETCONN_THREAD_SEM_FREE() freeing the semaphore
+ * The latter 2 can be invoked up by calling netconn_thread_init()/netconn_thread_cleanup().
+ * Ports may call these for threads created with sys_thread_new().
+ */
+#if !defined LWIP_NETCONN_SEM_PER_THREAD || defined __DOXYGEN__
+#define LWIP_NETCONN_SEM_PER_THREAD     0
+#endif
+
+/** LWIP_NETCONN_FULLDUPLEX==1: Enable code that allows reading from one thread,
+ * writing from a 2nd thread and closing from a 3rd thread at the same time.
+ * ATTENTION: This is currently really alpha! Some requirements:
+ * - LWIP_NETCONN_SEM_PER_THREAD==1 is required to use one socket/netconn from
+ *   multiple threads at once
+ * - sys_mbox_free() has to unblock receive tasks waiting on recvmbox/acceptmbox
+ *   and prevent a task pending on this during/after deletion
+ */
+#if !defined LWIP_NETCONN_FULLDUPLEX || defined __DOXYGEN__
+#define LWIP_NETCONN_FULLDUPLEX         0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- Socket options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_socket Sockets
+ * @ingroup lwip_opts_threadsafe_apis
+ * @{
+ */
+/**
+ * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
+ */
+#if !defined LWIP_SOCKET || defined __DOXYGEN__
+#define LWIP_SOCKET                     1
+#endif
+
+/**
+ * LWIP_COMPAT_SOCKETS==1: Enable BSD-style sockets functions names through defines.
+ * LWIP_COMPAT_SOCKETS==2: Same as ==1 but correctly named functions are created.
+ * While this helps code completion, it might conflict with existing libraries.
+ * (only used if you use sockets.c)
+ */
+#if !defined LWIP_COMPAT_SOCKETS || defined __DOXYGEN__
+#define LWIP_COMPAT_SOCKETS             1
+#endif
+
+/**
+ * LWIP_POSIX_SOCKETS_IO_NAMES==1: Enable POSIX-style sockets functions names.
+ * Disable this option if you use a POSIX operating system that uses the same
+ * names (read, write & close). (only used if you use sockets.c)
+ */
+#if !defined LWIP_POSIX_SOCKETS_IO_NAMES || defined __DOXYGEN__
+#define LWIP_POSIX_SOCKETS_IO_NAMES     1
+#endif
+
+/**
+ * LWIP_SOCKET_OFFSET==n: Increases the file descriptor number created by LwIP with n.
+ * This can be useful when there are multiple APIs which create file descriptors.
+ * When they all start with a different offset and you won't make them overlap you can
+ * re implement read/write/close/ioctl/fnctl to send the requested action to the right
+ * library (sharing select will need more work though).
+ */
+#if !defined LWIP_SOCKET_OFFSET || defined __DOXYGEN__
+#define LWIP_SOCKET_OFFSET              0
+#endif
+
+/**
+ * LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
+ * options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set
+ * in seconds. (does not require sockets.c, and will affect tcp.c)
+ */
+#if !defined LWIP_TCP_KEEPALIVE || defined __DOXYGEN__
+#define LWIP_TCP_KEEPALIVE              0
+#endif
+
+/**
+ * LWIP_SO_SNDTIMEO==1: Enable send timeout for sockets/netconns and
+ * SO_SNDTIMEO processing.
+ */
+#if !defined LWIP_SO_SNDTIMEO || defined __DOXYGEN__
+#define LWIP_SO_SNDTIMEO                0
+#endif
+
+/**
+ * LWIP_SO_RCVTIMEO==1: Enable receive timeout for sockets/netconns and
+ * SO_RCVTIMEO processing.
+ */
+#if !defined LWIP_SO_RCVTIMEO || defined __DOXYGEN__
+#define LWIP_SO_RCVTIMEO                0
+#endif
+
+/**
+ * LWIP_SO_SNDRCVTIMEO_NONSTANDARD==1: SO_RCVTIMEO/SO_SNDTIMEO take an int
+ * (milliseconds, much like winsock does) instead of a struct timeval (default).
+ */
+#if !defined LWIP_SO_SNDRCVTIMEO_NONSTANDARD || defined __DOXYGEN__
+#define LWIP_SO_SNDRCVTIMEO_NONSTANDARD 0
+#endif
+
+/**
+ * LWIP_SO_RCVBUF==1: Enable SO_RCVBUF processing.
+ */
+#if !defined LWIP_SO_RCVBUF || defined __DOXYGEN__
+#define LWIP_SO_RCVBUF                  0
+#endif
+
+/**
+ * LWIP_SO_LINGER==1: Enable SO_LINGER processing.
+ */
+#if !defined LWIP_SO_LINGER || defined __DOXYGEN__
+#define LWIP_SO_LINGER                  0
+#endif
+
+/**
+ * If LWIP_SO_RCVBUF is used, this is the default value for recv_bufsize.
+ */
+#if !defined RECV_BUFSIZE_DEFAULT || defined __DOXYGEN__
+#define RECV_BUFSIZE_DEFAULT            INT_MAX
+#endif
+
+/**
+ * By default, TCP socket/netconn close waits 20 seconds max to send the FIN
+ */
+#if !defined LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT || defined __DOXYGEN__
+#define LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT 20000
+#endif
+
+/**
+ * SO_REUSE==1: Enable SO_REUSEADDR option.
+ */
+#if !defined SO_REUSE || defined __DOXYGEN__
+#define SO_REUSE                        0
+#endif
+
+/**
+ * SO_REUSE_RXTOALL==1: Pass a copy of incoming broadcast/multicast packets
+ * to all local matches if SO_REUSEADDR is turned on.
+ * WARNING: Adds a memcpy for every packet if passing to more than one pcb!
+ */
+#if !defined SO_REUSE_RXTOALL || defined __DOXYGEN__
+#define SO_REUSE_RXTOALL                0
+#endif
+
+/**
+ * LWIP_FIONREAD_LINUXMODE==0 (default): ioctl/FIONREAD returns the amount of
+ * pending data in the network buffer. This is the way windows does it. It's
+ * the default for lwIP since it is smaller.
+ * LWIP_FIONREAD_LINUXMODE==1: ioctl/FIONREAD returns the size of the next
+ * pending datagram in bytes. This is the way linux does it. This code is only
+ * here for compatibility.
+ */
+#if !defined LWIP_FIONREAD_LINUXMODE || defined __DOXYGEN__
+#define LWIP_FIONREAD_LINUXMODE         0
+#endif
+
+/**
+ * LWIP_SOCKET_SELECT==1 (default): enable select() for sockets (uses a netconn
+ * callback to keep track of events).
+ * This saves RAM (counters per socket) and code (netconn event callback), which
+ * should improve performance a bit).
+ */
+#if !defined LWIP_SOCKET_SELECT || defined __DOXYGEN__
+#define LWIP_SOCKET_SELECT              1
+#endif
+
+/**
+ * LWIP_SOCKET_POLL==1 (default): enable poll() for sockets (including
+ * struct pollfd, nfds_t, and constants)
+ */
+#if !defined LWIP_SOCKET_POLL || defined __DOXYGEN__
+#define LWIP_SOCKET_POLL                1
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------------
+   ---------- Statistics options ----------
+   ----------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_stats Statistics
+ * @ingroup lwip_opts_debug
+ * @{
+ */
+/**
+ * LWIP_STATS==1: Enable statistics collection in lwip_stats.
+ */
+#if !defined LWIP_STATS || defined __DOXYGEN__
+#define LWIP_STATS                      1
+#endif
+
+#if LWIP_STATS
+
+/**
+ * LWIP_STATS_DISPLAY==1: Compile in the statistics output functions.
+ */
+#if !defined LWIP_STATS_DISPLAY || defined __DOXYGEN__
+#define LWIP_STATS_DISPLAY              0
+#endif
+
+/**
+ * LINK_STATS==1: Enable link stats.
+ */
+#if !defined LINK_STATS || defined __DOXYGEN__
+#define LINK_STATS                      1
+#endif
+
+/**
+ * ETHARP_STATS==1: Enable etharp stats.
+ */
+#if !defined ETHARP_STATS || defined __DOXYGEN__
+#define ETHARP_STATS                    (LWIP_ARP)
+#endif
+
+/**
+ * IP_STATS==1: Enable IP stats.
+ */
+#if !defined IP_STATS || defined __DOXYGEN__
+#define IP_STATS                        1
+#endif
+
+/**
+ * IPFRAG_STATS==1: Enable IP fragmentation stats. Default is
+ * on if using either frag or reass.
+ */
+#if !defined IPFRAG_STATS || defined __DOXYGEN__
+#define IPFRAG_STATS                    (IP_REASSEMBLY || IP_FRAG)
+#endif
+
+/**
+ * ICMP_STATS==1: Enable ICMP stats.
+ */
+#if !defined ICMP_STATS || defined __DOXYGEN__
+#define ICMP_STATS                      1
+#endif
+
+/**
+ * IGMP_STATS==1: Enable IGMP stats.
+ */
+#if !defined IGMP_STATS || defined __DOXYGEN__
+#define IGMP_STATS                      (LWIP_IGMP)
+#endif
+
+/**
+ * UDP_STATS==1: Enable UDP stats. Default is on if
+ * UDP enabled, otherwise off.
+ */
+#if !defined UDP_STATS || defined __DOXYGEN__
+#define UDP_STATS                       (LWIP_UDP)
+#endif
+
+/**
+ * TCP_STATS==1: Enable TCP stats. Default is on if TCP
+ * enabled, otherwise off.
+ */
+#if !defined TCP_STATS || defined __DOXYGEN__
+#define TCP_STATS                       (LWIP_TCP)
+#endif
+
+/**
+ * MEM_STATS==1: Enable mem.c stats.
+ */
+#if !defined MEM_STATS || defined __DOXYGEN__
+#define MEM_STATS                       ((MEM_LIBC_MALLOC == 0) && (MEM_USE_POOLS == 0))
+#endif
+
+/**
+ * MEMP_STATS==1: Enable memp.c pool stats.
+ */
+#if !defined MEMP_STATS || defined __DOXYGEN__
+#define MEMP_STATS                      (MEMP_MEM_MALLOC == 0)
+#endif
+
+/**
+ * SYS_STATS==1: Enable system stats (sem and mbox counts, etc).
+ */
+#if !defined SYS_STATS || defined __DOXYGEN__
+#define SYS_STATS                       (NO_SYS == 0)
+#endif
+
+/**
+ * IP6_STATS==1: Enable IPv6 stats.
+ */
+#if !defined IP6_STATS || defined __DOXYGEN__
+#define IP6_STATS                       (LWIP_IPV6)
+#endif
+
+/**
+ * ICMP6_STATS==1: Enable ICMP for IPv6 stats.
+ */
+#if !defined ICMP6_STATS || defined __DOXYGEN__
+#define ICMP6_STATS                     (LWIP_IPV6 && LWIP_ICMP6)
+#endif
+
+/**
+ * IP6_FRAG_STATS==1: Enable IPv6 fragmentation stats.
+ */
+#if !defined IP6_FRAG_STATS || defined __DOXYGEN__
+#define IP6_FRAG_STATS                  (LWIP_IPV6 && (LWIP_IPV6_FRAG || LWIP_IPV6_REASS))
+#endif
+
+/**
+ * MLD6_STATS==1: Enable MLD for IPv6 stats.
+ */
+#if !defined MLD6_STATS || defined __DOXYGEN__
+#define MLD6_STATS                      (LWIP_IPV6 && LWIP_IPV6_MLD)
+#endif
+
+/**
+ * ND6_STATS==1: Enable Neighbor discovery for IPv6 stats.
+ */
+#if !defined ND6_STATS || defined __DOXYGEN__
+#define ND6_STATS                       (LWIP_IPV6)
+#endif
+
+/**
+ * MIB2_STATS==1: Stats for SNMP MIB2.
+ */
+#if !defined MIB2_STATS || defined __DOXYGEN__
+#define MIB2_STATS                      0
+#endif
+
+/**
+ * IP_NAPT_STATS==1: Stats for IP NAPT.
+ */
+#if !defined IP_NAPT_STATS || defined __DOXYGEN__
+#define IP_NAPT_STATS                   (IP_NAPT)
+#endif
+
+
+#else
+
+#define LINK_STATS                      0
+#define ETHARP_STATS                    0
+#define IP_STATS                        0
+#define IPFRAG_STATS                    0
+#define ICMP_STATS                      0
+#define IGMP_STATS                      0
+#define UDP_STATS                       0
+#define TCP_STATS                       0
+#define MEM_STATS                       0
+#define MEMP_STATS                      0
+#define SYS_STATS                       0
+#define LWIP_STATS_DISPLAY              0
+#define IP6_STATS                       0
+#define ICMP6_STATS                     0
+#define IP6_FRAG_STATS                  0
+#define MLD6_STATS                      0
+#define ND6_STATS                       0
+#define MIB2_STATS                      0
+#define IP_NAPT_STATS                   0
+
+#endif /* LWIP_STATS */
+/**
+ * @}
+ */
+
+/*
+   --------------------------------------
+   ---------- Checksum options ----------
+   --------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_checksum Checksum
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_CHECKSUM_CTRL_PER_NETIF==1: Checksum generation/check can be enabled/disabled
+ * per netif.
+ * ATTENTION: if enabled, the CHECKSUM_GEN_* and CHECKSUM_CHECK_* defines must be enabled!
+ */
+#if !defined LWIP_CHECKSUM_CTRL_PER_NETIF || defined __DOXYGEN__
+#define LWIP_CHECKSUM_CTRL_PER_NETIF    0
+#endif
+
+/**
+ * CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.
+ */
+#if !defined CHECKSUM_GEN_IP || defined __DOXYGEN__
+#define CHECKSUM_GEN_IP                 1
+#endif
+
+/**
+ * CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.
+ */
+#if !defined CHECKSUM_GEN_UDP || defined __DOXYGEN__
+#define CHECKSUM_GEN_UDP                1
+#endif
+
+/**
+ * CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.
+ */
+#if !defined CHECKSUM_GEN_TCP || defined __DOXYGEN__
+#define CHECKSUM_GEN_TCP                1
+#endif
+
+/**
+ * CHECKSUM_GEN_ICMP==1: Generate checksums in software for outgoing ICMP packets.
+ */
+#if !defined CHECKSUM_GEN_ICMP || defined __DOXYGEN__
+#define CHECKSUM_GEN_ICMP               1
+#endif
+
+/**
+ * CHECKSUM_GEN_ICMP6==1: Generate checksums in software for outgoing ICMP6 packets.
+ */
+#if !defined CHECKSUM_GEN_ICMP6 || defined __DOXYGEN__
+#define CHECKSUM_GEN_ICMP6              1
+#endif
+
+/**
+ * CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.
+ */
+#if !defined CHECKSUM_CHECK_IP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_IP               1
+#endif
+
+/**
+ * CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.
+ */
+#if !defined CHECKSUM_CHECK_UDP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_UDP              1
+#endif
+
+/**
+ * CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.
+ */
+#if !defined CHECKSUM_CHECK_TCP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_TCP              1
+#endif
+
+/**
+ * CHECKSUM_CHECK_ICMP==1: Check checksums in software for incoming ICMP packets.
+ */
+#if !defined CHECKSUM_CHECK_ICMP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_ICMP             1
+#endif
+
+/**
+ * CHECKSUM_CHECK_ICMP6==1: Check checksums in software for incoming ICMPv6 packets
+ */
+#if !defined CHECKSUM_CHECK_ICMP6 || defined __DOXYGEN__
+#define CHECKSUM_CHECK_ICMP6            1
+#endif
+
+/**
+ * LWIP_CHECKSUM_ON_COPY==1: Calculate checksum when copying data from
+ * application buffers to pbufs.
+ */
+#if !defined LWIP_CHECKSUM_ON_COPY || defined __DOXYGEN__
+#define LWIP_CHECKSUM_ON_COPY           0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------------
+   ---------- IPv6 options ---------------
+   ---------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_ipv6 IPv6
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * LWIP_IPV6==1: Enable IPv6
+ */
+#if !defined LWIP_IPV6 || defined __DOXYGEN__
+#define LWIP_IPV6                       0
+#endif
+
+/**
+ * IPV6_REASS_MAXAGE: Maximum time (in multiples of IP6_REASS_TMR_INTERVAL - so seconds, normally)
+ * a fragmented IP packet waits for all fragments to arrive. If not all fragments arrived
+ * in this time, the whole packet is discarded.
+ */
+#if !defined IPV6_REASS_MAXAGE || defined __DOXYGEN__
+#define IPV6_REASS_MAXAGE               60
+#endif
+
+/**
+ * LWIP_IPV6_SCOPES==1: Enable support for IPv6 address scopes, ensuring that
+ * e.g. link-local addresses are really treated as link-local. Disable this
+ * setting only for single-interface configurations.
+ * All addresses that have a scope according to the default policy (link-local
+ * unicast addresses, interface-local and link-local multicast addresses) should
+ * now have a zone set on them before being passed to the core API, although
+ * lwIP will currently attempt to select a zone on the caller's behalf when 
+ * necessary. Applications that directly assign IPv6 addresses to interfaces
+ * (which is NOT recommended) must now ensure that link-local addresses carry
+ * the netif's zone. See the new ip6_zone.h header file for more information and
+ * relevant macros. For now it is still possible to turn off scopes support
+ * through the new LWIP_IPV6_SCOPES option. When upgrading an implementation that
+ * uses the core API directly, it is highly recommended to enable
+ * LWIP_IPV6_SCOPES_DEBUG at least for a while, to ensure e.g. proper address
+ * initialization.
+ */
+#if !defined LWIP_IPV6_SCOPES || defined __DOXYGEN__
+#define LWIP_IPV6_SCOPES                (LWIP_IPV6 && !LWIP_SINGLE_NETIF)
+#endif
+
+/**
+ * LWIP_IPV6_SCOPES_DEBUG==1: Perform run-time checks to verify that addresses
+ * are properly zoned (see ip6_zone.h on what that means) where it matters.
+ * Enabling this setting is highly recommended when upgrading from an existing
+ * installation that is not yet scope-aware; otherwise it may be too expensive.
+ */
+#if !defined LWIP_IPV6_SCOPES_DEBUG || defined __DOXYGEN__
+#define LWIP_IPV6_SCOPES_DEBUG          0
+#endif
+
+/**
+ * LWIP_IPV6_NUM_ADDRESSES: Number of IPv6 addresses per netif.
+ */
+#if !defined LWIP_IPV6_NUM_ADDRESSES || defined __DOXYGEN__
+#define LWIP_IPV6_NUM_ADDRESSES         3
+#endif
+
+/**
+ * LWIP_IPV6_FORWARD==1: Forward IPv6 packets across netifs
+ */
+#if !defined LWIP_IPV6_FORWARD || defined __DOXYGEN__
+#define LWIP_IPV6_FORWARD               0
+#endif
+
+/**
+ * LWIP_IPV6_FRAG==1: Fragment outgoing IPv6 packets that are too big.
+ */
+#if !defined LWIP_IPV6_FRAG || defined __DOXYGEN__
+#define LWIP_IPV6_FRAG                  1
+#endif
+
+/**
+ * LWIP_IPV6_REASS==1: reassemble incoming IPv6 packets that fragmented
+ */
+#if !defined LWIP_IPV6_REASS || defined __DOXYGEN__
+#define LWIP_IPV6_REASS                 LWIP_IPV6
+#endif
+
+/**
+ * LWIP_IPV6_SEND_ROUTER_SOLICIT==1: Send router solicitation messages during
+ * network startup.
+ */
+#if !defined LWIP_IPV6_SEND_ROUTER_SOLICIT || defined __DOXYGEN__
+#define LWIP_IPV6_SEND_ROUTER_SOLICIT   1
+#endif
+
+/**
+ * LWIP_IPV6_AUTOCONFIG==1: Enable stateless address autoconfiguration as per RFC 4862.
+ */
+#if !defined LWIP_IPV6_AUTOCONFIG || defined __DOXYGEN__
+#define LWIP_IPV6_AUTOCONFIG            LWIP_IPV6
+#endif
+
+/**
+ * LWIP_IPV6_ADDRESS_LIFETIMES==1: Keep valid and preferred lifetimes for each
+ * IPv6 address. Required for LWIP_IPV6_AUTOCONFIG. May still be enabled
+ * otherwise, in which case the application may assign address lifetimes with
+ * the appropriate macros. Addresses with no lifetime are assumed to be static.
+ * If this option is disabled, all addresses are assumed to be static.
+ */
+#if !defined LWIP_IPV6_ADDRESS_LIFETIMES || defined __DOXYGEN__
+#define LWIP_IPV6_ADDRESS_LIFETIMES     LWIP_IPV6_AUTOCONFIG
+#endif
+
+/**
+ * LWIP_IPV6_DUP_DETECT_ATTEMPTS=[0..7]: Number of duplicate address detection attempts.
+ */
+#if !defined LWIP_IPV6_DUP_DETECT_ATTEMPTS || defined __DOXYGEN__
+#define LWIP_IPV6_DUP_DETECT_ATTEMPTS   1
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_icmp6 ICMP6
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_ICMP6==1: Enable ICMPv6 (mandatory per RFC)
+ */
+#if !defined LWIP_ICMP6 || defined __DOXYGEN__
+#define LWIP_ICMP6                      LWIP_IPV6
+#endif
+
+/**
+ * LWIP_ICMP6_DATASIZE: bytes from original packet to send back in
+ * ICMPv6 error messages (0 = default of IP6_MIN_MTU_LENGTH)
+ * ATTENTION: RFC4443 section 2.4 says IP6_MIN_MTU_LENGTH is a MUST,
+ * so override this only if you absolutely have to!
+ */
+#if !defined LWIP_ICMP6_DATASIZE || defined __DOXYGEN__
+#define LWIP_ICMP6_DATASIZE             0
+#endif
+
+/**
+ * LWIP_ICMP6_HL: default hop limit for ICMPv6 messages
+ */
+#if !defined LWIP_ICMP6_HL || defined __DOXYGEN__
+#define LWIP_ICMP6_HL                   255
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_mld6 Multicast listener discovery
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_IPV6_MLD==1: Enable multicast listener discovery protocol.
+ * If LWIP_IPV6 is enabled but this setting is disabled, the MAC layer must
+ * indiscriminately pass all inbound IPv6 multicast traffic to lwIP.
+ */
+#if !defined LWIP_IPV6_MLD || defined __DOXYGEN__
+#define LWIP_IPV6_MLD                   LWIP_IPV6
+#endif
+
+/**
+ * MEMP_NUM_MLD6_GROUP: Max number of IPv6 multicast groups that can be joined.
+ * There must be enough groups so that each netif can join the solicited-node
+ * multicast group for each of its local addresses, plus one for MDNS if
+ * applicable, plus any number of groups to be joined on UDP sockets.
+ */
+#if !defined MEMP_NUM_MLD6_GROUP || defined __DOXYGEN__
+#define MEMP_NUM_MLD6_GROUP             4
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_nd6 Neighbor discovery
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address
+ * is being resolved.
+ */
+#if !defined LWIP_ND6_QUEUEING || defined __DOXYGEN__
+#define LWIP_ND6_QUEUEING               LWIP_IPV6
+#endif
+
+/**
+ * ESP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address
+ * is being resolved.
+ */
+#if !defined ESP_ND6_QUEUEING || defined __DOXYGEN__
+#define ESP_ND6_QUEUEING               LWIP_IPV6
+#endif
+
+/**
+ * MEMP_NUM_ND6_QUEUE: Max number of IPv6 packets to queue during MAC resolution.
+ */
+#if !defined MEMP_NUM_ND6_QUEUE || defined __DOXYGEN__
+#define MEMP_NUM_ND6_QUEUE              20
+#endif
+
+/**
+ * LWIP_ND6_NUM_NEIGHBORS: Number of entries in IPv6 neighbor cache
+ */
+#if !defined LWIP_ND6_NUM_NEIGHBORS || defined __DOXYGEN__
+#define LWIP_ND6_NUM_NEIGHBORS          10
+#endif
+
+/**
+ * LWIP_ND6_NUM_DESTINATIONS: number of entries in IPv6 destination cache
+ */
+#if !defined LWIP_ND6_NUM_DESTINATIONS || defined __DOXYGEN__
+#define LWIP_ND6_NUM_DESTINATIONS       10
+#endif
+
+/**
+ * LWIP_ND6_NUM_PREFIXES: number of entries in IPv6 on-link prefixes cache
+ */
+#if !defined LWIP_ND6_NUM_PREFIXES || defined __DOXYGEN__
+#define LWIP_ND6_NUM_PREFIXES           5
+#endif
+
+/**
+ * LWIP_ND6_NUM_ROUTERS: number of entries in IPv6 default router cache
+ */
+#if !defined LWIP_ND6_NUM_ROUTERS || defined __DOXYGEN__
+#define LWIP_ND6_NUM_ROUTERS            3
+#endif
+
+/**
+ * LWIP_ND6_MAX_MULTICAST_SOLICIT: max number of multicast solicit messages to send
+ * (neighbor solicit and router solicit)
+ */
+#if !defined LWIP_ND6_MAX_MULTICAST_SOLICIT || defined __DOXYGEN__
+#define LWIP_ND6_MAX_MULTICAST_SOLICIT  3
+#endif
+
+/**
+ * LWIP_ND6_MAX_UNICAST_SOLICIT: max number of unicast neighbor solicitation messages
+ * to send during neighbor reachability detection.
+ */
+#if !defined LWIP_ND6_MAX_UNICAST_SOLICIT || defined __DOXYGEN__
+#define LWIP_ND6_MAX_UNICAST_SOLICIT    3
+#endif
+
+/**
+ * Unused: See ND RFC (time in milliseconds).
+ */
+#if !defined LWIP_ND6_MAX_ANYCAST_DELAY_TIME || defined __DOXYGEN__
+#define LWIP_ND6_MAX_ANYCAST_DELAY_TIME 1000
+#endif
+
+/**
+ * Unused: See ND RFC
+ */
+#if !defined LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT || defined __DOXYGEN__
+#define LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT  3
+#endif
+
+/**
+ * LWIP_ND6_REACHABLE_TIME: default neighbor reachable time (in milliseconds).
+ * May be updated by router advertisement messages.
+ */
+#if !defined LWIP_ND6_REACHABLE_TIME || defined __DOXYGEN__
+#define LWIP_ND6_REACHABLE_TIME         30000
+#endif
+
+/**
+ * LWIP_ND6_RETRANS_TIMER: default retransmission timer for solicitation messages
+ */
+#if !defined LWIP_ND6_RETRANS_TIMER || defined __DOXYGEN__
+#define LWIP_ND6_RETRANS_TIMER          1000
+#endif
+
+/**
+ * LWIP_ND6_DELAY_FIRST_PROBE_TIME: Delay before first unicast neighbor solicitation
+ * message is sent, during neighbor reachability detection.
+ */
+#if !defined LWIP_ND6_DELAY_FIRST_PROBE_TIME || defined __DOXYGEN__
+#define LWIP_ND6_DELAY_FIRST_PROBE_TIME 5000
+#endif
+
+/**
+ * LWIP_ND6_ALLOW_RA_UPDATES==1: Allow Router Advertisement messages to update
+ * Reachable time and retransmission timers, and netif MTU.
+ */
+#if !defined LWIP_ND6_ALLOW_RA_UPDATES || defined __DOXYGEN__
+#define LWIP_ND6_ALLOW_RA_UPDATES       1
+#endif
+
+/**
+ * LWIP_ND6_TCP_REACHABILITY_HINTS==1: Allow TCP to provide Neighbor Discovery
+ * with reachability hints for connected destinations. This helps avoid sending
+ * unicast neighbor solicitation messages.
+ */
+#if !defined LWIP_ND6_TCP_REACHABILITY_HINTS || defined __DOXYGEN__
+#define LWIP_ND6_TCP_REACHABILITY_HINTS 1
+#endif
+
+/**
+ * LWIP_ND6_RDNSS_MAX_DNS_SERVERS > 0: Use IPv6 Router Advertisement Recursive
+ * DNS Server Option (as per RFC 6106) to copy a defined maximum number of DNS
+ * servers to the DNS module.
+ */
+#if !defined LWIP_ND6_RDNSS_MAX_DNS_SERVERS || defined __DOXYGEN__
+#define LWIP_ND6_RDNSS_MAX_DNS_SERVERS  0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_dhcpv6 DHCPv6
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_IPV6_DHCP6==1: enable DHCPv6 stateful/stateless address autoconfiguration.
+ */
+#if !defined LWIP_IPV6_DHCP6 || defined __DOXYGEN__
+#define LWIP_IPV6_DHCP6                 0
+#endif
+
+/**
+ * LWIP_IPV6_DHCP6_STATEFUL==1: enable DHCPv6 stateful address autoconfiguration.
+ * (not supported, yet!)
+ */
+#if !defined LWIP_IPV6_DHCP6_STATEFUL || defined __DOXYGEN__
+#define LWIP_IPV6_DHCP6_STATEFUL        0
+#endif
+
+/**
+ * LWIP_IPV6_DHCP6_STATELESS==1: enable DHCPv6 stateless address autoconfiguration.
+ */
+#if !defined LWIP_IPV6_DHCP6_STATELESS || defined __DOXYGEN__
+#define LWIP_IPV6_DHCP6_STATELESS       LWIP_IPV6_DHCP6
+#endif
+
+/**
+ * LWIP_DHCP6_GETS_NTP==1: Request NTP servers via DHCPv6. For each
+ * response packet, a callback is called, which has to be provided by the port:
+ * void dhcp6_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs);
+*/
+#if !defined LWIP_DHCP6_GET_NTP_SRV || defined __DOXYGEN__
+#define LWIP_DHCP6_GET_NTP_SRV          0
+#endif
+
+/**
+ * The maximum of NTP servers requested
+ */
+#if !defined LWIP_DHCP6_MAX_NTP_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP6_MAX_NTP_SERVERS      1
+#endif
+
+/**
+ * LWIP_DHCP6_MAX_DNS_SERVERS > 0: Request DNS servers via DHCPv6.
+ * DNS servers received in the response are passed to DNS via @ref dns_setserver()
+ * (up to the maximum limit defined here).
+ */
+#if !defined LWIP_DHCP6_MAX_DNS_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP6_MAX_DNS_SERVERS      DNS_MAX_SERVERS
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------------
+   ---------- Hook options ---------------
+   ---------------------------------------
+*/
+
+/**
+ * @defgroup lwip_opts_hooks Hooks
+ * @ingroup lwip_opts_infrastructure
+ * Hooks are undefined by default, define them to a function if you need them.
+ * @{
+ */
+
+/**
+ * LWIP_HOOK_FILENAME: Custom filename to \#include in files that provide hooks.
+ * Declare your hook function prototypes in there, you may also \#include all headers
+ * providing data types that are need in this file.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_FILENAME "path/to/my/lwip_hooks.h"
+#endif
+
+/**
+ * LWIP_HOOK_TCP_ISN:
+ * Hook for generation of the Initial Sequence Number (ISN) for a new TCP
+ * connection. The default lwIP ISN generation algorithm is very basic and may
+ * allow for TCP spoofing attacks. This hook provides the means to implement
+ * the standardized ISN generation algorithm from RFC 6528 (see contrib/adons/tcp_isn),
+ * or any other desired algorithm as a replacement.
+ * Called from tcp_connect() and tcp_listen_input() when an ISN is needed for
+ * a new TCP connection, if TCP support (@ref LWIP_TCP) is enabled.\n
+ * Signature:\code{.c}
+ * u32_t my_hook_tcp_isn(const ip_addr_t* local_ip, u16_t local_port, const ip_addr_t* remote_ip, u16_t remote_port);
+ * \endcode
+ * - it may be necessary to use "struct ip_addr" (ip4_addr, ip6_addr) instead of "ip_addr_t" in function declarations\n
+ * Arguments:
+ * - local_ip: pointer to the local IP address of the connection
+ * - local_port: local port number of the connection (host-byte order)
+ * - remote_ip: pointer to the remote IP address of the connection
+ * - remote_port: remote port number of the connection (host-byte order)\n
+ * Return value:
+ * - the 32-bit Initial Sequence Number to use for the new TCP connection.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_ISN(local_ip, local_port, remote_ip, remote_port)
+#endif
+
+/**
+ * LWIP_HOOK_TCP_INPACKET_PCB:
+ * Hook for intercepting incoming packets before they are passed to a pcb. This
+ * allows updating some state or even dropping a packet.
+ * Signature:\code{.c}
+ * err_t my_hook_tcp_inpkt(struct tcp_pcb *pcb, struct tcp_hdr *hdr, u16_t optlen, u16_t opt1len, u8_t *opt2, struct pbuf *p);
+ * \endcode
+ * Arguments:
+ * - pcb: tcp_pcb selected for input of this packet (ATTENTION: this may be
+ *        struct tcp_pcb_listen if pcb->state == LISTEN)
+ * - hdr: pointer to tcp header (ATTENTION: tcp options may not be in one piece!)
+ * - optlen: tcp option length
+ * - opt1len: tcp option length 1st part
+ * - opt2: if this is != NULL, tcp options are split among 2 pbufs. In that case,
+ *         options start at right after the tcp header ('(u8_t*)(hdr + 1)') for
+ *         the first 'opt1len' bytes and the rest starts at 'opt2'. opt2len can
+ *         be simply calculated: 'opt2len = optlen - opt1len;'
+ * - p: input packet, p->payload points to application data (that's why tcp hdr
+ *      and options are passed in seperately)
+ * Return value:
+ * - ERR_OK: continue input of this packet as normal
+ * - != ERR_OK: drop this packet for input (don't continue input processing)
+ *
+ * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
+ * state or any pcb lists) from this callback!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_INPACKET_PCB(pcb, hdr, optlen, opt1len, opt2, p)
+#endif
+
+/**
+ * LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH:
+ * Hook for increasing the size of the options allocated with a tcp header.
+ * Together with LWIP_HOOK_TCP_OUT_ADD_TCPOPTS, this can be used to add custom
+ * options to outgoing tcp segments.
+ * Signature:\code{.c}
+ * u8_t my_hook_tcp_out_tcpopt_length(const struct tcp_pcb *pcb, u8_t internal_option_length);
+ * \endcode
+ * Arguments:
+ * - pcb: tcp_pcb that transmits (ATTENTION: this may be NULL or
+ *        struct tcp_pcb_listen if pcb->state == LISTEN)
+ * - internal_option_length: tcp option length used by the stack internally
+ * Return value:
+ * - a number of bytes to allocate for tcp options (internal_option_length <= ret <= 40)
+ *
+ * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
+ * state or any pcb lists) from this callback!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH(pcb, internal_len)
+#endif
+
+/**
+ * LWIP_HOOK_TCP_OUT_ADD_TCPOPTS:
+ * Hook for adding custom options to outgoing tcp segments.
+ * Space for these custom options has to be reserved via LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH.
+ * Signature:\code{.c}
+ * u32_t *my_hook_tcp_out_add_tcpopts(struct pbuf *p, struct tcp_hdr *hdr, const struct tcp_pcb *pcb, u32_t *opts);
+ * \endcode
+ * Arguments:
+ * - p: output packet, p->payload pointing to tcp header, data follows
+ * - hdr: tcp header
+ * - pcb: tcp_pcb that transmits (ATTENTION: this may be NULL or
+ *        struct tcp_pcb_listen if pcb->state == LISTEN)
+ * - opts: pointer where to add the custom options (there may already be options
+ *         between the header and these)
+ * Return value:
+ * - pointer pointing directly after the inserted options
+ *
+ * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
+ * state or any pcb lists) from this callback!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_OUT_ADD_TCPOPTS(p, hdr, pcb, opts)
+#endif
+
+/**
+ * LWIP_HOOK_IP4_INPUT(pbuf, input_netif):
+ * Called from ip_input() (IPv4)
+ * Signature:\code{.c}
+ *   int my_hook(struct pbuf *pbuf, struct netif *input_netif);
+ * \endcode
+ * Arguments:
+ * - pbuf: received struct pbuf passed to ip_input()
+ * - input_netif: struct netif on which the packet has been received
+ * Return values:
+ * - 0: Hook has not consumed the packet, packet is processed as normal
+ * - != 0: Hook has consumed the packet.
+ * If the hook consumed the packet, 'pbuf' is in the responsibility of the hook
+ * (i.e. free it when done).
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_INPUT(pbuf, input_netif)
+#endif
+
+/**
+ * LWIP_HOOK_IP4_ROUTE(dest):
+ * Called from ip_route() (IPv4)
+ * Signature:\code{.c}
+ *   struct netif *my_hook(const ip4_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - dest: destination IPv4 address
+ * Returns values:
+ * - the destination netif
+ * - NULL if no destination netif is found. In that case, ip_route() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_ROUTE()
+#endif
+
+/**
+ * LWIP_HOOK_IP4_ROUTE_SRC(src, dest):
+ * Source-based routing for IPv4 - called from ip_route() (IPv4)
+ * Signature:\code{.c}
+ *   struct netif *my_hook(const ip4_addr_t *src, const ip4_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - src: local/source IPv4 address
+ * - dest: destination IPv4 address
+ * Returns values:
+ * - the destination netif
+ * - NULL if no destination netif is found. In that case, ip_route() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_ROUTE_SRC(src, dest)
+#endif
+
+/**
+ * LWIP_HOOK_IP4_CANFORWARD(src, dest):
+ * Check if an IPv4 can be forwarded - called from:
+ * ip4_input() -> ip4_forward() -> ip4_canforward() (IPv4)
+ * - source address is available via ip4_current_src_addr()
+ * - calling an output function in this context (e.g. multicast router) is allowed
+ * Signature:\code{.c}
+ *   int my_hook(struct pbuf *p, u32_t dest_addr_hostorder);
+ * \endcode
+ * Arguments:
+ * - p: packet to forward
+ * - dest: destination IPv4 address
+ * Returns values:
+ * - 1: forward
+ * - 0: don't forward
+ * - -1: no decision. In that case, ip4_canforward() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_CANFORWARD(src, dest)
+#endif
+
+/**
+ * LWIP_HOOK_ETHARP_GET_GW(netif, dest):
+ * Called from etharp_output() (IPv4)
+ * Signature:\code{.c}
+ *   const ip4_addr_t *my_hook(struct netif *netif, const ip4_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - netif: the netif used for sending
+ * - dest: the destination IPv4 address
+ * Return values:
+ * - the IPv4 address of the gateway to handle the specified destination IPv4 address
+ * - NULL, in which case the netif's default gateway is used
+ *
+ * The returned address MUST be directly reachable on the specified netif!
+ * This function is meant to implement advanced IPv4 routing together with
+ * LWIP_HOOK_IP4_ROUTE(). The actual routing/gateway table implementation is
+ * not part of lwIP but can e.g. be hidden in the netif's state argument.
+*/
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_ETHARP_GET_GW(netif, dest)
+#endif
+
+/**
+ * LWIP_HOOK_IP6_INPUT(pbuf, input_netif):
+ * Called from ip6_input() (IPv6)
+ * Signature:\code{.c}
+ *   int my_hook(struct pbuf *pbuf, struct netif *input_netif);
+ * \endcode
+ * Arguments:
+ * - pbuf: received struct pbuf passed to ip6_input()
+ * - input_netif: struct netif on which the packet has been received
+ * Return values:
+ * - 0: Hook has not consumed the packet, packet is processed as normal
+ * - != 0: Hook has consumed the packet.
+ * If the hook consumed the packet, 'pbuf' is in the responsibility of the hook
+ * (i.e. free it when done).
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP6_INPUT(pbuf, input_netif)
+#endif
+
+/**
+ * LWIP_HOOK_IP6_ROUTE(src, dest):
+ * Called from ip_route() (IPv6)
+ * Signature:\code{.c}
+ *   struct netif *my_hook(const ip6_addr_t *dest, const ip6_addr_t *src);
+ * \endcode
+ * Arguments:
+ * - src: source IPv6 address
+ * - dest: destination IPv6 address
+ * Return values:
+ * - the destination netif
+ * - NULL if no destination netif is found. In that case, ip6_route() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP6_ROUTE(src, dest)
+#endif
+
+/**
+ * LWIP_HOOK_ND6_GET_GW(netif, dest):
+ * Called from nd6_get_next_hop_entry() (IPv6)
+ * Signature:\code{.c}
+ *   const ip6_addr_t *my_hook(struct netif *netif, const ip6_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - netif: the netif used for sending
+ * - dest: the destination IPv6 address
+ * Return values:
+ * - the IPv6 address of the next hop to handle the specified destination IPv6 address
+ * - NULL, in which case a NDP-discovered router is used instead
+ *
+ * The returned address MUST be directly reachable on the specified netif!
+ * This function is meant to implement advanced IPv6 routing together with
+ * LWIP_HOOK_IP6_ROUTE(). The actual routing/gateway table implementation is
+ * not part of lwIP but can e.g. be hidden in the netif's state argument.
+*/
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_ND6_GET_GW(netif, dest)
+#endif
+
+/**
+ * LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr):
+ * Called from ethernet_input() if VLAN support is enabled
+ * Signature:\code{.c}
+ *   int my_hook(struct netif *netif, struct eth_hdr *eth_hdr, struct eth_vlan_hdr *vlan_hdr);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif on which the packet has been received
+ * - eth_hdr: struct eth_hdr of the packet
+ * - vlan_hdr: struct eth_vlan_hdr of the packet
+ * Return values:
+ * - 0: Packet must be dropped.
+ * - != 0: Packet must be accepted.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr)
+#endif
+
+/**
+ * LWIP_HOOK_VLAN_SET:
+ * Hook can be used to set prio_vid field of vlan_hdr. If you need to store data
+ * on per-netif basis to implement this callback, see @ref netif_cd.
+ * Called from ethernet_output() if VLAN support (@ref ETHARP_SUPPORT_VLAN) is enabled.\n
+ * Signature:\code{.c}
+ *   s32_t my_hook_vlan_set(struct netif* netif, struct pbuf* pbuf, const struct eth_addr* src, const struct eth_addr* dst, u16_t eth_type);\n
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - p: struct pbuf packet to be sent
+ * - src: source eth address
+ * - dst: destination eth address
+ * - eth_type: ethernet type to packet to be sent\n
+ * 
+ * 
+ * Return values:
+ * - &lt;0: Packet shall not contain VLAN header.
+ * - 0 &lt;= return value &lt;= 0xFFFF: Packet shall contain VLAN header. Return value is prio_vid in host byte order.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_VLAN_SET(netif, p, src, dst, eth_type)
+#endif
+
+/**
+ * LWIP_HOOK_MEMP_AVAILABLE(memp_t_type):
+ * Called from memp_free() when a memp pool was empty and an item is now available
+ * Signature:\code{.c}
+ *   void my_hook(memp_t type);
+ * \endcode
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_MEMP_AVAILABLE(memp_t_type)
+#endif
+
+/**
+ * LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(pbuf, netif):
+ * Called from ethernet_input() when an unknown eth type is encountered.
+ * Signature:\code{.c}
+ *   err_t my_hook(struct pbuf* pbuf, struct netif* netif);
+ * \endcode
+ * Arguments:
+ * - p: rx packet with unknown eth type
+ * - netif: netif on which the packet has been received
+ * Return values:
+ * - ERR_OK if packet is accepted (hook function now owns the pbuf)
+ * - any error code otherwise (pbuf is freed)
+ *
+ * Payload points to ethernet header!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(pbuf, netif)
+#endif
+
+/**
+ * LWIP_HOOK_DHCP_APPEND_OPTIONS(netif, dhcp, state, msg, msg_type, options_len_ptr):
+ * Called from various dhcp functions when sending a DHCP message.
+ * This hook is called just before the DHCP message trailer is added, so the
+ * options are at the end of a DHCP message.
+ * Signature:\code{.c}
+ *   void my_hook(struct netif *netif, struct dhcp *dhcp, u8_t state, struct dhcp_msg *msg,
+ *                u8_t msg_type, u16_t *options_len_ptr);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - dhcp: struct dhcp on that netif
+ * - state: current dhcp state (dhcp_state_enum_t as an u8_t)
+ * - msg: struct dhcp_msg that will be sent
+ * - msg_type: dhcp message type to be sent (u8_t)
+ * - options_len_ptr: pointer to the current length of options in the dhcp_msg "msg"
+ *                    (must be increased when options are added!)
+ *
+ * Options need to appended like this:
+ *   LWIP_ASSERT("dhcp option overflow", *options_len_ptr + option_len + 2 <= DHCP_OPTIONS_LEN);
+ *   msg->options[(*options_len_ptr)++] = &lt;option_number&gt;;
+ *   msg->options[(*options_len_ptr)++] = &lt;option_len&gt;;
+ *   msg->options[(*options_len_ptr)++] = &lt;option_bytes&gt;;
+ *   [...]
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_DHCP_APPEND_OPTIONS(netif, dhcp, state, msg, msg_type, options_len_ptr)
+#endif
+
+/**
+ * LWIP_HOOK_DHCP_PARSE_OPTION(netif, dhcp, state, msg, msg_type, option, len, pbuf, option_value_offset):
+ * Called from dhcp_parse_reply when receiving a DHCP message.
+ * This hook is called for every option in the received message that is not handled internally.
+ * Signature:\code{.c}
+ *   void my_hook(struct netif *netif, struct dhcp *dhcp, u8_t state, struct dhcp_msg *msg,
+ *                u8_t msg_type, u8_t option, u8_t option_len, struct pbuf *pbuf, u16_t option_value_offset);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - dhcp: struct dhcp on that netif
+ * - state: current dhcp state (dhcp_state_enum_t as an u8_t)
+ * - msg: struct dhcp_msg that was received
+ * - msg_type: dhcp message type received (u8_t, ATTENTION: only valid after
+ *             the message type option has been parsed!)
+ * - option: option value (u8_t)
+ * - len: option data length (u8_t)
+ * - pbuf: pbuf where option data is contained
+ * - option_value_offset: offset in pbuf where option data begins
+ *
+ * A nice way to get the option contents is pbuf_get_contiguous():
+ *  u8_t buf[32];
+ *  u8_t *ptr = (u8_t*)pbuf_get_contiguous(p, buf, sizeof(buf), LWIP_MIN(option_len, sizeof(buf)), offset);
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_DHCP_PARSE_OPTION(netif, dhcp, state, msg, msg_type, option, len, pbuf, offset)
+#endif
+
+/**
+ * LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, state, msg, msg_type, options_len_ptr, max_len):
+ * Called from various dhcp6 functions when sending a DHCP6 message.
+ * This hook is called just before the DHCP6 message is sent, so the
+ * options are at the end of a DHCP6 message.
+ * Signature:\code{.c}
+ *   void my_hook(struct netif *netif, struct dhcp6 *dhcp, u8_t state, struct dhcp6_msg *msg,
+ *                u8_t msg_type, u16_t *options_len_ptr);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - dhcp6: struct dhcp6 on that netif
+ * - state: current dhcp6 state (dhcp6_state_enum_t as an u8_t)
+ * - msg: struct dhcp6_msg that will be sent
+ * - msg_type: dhcp6 message type to be sent (u8_t)
+ * - options_len_ptr: pointer to the current length of options in the dhcp6_msg "msg"
+ *                    (must be increased when options are added!)
+ *
+ * Options need to appended like this:
+ *   u8_t *options = (u8_t *)(msg + 1);
+ *   LWIP_ASSERT("dhcp option overflow", sizeof(struct dhcp6_msg) + *options_len_ptr + newoptlen <= max_len);
+ *   options[(*options_len_ptr)++] = &lt;option_data&gt;;
+ *   [...]
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, state, msg, msg_type, options_len_ptr, max_len)
+#endif
+
+/**
+ * LWIP_HOOK_SOCKETS_SETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+ * Called from socket API to implement setsockopt() for options not provided by lwIP.
+ * Core lock is held when this hook is called.
+ * Signature:\code{.c}
+ *   int my_hook(int s, struct lwip_sock *sock, int level, int optname, const void *optval, socklen_t optlen, int *err)
+ * \endcode
+ * Arguments:
+ * - s: socket file descriptor
+ * - sock: internal socket descriptor (see lwip/priv/sockets_priv.h)
+ * - level: protocol level at which the option resides
+ * - optname: option to set
+ * - optval: value to set
+ * - optlen: size of optval
+ * - err: output error
+ * Return values:
+ * - 0: Hook has not consumed the option, code continues as normal (to internal options)
+ * - != 0: Hook has consumed the option, 'err' is returned
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_SOCKETS_SETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+#endif
+
+/**
+ * LWIP_HOOK_SOCKETS_GETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+ * Called from socket API to implement getsockopt() for options not provided by lwIP.
+ * Core lock is held when this hook is called.
+ * Signature:\code{.c}
+ *   int my_hook(int s, struct lwip_sock *sock, int level, int optname, void *optval, socklen_t *optlen, int *err)
+ * \endcode
+ * Arguments:
+ * - s: socket file descriptor
+ * - sock: internal socket descriptor (see lwip/priv/sockets_priv.h)
+ * - level: protocol level at which the option resides
+ * - optname: option to get
+ * - optval: value to get
+ * - optlen: size of optval
+ * - err: output error
+ * Return values:
+ * - 0: Hook has not consumed the option, code continues as normal (to internal options)
+ * - != 0: Hook has consumed the option, 'err' is returned
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_SOCKETS_GETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+#endif
+
+/**
+ * LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE(name, addr, addrtype, err)
+ * Called from netconn APIs (not usable with callback apps) allowing an
+ * external DNS resolver (which uses sequential API) to handle the query.
+ * Signature:\code{.c}
+ *   int my_hook(const char *name, ip_addr_t *addr, u8_t addrtype, err_t *err)
+ * \endcode
+ * Arguments:
+ * - name: hostname to resolve
+ * - addr: output host address
+ * - addrtype: type of address to query
+ * - err: output error
+ * Return values:
+ * - 0: Hook has not consumed hostname query, query continues into DNS module
+ * - != 0: Hook has consumed the query
+ *
+ * err must also be checked to determine if the hook consumed the query, but
+ * the query failed
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE(name, addr, addrtype, err)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------------
+   ---------- Debugging options ----------
+   ---------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_debugmsg Debug messages
+ * @ingroup lwip_opts_debug
+ * @{
+ */
+/**
+ * LWIP_DBG_MIN_LEVEL: After masking, the value of the debug is
+ * compared against this value. If it is smaller, then debugging
+ * messages are written.
+ * @see debugging_levels
+ */
+#if !defined LWIP_DBG_MIN_LEVEL || defined __DOXYGEN__
+#define LWIP_DBG_MIN_LEVEL              LWIP_DBG_LEVEL_ALL
+#endif
+
+/**
+ * LWIP_DBG_TYPES_ON: A mask that can be used to globally enable/disable
+ * debug messages of certain types.
+ * @see debugging_levels
+ */
+#if !defined LWIP_DBG_TYPES_ON || defined __DOXYGEN__
+#define LWIP_DBG_TYPES_ON               LWIP_DBG_ON
+#endif
+
+/**
+ * ETHARP_DEBUG: Enable debugging in etharp.c.
+ */
+#if !defined ETHARP_DEBUG || defined __DOXYGEN__
+#define ETHARP_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * NETIF_DEBUG: Enable debugging in netif.c.
+ */
+#if !defined NETIF_DEBUG || defined __DOXYGEN__
+#define NETIF_DEBUG                     LWIP_DBG_OFF
+#endif
+
+/**
+ * PBUF_DEBUG: Enable debugging in pbuf.c.
+ */
+#if !defined PBUF_DEBUG || defined __DOXYGEN__
+#define PBUF_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * API_LIB_DEBUG: Enable debugging in api_lib.c.
+ */
+#if !defined API_LIB_DEBUG || defined __DOXYGEN__
+#define API_LIB_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * API_MSG_DEBUG: Enable debugging in api_msg.c.
+ */
+#if !defined API_MSG_DEBUG || defined __DOXYGEN__
+#define API_MSG_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * SOCKETS_DEBUG: Enable debugging in sockets.c.
+ */
+#if !defined SOCKETS_DEBUG || defined __DOXYGEN__
+#define SOCKETS_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * ICMP_DEBUG: Enable debugging in icmp.c.
+ */
+#if !defined ICMP_DEBUG || defined __DOXYGEN__
+#define ICMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * IGMP_DEBUG: Enable debugging in igmp.c.
+ */
+#if !defined IGMP_DEBUG || defined __DOXYGEN__
+#define IGMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * INET_DEBUG: Enable debugging in inet.c.
+ */
+#if !defined INET_DEBUG || defined __DOXYGEN__
+#define INET_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * IP_DEBUG: Enable debugging for IP.
+ */
+#if !defined IP_DEBUG || defined __DOXYGEN__
+#define IP_DEBUG                        LWIP_DBG_OFF
+#endif
+
+/**
+ * IP_REASS_DEBUG: Enable debugging in ip_frag.c for both frag & reass.
+ */
+#if !defined IP_REASS_DEBUG || defined __DOXYGEN__
+#define IP_REASS_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * RAW_DEBUG: Enable debugging in raw.c.
+ */
+#if !defined RAW_DEBUG || defined __DOXYGEN__
+#define RAW_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * MEM_DEBUG: Enable debugging in mem.c.
+ */
+#if !defined MEM_DEBUG || defined __DOXYGEN__
+#define MEM_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * MEMP_DEBUG: Enable debugging in memp.c.
+ */
+#if !defined MEMP_DEBUG || defined __DOXYGEN__
+#define MEMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * SYS_DEBUG: Enable debugging in sys.c.
+ */
+#if !defined SYS_DEBUG || defined __DOXYGEN__
+#define SYS_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TIMERS_DEBUG: Enable debugging in timers.c.
+ */
+#if !defined TIMERS_DEBUG || defined __DOXYGEN__
+#define TIMERS_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_DEBUG: Enable debugging for TCP.
+ */
+#if !defined TCP_DEBUG || defined __DOXYGEN__
+#define TCP_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_INPUT_DEBUG: Enable debugging in tcp_in.c for incoming debug.
+ */
+#if !defined TCP_INPUT_DEBUG || defined __DOXYGEN__
+#define TCP_INPUT_DEBUG                 LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_FR_DEBUG: Enable debugging in tcp_in.c for fast retransmit.
+ */
+#if !defined TCP_FR_DEBUG || defined __DOXYGEN__
+#define TCP_FR_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_RTO_DEBUG: Enable debugging in TCP for retransmit
+ * timeout.
+ */
+#if !defined TCP_RTO_DEBUG || defined __DOXYGEN__
+#define TCP_RTO_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_CWND_DEBUG: Enable debugging for TCP congestion window.
+ */
+#if !defined TCP_CWND_DEBUG || defined __DOXYGEN__
+#define TCP_CWND_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_WND_DEBUG: Enable debugging in tcp_in.c for window updating.
+ */
+#if !defined TCP_WND_DEBUG || defined __DOXYGEN__
+#define TCP_WND_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_OUTPUT_DEBUG: Enable debugging in tcp_out.c output functions.
+ */
+#if !defined TCP_OUTPUT_DEBUG || defined __DOXYGEN__
+#define TCP_OUTPUT_DEBUG                LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_RST_DEBUG: Enable debugging for TCP with the RST message.
+ */
+#if !defined TCP_RST_DEBUG || defined __DOXYGEN__
+#define TCP_RST_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_QLEN_DEBUG: Enable debugging for TCP queue lengths.
+ */
+#if !defined TCP_QLEN_DEBUG || defined __DOXYGEN__
+#define TCP_QLEN_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * UDP_DEBUG: Enable debugging in UDP.
+ */
+#if !defined UDP_DEBUG || defined __DOXYGEN__
+#define UDP_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TCPIP_DEBUG: Enable debugging in tcpip.c.
+ */
+#if !defined TCPIP_DEBUG || defined __DOXYGEN__
+#define TCPIP_DEBUG                     LWIP_DBG_OFF
+#endif
+
+/**
+ * SLIP_DEBUG: Enable debugging in slipif.c.
+ */
+#if !defined SLIP_DEBUG || defined __DOXYGEN__
+#define SLIP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * DHCP_DEBUG: Enable debugging in dhcp.c.
+ */
+#if !defined DHCP_DEBUG || defined __DOXYGEN__
+#define DHCP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * AUTOIP_DEBUG: Enable debugging in autoip.c.
+ */
+#if !defined AUTOIP_DEBUG || defined __DOXYGEN__
+#define AUTOIP_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * DNS_DEBUG: Enable debugging for DNS.
+ */
+#if !defined DNS_DEBUG || defined __DOXYGEN__
+#define DNS_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * IP6_DEBUG: Enable debugging for IPv6.
+ */
+#if !defined IP6_DEBUG || defined __DOXYGEN__
+#define IP6_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * DHCP6_DEBUG: Enable debugging in dhcp6.c.
+ */
+#if !defined DHCP6_DEBUG || defined __DOXYGEN__
+#define DHCP6_DEBUG                     LWIP_DBG_OFF
+#endif
+/**
+ * @}
+ */
+
+/**
+ * LWIP_TESTMODE: Changes to make unit test possible
+ */
+#if !defined LWIP_TESTMODE
+#define LWIP_TESTMODE                   0
+#endif
+
+/**
+ * NAPT_DEBUG: Enable debugging for NAPT.
+ */
+#ifndef NAPT_DEBUG
+#define NAPT_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/*
+   --------------------------------------------------
+   ---------- Performance tracking options ----------
+   --------------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_perf Performance
+ * @ingroup lwip_opts_debug
+ * @{
+ */
+/**
+ * LWIP_PERF: Enable performance testing for lwIP
+ * (if enabled, arch/perf.h is included)
+ */
+#if !defined LWIP_PERF || defined __DOXYGEN__
+#define LWIP_PERF                       0
+#endif
+/**
+ * @}
+ */
+
+#endif /* LWIP_HDR_OPT_H */
diff --git a/arduinoIDE_esp32_boards/2.0.17 b/arduinoIDE_esp32_boards/2.0.17
new file mode 120000
index 0000000000000000000000000000000000000000..e6f474a033b7694b00c221692571bcc6d60c3baa
--- /dev/null
+++ b/arduinoIDE_esp32_boards/2.0.17
@@ -0,0 +1 @@
+2.0.15
\ No newline at end of file
diff --git a/arduinoIDE_esp32_boards/3.0.2/liblwip.a b/arduinoIDE_esp32_boards/3.0.2/liblwip.a
new file mode 100644
index 0000000000000000000000000000000000000000..a2453bd311b6a2a04c0f895415a4dacb0412ca9c
Binary files /dev/null and b/arduinoIDE_esp32_boards/3.0.2/liblwip.a differ
diff --git a/arduinoIDE_esp32_boards/3.0.2/opt.h b/arduinoIDE_esp32_boards/3.0.2/opt.h
new file mode 100644
index 0000000000000000000000000000000000000000..08169bfbf4d6f50db258f2583e451978c31889c3
--- /dev/null
+++ b/arduinoIDE_esp32_boards/3.0.2/opt.h
@@ -0,0 +1,3609 @@
+/**
+ * @file
+ *
+ * lwIP Options Configuration
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@sics.se>
+ *
+ */
+
+/*
+ * NOTE: || defined __DOXYGEN__ is a workaround for doxygen bug -
+ * without this, doxygen does not see the actual #define
+ */
+
+#if !defined LWIP_HDR_OPT_H
+#define LWIP_HDR_OPT_H
+
+/*
+ * Include user defined options first. Anything not defined in these files
+ * will be set to standard values. Override anything you don't like!
+ */
+#include "lwipopts.h"
+#include "lwip/debug.h"
+
+/**
+ * @defgroup lwip_opts Options (lwipopts.h)
+ * @ingroup lwip
+ *
+ * @defgroup lwip_opts_debug Debugging
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_infrastructure Infrastructure
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_callback Callback-style APIs
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_threadsafe_apis Thread-safe APIs
+ * @ingroup lwip_opts
+ */
+
+ /*
+   ------------------------------------
+   -------------- NO SYS --------------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_nosys NO_SYS
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * NO_SYS==1: Use lwIP without OS-awareness (no thread, semaphores, mutexes or
+ * mboxes). This means threaded APIs cannot be used (socket, netconn,
+ * i.e. everything in the 'api' folder), only the callback-style raw API is
+ * available (and you have to watch out for yourself that you don't access
+ * lwIP functions/structures from more than one context at a time!)
+ */
+#if !defined NO_SYS || defined __DOXYGEN__
+#define NO_SYS                          0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_timers Timers
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_TIMERS==0: Drop support for sys_timeout and lwip-internal cyclic timers.
+ * (the array of lwip-internal cyclic timers is still provided)
+ * (check NO_SYS_NO_TIMERS for compatibility to old versions)
+ */
+#if !defined LWIP_TIMERS || defined __DOXYGEN__
+#ifdef NO_SYS_NO_TIMERS
+#define LWIP_TIMERS                     (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS))
+#else
+#define LWIP_TIMERS                     1
+#endif
+#endif
+
+/**
+ * LWIP_TIMERS_CUSTOM==1: Provide your own timer implementation.
+ * Function prototypes in timeouts.h and the array of lwip-internal cyclic timers
+ * are still included, but the implementation is not. The following functions
+ * will be required: sys_timeouts_init(), sys_timeout(), sys_untimeout(),
+ *                   sys_timeouts_mbox_fetch()
+ */
+#if !defined LWIP_TIMERS_CUSTOM || defined __DOXYGEN__
+#define LWIP_TIMERS_CUSTOM              0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_memcpy memcpy
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * MEMCPY: override this if you have a faster implementation at hand than the
+ * one included in your C library
+ */
+#if !defined MEMCPY || defined __DOXYGEN__
+#define MEMCPY(dst,src,len)             memcpy(dst,src,len)
+#endif
+
+/**
+ * SMEMCPY: override this with care! Some compilers (e.g. gcc) can inline a
+ * call to memcpy() if the length is known at compile time and is small.
+ */
+#if !defined SMEMCPY || defined __DOXYGEN__
+#define SMEMCPY(dst,src,len)            memcpy(dst,src,len)
+#endif
+
+/**
+ * MEMMOVE: override this if you have a faster implementation at hand than the
+ * one included in your C library.  lwIP currently uses MEMMOVE only when IPv6
+ * fragmentation support is enabled.
+ */
+#if !defined MEMMOVE || defined __DOXYGEN__
+#define MEMMOVE(dst,src,len)            memmove(dst,src,len)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ----------- Core locking -----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_lock Core locking and MPU
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MPU_COMPATIBLE: enables special memory management mechanism
+ * which makes lwip able to work on MPU (Memory Protection Unit) system
+ * by not passing stack-pointers to other threads
+ * (this decreases performance as memory is allocated from pools instead
+ * of keeping it on the stack)
+ */
+#if !defined LWIP_MPU_COMPATIBLE || defined __DOXYGEN__
+#define LWIP_MPU_COMPATIBLE             0
+#endif
+
+/**
+ * LWIP_TCPIP_CORE_LOCKING
+ * Creates a global mutex that is held during TCPIP thread operations.
+ * Can be locked by client code to perform lwIP operations without changing
+ * into TCPIP thread using callbacks. See LOCK_TCPIP_CORE() and
+ * UNLOCK_TCPIP_CORE().
+ * Your system should provide mutexes supporting priority inversion to use this.
+ */
+#if !defined LWIP_TCPIP_CORE_LOCKING || defined __DOXYGEN__
+#define LWIP_TCPIP_CORE_LOCKING         1
+#endif
+
+/**
+ * LWIP_TCPIP_CORE_LOCKING_INPUT: when LWIP_TCPIP_CORE_LOCKING is enabled,
+ * this lets tcpip_input() grab the mutex for input packets as well,
+ * instead of allocating a message and passing it to tcpip_thread.
+ *
+ * ATTENTION: this does not work when tcpip_input() is called from
+ * interrupt context!
+ */
+#if !defined LWIP_TCPIP_CORE_LOCKING_INPUT || defined __DOXYGEN__
+#define LWIP_TCPIP_CORE_LOCKING_INPUT   0
+#endif
+
+/**
+ * SYS_LIGHTWEIGHT_PROT==1: enable inter-task protection (and task-vs-interrupt
+ * protection) for certain critical regions during buffer allocation, deallocation
+ * and memory allocation and deallocation.
+ * ATTENTION: This is required when using lwIP from more than one context! If
+ * you disable this, you must be sure what you are doing!
+ */
+#if !defined SYS_LIGHTWEIGHT_PROT || defined __DOXYGEN__
+#define SYS_LIGHTWEIGHT_PROT            1
+#endif
+
+/**
+ * Macro/function to check whether lwIP's threading/locking
+ * requirements are satisfied during current function call.
+ * This macro usually calls a function that is implemented in the OS-dependent
+ * sys layer and performs the following checks:
+ * - Not in ISR (this should be checked for NO_SYS==1, too!)
+ * - If @ref LWIP_TCPIP_CORE_LOCKING = 1: TCPIP core lock is held
+ * - If @ref LWIP_TCPIP_CORE_LOCKING = 0: function is called from TCPIP thread
+ * @see @ref multithreading
+ */
+#if !defined LWIP_ASSERT_CORE_LOCKED || defined __DOXYGEN__
+#define LWIP_ASSERT_CORE_LOCKED()
+#endif
+
+/**
+ * Called as first thing in the lwIP TCPIP thread. Can be used in conjunction
+ * with @ref LWIP_ASSERT_CORE_LOCKED to check core locking.
+ * @see @ref multithreading
+ */
+#if !defined LWIP_MARK_TCPIP_THREAD || defined __DOXYGEN__
+#define LWIP_MARK_TCPIP_THREAD()
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- Memory options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_mem Heap and memory pools
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by your C-library
+ * instead of the lwip internal allocator. Can save code size if you
+ * already use it.
+ */
+#if !defined MEM_LIBC_MALLOC || defined __DOXYGEN__
+#define MEM_LIBC_MALLOC                 0
+#endif
+
+/**
+ * MEMP_MEM_MALLOC==1: Use mem_malloc/mem_free instead of the lwip pool allocator.
+ * Especially useful with MEM_LIBC_MALLOC but handle with care regarding execution
+ * speed (heap alloc can be much slower than pool alloc) and usage from interrupts
+ * (especially if your netif driver allocates PBUF_POOL pbufs for received frames
+ * from interrupt)!
+ * ATTENTION: Currently, this uses the heap for ALL pools (also for private pools,
+ * not only for internal pools defined in memp_std.h)!
+ */
+#if !defined MEMP_MEM_MALLOC || defined __DOXYGEN__
+#define MEMP_MEM_MALLOC                 0
+#endif
+
+/**
+ * MEMP_MEM_INIT==1: Force use of memset to initialize pool memory.
+ * Useful if pool are moved in uninitialized section of memory. This will ensure
+ * default values in pcbs struct are well initialized in all conditions.
+ */
+#if !defined MEMP_MEM_INIT || defined __DOXYGEN__
+#define MEMP_MEM_INIT                   0
+#endif
+
+/**
+ * MEM_ALIGNMENT: should be set to the alignment of the CPU
+ *    4 byte alignment -> \#define MEM_ALIGNMENT 4
+ *    2 byte alignment -> \#define MEM_ALIGNMENT 2
+ */
+#if !defined MEM_ALIGNMENT || defined __DOXYGEN__
+#define MEM_ALIGNMENT                   1
+#endif
+
+/**
+ * MEM_SIZE: the size of the heap memory. If the application will send
+ * a lot of data that needs to be copied, this should be set high.
+ */
+#if !defined MEM_SIZE || defined __DOXYGEN__
+#define MEM_SIZE                        1600
+#endif
+
+/**
+ * MEMP_OVERFLOW_CHECK: memp overflow protection reserves a configurable
+ * amount of bytes before and after each memp element in every pool and fills
+ * it with a prominent default value.
+ *    MEMP_OVERFLOW_CHECK == 0 no checking
+ *    MEMP_OVERFLOW_CHECK == 1 checks each element when it is freed
+ *    MEMP_OVERFLOW_CHECK >= 2 checks each element in every pool every time
+ *      memp_malloc() or memp_free() is called (useful but slow!)
+ */
+#if !defined MEMP_OVERFLOW_CHECK || defined __DOXYGEN__
+#define MEMP_OVERFLOW_CHECK             0
+#endif
+
+/**
+ * MEMP_SANITY_CHECK==1: run a sanity check after each memp_free() to make
+ * sure that there are no cycles in the linked lists.
+ */
+#if !defined MEMP_SANITY_CHECK || defined __DOXYGEN__
+#define MEMP_SANITY_CHECK               0
+#endif
+
+/**
+ * MEM_OVERFLOW_CHECK: mem overflow protection reserves a configurable
+ * amount of bytes before and after each heap allocation chunk and fills
+ * it with a prominent default value.
+ *    MEM_OVERFLOW_CHECK == 0 no checking
+ *    MEM_OVERFLOW_CHECK == 1 checks each element when it is freed
+ *    MEM_OVERFLOW_CHECK >= 2 checks all heap elements every time
+ *      mem_malloc() or mem_free() is called (useful but slow!)
+ */
+#if !defined MEM_OVERFLOW_CHECK || defined __DOXYGEN__
+#define MEM_OVERFLOW_CHECK              0
+#endif
+
+/**
+ * MEM_SANITY_CHECK==1: run a sanity check after each mem_free() to make
+ * sure that the linked list of heap elements is not corrupted.
+ */
+#if !defined MEM_SANITY_CHECK || defined __DOXYGEN__
+#define MEM_SANITY_CHECK                0
+#endif
+
+/**
+ * MEM_USE_POOLS==1: Use an alternative to malloc() by allocating from a set
+ * of memory pools of various sizes. When mem_malloc is called, an element of
+ * the smallest pool that can provide the length needed is returned.
+ * To use this, MEMP_USE_CUSTOM_POOLS also has to be enabled.
+ */
+#if !defined MEM_USE_POOLS || defined __DOXYGEN__
+#define MEM_USE_POOLS                   0
+#endif
+
+/**
+ * MEM_USE_POOLS_TRY_BIGGER_POOL==1: if one malloc-pool is empty, try the next
+ * bigger pool - WARNING: THIS MIGHT WASTE MEMORY but it can make a system more
+ * reliable. */
+#if !defined MEM_USE_POOLS_TRY_BIGGER_POOL || defined __DOXYGEN__
+#define MEM_USE_POOLS_TRY_BIGGER_POOL   0
+#endif
+
+/**
+ * MEMP_USE_CUSTOM_POOLS==1: whether to include a user file lwippools.h
+ * that defines additional pools beyond the "standard" ones required
+ * by lwIP. If you set this to 1, you must have lwippools.h in your
+ * include path somewhere.
+ */
+#if !defined MEMP_USE_CUSTOM_POOLS || defined __DOXYGEN__
+#define MEMP_USE_CUSTOM_POOLS           0
+#endif
+
+/**
+ * Set this to 1 if you want to free PBUF_RAM pbufs (or call mem_free()) from
+ * interrupt context (or another context that doesn't allow waiting for a
+ * semaphore).
+ * If set to 1, mem_malloc will be protected by a semaphore and SYS_ARCH_PROTECT,
+ * while mem_free will only use SYS_ARCH_PROTECT. mem_malloc SYS_ARCH_UNPROTECTs
+ * with each loop so that mem_free can run.
+ *
+ * ATTENTION: As you can see from the above description, this leads to dis-/
+ * enabling interrupts often, which can be slow! Also, on low memory, mem_malloc
+ * can need longer.
+ *
+ * If you don't want that, at least for NO_SYS=0, you can still use the following
+ * functions to enqueue a deallocation call which then runs in the tcpip_thread
+ * context:
+ * - pbuf_free_callback(p);
+ * - mem_free_callback(m);
+ */
+#if !defined LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT || defined __DOXYGEN__
+#define LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------------------
+   ---------- Internal Memory Pool Sizes ----------
+   ------------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_memp Internal memory pools
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * MEMP_NUM_PBUF: the number of memp struct pbufs (used for PBUF_ROM and PBUF_REF).
+ * If the application sends a lot of data out of ROM (or other static memory),
+ * this should be set high.
+ */
+#if !defined MEMP_NUM_PBUF || defined __DOXYGEN__
+#define MEMP_NUM_PBUF                   16
+#endif
+
+/**
+ * MEMP_NUM_RAW_PCB: Number of raw connection PCBs
+ * (requires the LWIP_RAW option)
+ */
+#if !defined MEMP_NUM_RAW_PCB || defined __DOXYGEN__
+#define MEMP_NUM_RAW_PCB                4
+#endif
+
+/**
+ * MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
+ * per active UDP "connection".
+ * (requires the LWIP_UDP option)
+ */
+#if !defined MEMP_NUM_UDP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_UDP_PCB                4
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB: the number of simultaneously active TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_TCP_PCB                5
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_PCB_LISTEN || defined __DOXYGEN__
+#define MEMP_NUM_TCP_PCB_LISTEN         8
+#endif
+
+/**
+ * MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP segments.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_SEG || defined __DOXYGEN__
+#define MEMP_NUM_TCP_SEG                16
+#endif
+
+/**
+ * MEMP_NUM_ALTCP_PCB: the number of simultaneously active altcp layer pcbs.
+ * (requires the LWIP_ALTCP option)
+ * Connections with multiple layers require more than one altcp_pcb (e.g. TLS
+ * over TCP requires 2 altcp_pcbs, one for TLS and one for TCP).
+ */
+#if !defined MEMP_NUM_ALTCP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_ALTCP_PCB              MEMP_NUM_TCP_PCB
+#endif
+
+/**
+ * MEMP_NUM_REASSDATA: the number of IP packets simultaneously queued for
+ * reassembly (whole packets, not fragments!)
+ */
+#if !defined MEMP_NUM_REASSDATA || defined __DOXYGEN__
+#define MEMP_NUM_REASSDATA              5
+#endif
+
+/**
+ * MEMP_NUM_FRAG_PBUF: the number of IP fragments simultaneously sent
+ * (fragments, not whole packets!).
+ * This is only used with LWIP_NETIF_TX_SINGLE_PBUF==0 and only has to be > 1
+ * with DMA-enabled MACs where the packet is not yet sent when netif->output
+ * returns.
+ */
+#if !defined MEMP_NUM_FRAG_PBUF || defined __DOXYGEN__
+#define MEMP_NUM_FRAG_PBUF              15
+#endif
+
+/**
+ * MEMP_NUM_ARP_QUEUE: the number of simultaneously queued outgoing
+ * packets (pbufs) that are waiting for an ARP request (to resolve
+ * their destination address) to finish.
+ * (requires the ARP_QUEUEING option)
+ */
+#if !defined MEMP_NUM_ARP_QUEUE || defined __DOXYGEN__
+#define MEMP_NUM_ARP_QUEUE              30
+#endif
+
+/**
+ * MEMP_NUM_IGMP_GROUP: The number of multicast groups whose network interfaces
+ * can be members at the same time (one per netif - allsystems group -, plus one
+ * per netif membership).
+ * (requires the LWIP_IGMP option)
+ */
+#if !defined MEMP_NUM_IGMP_GROUP || defined __DOXYGEN__
+#define MEMP_NUM_IGMP_GROUP             8
+#endif
+
+/**
+ * The number of sys timeouts used by the core stack (not apps)
+ * The default number of timeouts is calculated here for all enabled modules.
+ */
+#define LWIP_NUM_SYS_TIMEOUT_INTERNAL   (LWIP_TCP + IP_REASSEMBLY + LWIP_ARP + (ESP_LWIP_DHCP_FINE_TIMERS_ONDEMAND ? LWIP_DHCP : 2*LWIP_DHCP) + LWIP_AUTOIP + (ESP_LWIP_IGMP_TIMERS_ONDEMAND ? 0 : LWIP_IGMP) + (ESP_LWIP_DNS_TIMERS_ONDEMAND ? 0 : LWIP_DNS) + PPP_NUM_TIMEOUTS + (LWIP_IPV6 * (1 + LWIP_IPV6_REASS + (ESP_LWIP_MLD6_TIMERS_ONDEMAND ? 0 : LWIP_IPV6_MLD))))
+
+/**
+ * MEMP_NUM_SYS_TIMEOUT: the number of simultaneously active timeouts.
+ * The default number of timeouts is calculated here for all enabled modules.
+ * The formula expects settings to be either '0' or '1'.
+ */
+#if !defined MEMP_NUM_SYS_TIMEOUT || defined __DOXYGEN__
+#define MEMP_NUM_SYS_TIMEOUT            LWIP_NUM_SYS_TIMEOUT_INTERNAL
+#endif
+
+/**
+ * MEMP_NUM_NETBUF: the number of struct netbufs.
+ * (only needed if you use the sequential API, like api_lib.c)
+ */
+#if !defined MEMP_NUM_NETBUF || defined __DOXYGEN__
+#define MEMP_NUM_NETBUF                 2
+#endif
+
+/**
+ * MEMP_NUM_NETCONN: the number of struct netconns.
+ * (only needed if you use the sequential API, like api_lib.c)
+ */
+#if !defined MEMP_NUM_NETCONN || defined __DOXYGEN__
+#define MEMP_NUM_NETCONN                4
+#endif
+
+/**
+ * MEMP_NUM_SELECT_CB: the number of struct lwip_select_cb.
+ * (Only needed if you have LWIP_MPU_COMPATIBLE==1 and use the socket API.
+ * In that case, you need one per thread calling lwip_select.)
+ */
+#if !defined MEMP_NUM_SELECT_CB || defined __DOXYGEN__
+#define MEMP_NUM_SELECT_CB              4
+#endif
+
+/**
+ * MEMP_NUM_TCPIP_MSG_API: the number of struct tcpip_msg, which are used
+ * for callback/timeout API communication.
+ * (only needed if you use tcpip.c)
+ */
+#if !defined MEMP_NUM_TCPIP_MSG_API || defined __DOXYGEN__
+#define MEMP_NUM_TCPIP_MSG_API          8
+#endif
+
+/**
+ * MEMP_NUM_TCPIP_MSG_INPKT: the number of struct tcpip_msg, which are used
+ * for incoming packets.
+ * (only needed if you use tcpip.c)
+ */
+#if !defined MEMP_NUM_TCPIP_MSG_INPKT || defined __DOXYGEN__
+#define MEMP_NUM_TCPIP_MSG_INPKT        8
+#endif
+
+/**
+ * MEMP_NUM_NETDB: the number of concurrently running lwip_addrinfo() calls
+ * (before freeing the corresponding memory using lwip_freeaddrinfo()).
+ */
+#if !defined MEMP_NUM_NETDB || defined __DOXYGEN__
+#define MEMP_NUM_NETDB                  1
+#endif
+
+/**
+ * MEMP_NUM_LOCALHOSTLIST: the number of host entries in the local host list
+ * if DNS_LOCAL_HOSTLIST_IS_DYNAMIC==1.
+ */
+#if !defined MEMP_NUM_LOCALHOSTLIST || defined __DOXYGEN__
+#define MEMP_NUM_LOCALHOSTLIST          1
+#endif
+
+/**
+ * PBUF_POOL_SIZE: the number of buffers in the pbuf pool.
+ */
+#if !defined PBUF_POOL_SIZE || defined __DOXYGEN__
+#define PBUF_POOL_SIZE                  16
+#endif
+
+/** MEMP_NUM_API_MSG: the number of concurrently active calls to various
+ * socket, netconn, and tcpip functions
+ */
+#if !defined MEMP_NUM_API_MSG || defined __DOXYGEN__
+#define MEMP_NUM_API_MSG                MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_DNS_API_MSG: the number of concurrently active calls to netconn_gethostbyname
+ */
+#if !defined MEMP_NUM_DNS_API_MSG || defined __DOXYGEN__
+#define MEMP_NUM_DNS_API_MSG            MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA: the number of concurrently active calls
+ * to getsockopt/setsockopt
+ */
+#if !defined MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA || defined __DOXYGEN__
+#define MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_NETIFAPI_MSG: the number of concurrently active calls to the
+ * netifapi functions
+ */
+#if !defined MEMP_NUM_NETIFAPI_MSG || defined __DOXYGEN__
+#define MEMP_NUM_NETIFAPI_MSG           MEMP_NUM_TCPIP_MSG_API
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- ARP options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_arp ARP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_ARP==1: Enable ARP functionality.
+ */
+#if !defined LWIP_ARP || defined __DOXYGEN__
+#define LWIP_ARP                        1
+#endif
+
+/**
+ * ARP_TABLE_SIZE: Number of active MAC-IP address pairs cached.
+ */
+#if !defined ARP_TABLE_SIZE || defined __DOXYGEN__
+#define ARP_TABLE_SIZE                  10
+#endif
+
+/** the time an ARP entry stays valid after its last update,
+ *  for ARP_TMR_INTERVAL = 1000, this is
+ *  (60 * 5) seconds = 5 minutes.
+ */
+#if !defined ARP_MAXAGE || defined __DOXYGEN__
+#define ARP_MAXAGE                      300
+#endif
+
+/**
+ * ARP_QUEUEING==1: Multiple outgoing packets are queued during hardware address
+ * resolution. By default, only the most recent packet is queued per IP address.
+ * This is sufficient for most protocols and mainly reduces TCP connection
+ * startup time. Set this to 1 if you know your application sends more than one
+ * packet in a row to an IP address that is not in the ARP cache.
+ */
+#if !defined ARP_QUEUEING || defined __DOXYGEN__
+#define ARP_QUEUEING                    0
+#endif
+
+/** The maximum number of packets which may be queued for each
+ *  unresolved address by other network layers. Defaults to 3, 0 means disabled.
+ *  Old packets are dropped, new packets are queued.
+ */
+#if !defined ARP_QUEUE_LEN || defined __DOXYGEN__
+#define ARP_QUEUE_LEN                   3
+#endif
+
+/**
+ * ETHARP_SUPPORT_VLAN==1: support receiving and sending ethernet packets with
+ * VLAN header. See the description of LWIP_HOOK_VLAN_CHECK and
+ * LWIP_HOOK_VLAN_SET hooks to check/set VLAN headers.
+ * Additionally, you can define ETHARP_VLAN_CHECK to an u16_t VLAN ID to check.
+ * If ETHARP_VLAN_CHECK is defined, only VLAN-traffic for this VLAN is accepted.
+ * If ETHARP_VLAN_CHECK is not defined, all traffic is accepted.
+ * Alternatively, define a function/define ETHARP_VLAN_CHECK_FN(eth_hdr, vlan)
+ * that returns 1 to accept a packet or 0 to drop a packet.
+ */
+#if !defined ETHARP_SUPPORT_VLAN || defined __DOXYGEN__
+#define ETHARP_SUPPORT_VLAN             0
+#endif
+
+/** LWIP_ETHERNET==1: enable ethernet support even though ARP might be disabled
+ */
+#if !defined LWIP_ETHERNET || defined __DOXYGEN__
+#define LWIP_ETHERNET                   LWIP_ARP
+#endif
+
+/** ETH_PAD_SIZE: number of bytes added before the ethernet header to ensure
+ * alignment of payload after that header. Since the header is 14 bytes long,
+ * without this padding e.g. addresses in the IP header will not be aligned
+ * on a 32-bit boundary, so setting this to 2 can speed up 32-bit-platforms.
+ */
+#if !defined ETH_PAD_SIZE || defined __DOXYGEN__
+#define ETH_PAD_SIZE                    0
+#endif
+
+/** ETHARP_SUPPORT_STATIC_ENTRIES==1: enable code to support static ARP table
+ * entries (using etharp_add_static_entry/etharp_remove_static_entry).
+ */
+#if !defined ETHARP_SUPPORT_STATIC_ENTRIES || defined __DOXYGEN__
+#define ETHARP_SUPPORT_STATIC_ENTRIES   0
+#endif
+
+/** ETHARP_TABLE_MATCH_NETIF==1: Match netif for ARP table entries.
+ * If disabled, duplicate IP address on multiple netifs are not supported
+ * (but this should only occur for AutoIP).
+ */
+#if !defined ETHARP_TABLE_MATCH_NETIF || defined __DOXYGEN__
+#define ETHARP_TABLE_MATCH_NETIF        !LWIP_SINGLE_NETIF
+#endif
+/**
+ * @}
+ */
+
+/*
+   --------------------------------
+   ---------- IP options ----------
+   --------------------------------
+*/
+/**
+ * @defgroup lwip_opts_ipv4 IPv4
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * LWIP_IPV4==1: Enable IPv4
+ */
+#if !defined LWIP_IPV4 || defined __DOXYGEN__
+#define LWIP_IPV4                       1
+#endif
+
+/**
+ * IP_FORWARD==1: Enables the ability to forward IP packets across network
+ * interfaces. If you are going to run lwIP on a device with only one network
+ * interface, define this to 0.
+ */
+#if !defined IP_FORWARD || defined __DOXYGEN__
+#define IP_FORWARD                      0
+#endif
+
+/**
+ * IP_REASSEMBLY==1: Reassemble incoming fragmented IP packets. Note that
+ * this option does not affect outgoing packet sizes, which can be controlled
+ * via IP_FRAG.
+ */
+#if !defined IP_REASSEMBLY || defined __DOXYGEN__
+#define IP_REASSEMBLY                   1
+#endif
+
+/**
+ * IP_FRAG==1: Fragment outgoing IP packets if their size exceeds MTU. Note
+ * that this option does not affect incoming packet sizes, which can be
+ * controlled via IP_REASSEMBLY.
+ */
+#if !defined IP_FRAG || defined __DOXYGEN__
+#define IP_FRAG                         1
+#endif
+
+#if !LWIP_IPV4
+/* disable IPv4 extensions when IPv4 is disabled */
+#undef IP_FORWARD
+#define IP_FORWARD                      0
+#undef IP_REASSEMBLY
+#define IP_REASSEMBLY                   0
+#undef IP_FRAG
+#define IP_FRAG                         0
+#endif /* !LWIP_IPV4 */
+
+/**
+ * IP_NAPT==1: Enables IPv4 Network Address and Port Translation
+ * Note that IP_FORWARD needs to be enabled for NAPT to work
+ */
+#if !defined IP_NAPT || defined __DOXYGEN__
+#define IP_NAPT                      0
+#endif
+
+/**
+ * IP_OPTIONS_ALLOWED: Defines the behavior for IP options.
+ *      IP_OPTIONS_ALLOWED==0: All packets with IP options are dropped.
+ *      IP_OPTIONS_ALLOWED==1: IP options are allowed (but not parsed).
+ */
+#if !defined IP_OPTIONS_ALLOWED || defined __DOXYGEN__
+#define IP_OPTIONS_ALLOWED              1
+#endif
+
+/**
+ * IP_REASS_MAXAGE: Maximum time (in multiples of IP_TMR_INTERVAL - so seconds, normally)
+ * a fragmented IP packet waits for all fragments to arrive. If not all fragments arrived
+ * in this time, the whole packet is discarded.
+ */
+#if !defined IP_REASS_MAXAGE || defined __DOXYGEN__
+#define IP_REASS_MAXAGE                 15
+#endif
+
+/**
+ * IP_REASS_MAX_PBUFS: Total maximum amount of pbufs waiting to be reassembled.
+ * Since the received pbufs are enqueued, be sure to configure
+ * PBUF_POOL_SIZE > IP_REASS_MAX_PBUFS so that the stack is still able to receive
+ * packets even if the maximum amount of fragments is enqueued for reassembly!
+ * When IPv4 *and* IPv6 are enabled, this even changes to
+ * (PBUF_POOL_SIZE > 2 * IP_REASS_MAX_PBUFS)!
+ */
+#if !defined IP_REASS_MAX_PBUFS || defined __DOXYGEN__
+#define IP_REASS_MAX_PBUFS              10
+#endif
+
+/**
+ * IP_DEFAULT_TTL: Default value for Time-To-Live used by transport layers.
+ */
+#if !defined IP_DEFAULT_TTL || defined __DOXYGEN__
+#define IP_DEFAULT_TTL                  255
+#endif
+
+/**
+ * IP_SOF_BROADCAST=1: Use the SOF_BROADCAST field to enable broadcast
+ * filter per pcb on udp and raw send operations. To enable broadcast filter
+ * on recv operations, you also have to set IP_SOF_BROADCAST_RECV=1.
+ */
+#if !defined IP_SOF_BROADCAST || defined __DOXYGEN__
+#define IP_SOF_BROADCAST                0
+#endif
+
+/**
+ * IP_SOF_BROADCAST_RECV (requires IP_SOF_BROADCAST=1) enable the broadcast
+ * filter on recv operations.
+ */
+#if !defined IP_SOF_BROADCAST_RECV || defined __DOXYGEN__
+#define IP_SOF_BROADCAST_RECV           0
+#endif
+
+/**
+ * IP_FORWARD_ALLOW_TX_ON_RX_NETIF==1: allow ip_forward() to send packets back
+ * out on the netif where it was received. This should only be used for
+ * wireless networks.
+ * ATTENTION: When this is 1, make sure your netif driver correctly marks incoming
+ * link-layer-broadcast/multicast packets as such using the corresponding pbuf flags!
+ */
+#if !defined IP_FORWARD_ALLOW_TX_ON_RX_NETIF || defined __DOXYGEN__
+#define IP_FORWARD_ALLOW_TX_ON_RX_NETIF 0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- ICMP options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_icmp ICMP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_ICMP==1: Enable ICMP module inside the IP stack.
+ * Be careful, disable that make your product non-compliant to RFC1122
+ */
+#if !defined LWIP_ICMP || defined __DOXYGEN__
+#define LWIP_ICMP                       1
+#endif
+
+/**
+ * ICMP_TTL: Default value for Time-To-Live used by ICMP packets.
+ */
+#if !defined ICMP_TTL || defined __DOXYGEN__
+#define ICMP_TTL                        IP_DEFAULT_TTL
+#endif
+
+/**
+ * LWIP_BROADCAST_PING==1: respond to broadcast pings (default is unicast only)
+ */
+#if !defined LWIP_BROADCAST_PING || defined __DOXYGEN__
+#define LWIP_BROADCAST_PING             0
+#endif
+
+/**
+ * LWIP_MULTICAST_PING==1: respond to multicast pings (default is unicast only)
+ */
+#if !defined LWIP_MULTICAST_PING || defined __DOXYGEN__
+#define LWIP_MULTICAST_PING             0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- RAW options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_raw RAW
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
+ */
+#if !defined LWIP_RAW || defined __DOXYGEN__
+#define LWIP_RAW                        0
+#endif
+
+/**
+ * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
+ */
+#if !defined RAW_TTL || defined __DOXYGEN__
+#define RAW_TTL                         IP_DEFAULT_TTL
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- DHCP options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_dhcp DHCP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_DHCP==1: Enable DHCP module.
+ */
+#if !defined LWIP_DHCP || defined __DOXYGEN__
+#define LWIP_DHCP                       0
+#endif
+#if !LWIP_IPV4
+/* disable DHCP when IPv4 is disabled */
+#undef LWIP_DHCP
+#define LWIP_DHCP                       0
+#endif /* !LWIP_IPV4 */
+
+/**
+ * DHCP_DOES_ARP_CHECK==1: Do an ARP check on the offered address.
+ */
+#if !defined DHCP_DOES_ARP_CHECK || defined __DOXYGEN__
+#define DHCP_DOES_ARP_CHECK             (LWIP_DHCP && LWIP_ARP)
+#endif
+
+/**
+ * LWIP_DHCP_BOOTP_FILE==1: Store offered_si_addr and boot_file_name.
+ */
+#if !defined LWIP_DHCP_BOOTP_FILE || defined __DOXYGEN__
+#define LWIP_DHCP_BOOTP_FILE            0
+#endif
+
+/**
+ * LWIP_DHCP_GETS_NTP==1: Request NTP servers with discover/select. For each
+ * response packet, an callback is called, which has to be provided by the port:
+ * void dhcp_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs);
+*/
+#if !defined LWIP_DHCP_GET_NTP_SRV || defined __DOXYGEN__
+#define LWIP_DHCP_GET_NTP_SRV           1
+#endif
+
+/**
+ * The maximum of NTP servers requested
+ */
+#if !defined LWIP_DHCP_MAX_NTP_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP_MAX_NTP_SERVERS       3
+#endif
+
+/**
+ * LWIP_DHCP_MAX_DNS_SERVERS > 0: Request DNS servers with discover/select.
+ * DNS servers received in the response are passed to DNS via @ref dns_setserver()
+ * (up to the maximum limit defined here).
+ */
+#if !defined LWIP_DHCP_MAX_DNS_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP_MAX_DNS_SERVERS       DNS_MAX_SERVERS
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- AUTOIP options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_autoip AUTOIP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_AUTOIP==1: Enable AUTOIP module.
+ */
+#if !defined LWIP_AUTOIP || defined __DOXYGEN__
+#define LWIP_AUTOIP                     0
+#endif
+#if !LWIP_IPV4
+/* disable AUTOIP when IPv4 is disabled */
+#undef LWIP_AUTOIP
+#define LWIP_AUTOIP                     0
+#endif /* !LWIP_IPV4 */
+
+/**
+ * LWIP_DHCP_AUTOIP_COOP==1: Allow DHCP and AUTOIP to be both enabled on
+ * the same interface at the same time.
+ */
+#if !defined LWIP_DHCP_AUTOIP_COOP || defined __DOXYGEN__
+#define LWIP_DHCP_AUTOIP_COOP           0
+#endif
+
+/**
+ * LWIP_DHCP_AUTOIP_COOP_TRIES: Set to the number of DHCP DISCOVER probes
+ * that should be sent before falling back on AUTOIP (the DHCP client keeps
+ * running in this case). This can be set as low as 1 to get an AutoIP address
+ * very  quickly, but you should be prepared to handle a changing IP address
+ * when DHCP overrides AutoIP.
+ */
+#if !defined LWIP_DHCP_AUTOIP_COOP_TRIES || defined __DOXYGEN__
+#define LWIP_DHCP_AUTOIP_COOP_TRIES     9
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ----- SNMP MIB2 support      -----
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_mib2 SNMP MIB2 callbacks
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MIB2_CALLBACKS==1: Turn on SNMP MIB2 callbacks.
+ * Turn this on to get callbacks needed to implement MIB2.
+ * Usually MIB2_STATS should be enabled, too.
+ */
+#if !defined LWIP_MIB2_CALLBACKS || defined __DOXYGEN__
+#define LWIP_MIB2_CALLBACKS             0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   -------- Multicast options -------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_multicast Multicast
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MULTICAST_TX_OPTIONS==1: Enable multicast TX support like the socket options
+ * IP_MULTICAST_TTL/IP_MULTICAST_IF/IP_MULTICAST_LOOP, as well as (currently only)
+ * core support for the corresponding IPv6 options.
+ */
+#if !defined LWIP_MULTICAST_TX_OPTIONS || defined __DOXYGEN__
+#define LWIP_MULTICAST_TX_OPTIONS       ((LWIP_IGMP || LWIP_IPV6_MLD) && (LWIP_UDP || LWIP_RAW))
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- IGMP options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_igmp IGMP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_IGMP==1: Turn on IGMP module.
+ */
+#if !defined LWIP_IGMP || defined __DOXYGEN__
+#define LWIP_IGMP                       0
+#endif
+#if !LWIP_IPV4
+#undef LWIP_IGMP
+#define LWIP_IGMP                       0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- DNS options -----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_dns DNS
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_DNS==1: Turn on DNS module. UDP must be available for DNS
+ * transport.
+ */
+#if !defined LWIP_DNS || defined __DOXYGEN__
+#define LWIP_DNS                        0
+#endif
+
+/** DNS maximum number of entries to maintain locally. */
+#if !defined DNS_TABLE_SIZE || defined __DOXYGEN__
+#define DNS_TABLE_SIZE                  4
+#endif
+
+/** DNS maximum host name length supported in the name table. */
+#if !defined DNS_MAX_NAME_LENGTH || defined __DOXYGEN__
+#define DNS_MAX_NAME_LENGTH             256
+#endif
+
+/** The maximum of DNS servers
+ * The first server can be initialized automatically by defining
+ * DNS_SERVER_ADDRESS(ipaddr), where 'ipaddr' is an 'ip_addr_t*'
+ */
+#if !defined DNS_MAX_SERVERS || defined __DOXYGEN__
+#define DNS_MAX_SERVERS                 2
+#endif
+
+/** DNS maximum number of retries when asking for a name, before "timeout". */
+#if !defined DNS_MAX_RETRIES || defined __DOXYGEN__
+#define DNS_MAX_RETRIES                 4
+#endif
+
+/** DNS do a name checking between the query and the response. */
+#if !defined DNS_DOES_NAME_CHECK || defined __DOXYGEN__
+#define DNS_DOES_NAME_CHECK             1
+#endif
+
+/** LWIP_DNS_SECURE: controls the security level of the DNS implementation
+ * Use all DNS security features by default.
+ * This is overridable but should only be needed by very small targets
+ * or when using against non standard DNS servers. */
+#if !defined LWIP_DNS_SECURE || defined __DOXYGEN__
+#define LWIP_DNS_SECURE (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT)
+#endif
+
+/* A list of DNS security features follows */
+#define LWIP_DNS_SECURE_RAND_XID                1
+#define LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING 2
+#define LWIP_DNS_SECURE_RAND_SRC_PORT           4
+
+/** DNS_LOCAL_HOSTLIST: Implements a local host-to-address list. If enabled, you have to define an initializer:
+ *  \#define DNS_LOCAL_HOSTLIST_INIT {DNS_LOCAL_HOSTLIST_ELEM("host_ip4", IPADDR4_INIT_BYTES(1,2,3,4)), \
+ *                                    DNS_LOCAL_HOSTLIST_ELEM("host_ip6", IPADDR6_INIT_HOST(123, 234, 345, 456)}
+ *
+ *  Instead, you can also use an external function:
+ *  \#define DNS_LOOKUP_LOCAL_EXTERN(x) extern err_t my_lookup_function(const char *name, ip_addr_t *addr, u8_t dns_addrtype)
+ *  that looks up the IP address and returns ERR_OK if found (LWIP_DNS_ADDRTYPE_xxx is passed in dns_addrtype).
+ */
+#if !defined DNS_LOCAL_HOSTLIST || defined __DOXYGEN__
+#define DNS_LOCAL_HOSTLIST              0
+#endif /* DNS_LOCAL_HOSTLIST */
+
+/** If this is turned on, the local host-list can be dynamically changed
+ *  at runtime. */
+#if !defined DNS_LOCAL_HOSTLIST_IS_DYNAMIC || defined __DOXYGEN__
+#define DNS_LOCAL_HOSTLIST_IS_DYNAMIC   0
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+
+/** Set this to 1 to enable querying ".local" names via mDNS
+ *  using a One-Shot Multicast DNS Query */
+#if !defined LWIP_DNS_SUPPORT_MDNS_QUERIES || defined __DOXYGEN__
+#define LWIP_DNS_SUPPORT_MDNS_QUERIES   0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- UDP options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_udp UDP
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_UDP==1: Turn on UDP.
+ */
+#if !defined LWIP_UDP || defined __DOXYGEN__
+#define LWIP_UDP                        1
+#endif
+
+/**
+ * LWIP_UDPLITE==1: Turn on UDP-Lite. (Requires LWIP_UDP)
+ */
+#if !defined LWIP_UDPLITE || defined __DOXYGEN__
+#define LWIP_UDPLITE                    0
+#endif
+
+/**
+ * UDP_TTL: Default Time-To-Live value.
+ */
+#if !defined UDP_TTL || defined __DOXYGEN__
+#define UDP_TTL                         IP_DEFAULT_TTL
+#endif
+
+/**
+ * LWIP_NETBUF_RECVINFO==1: append destination addr and port to every netbuf.
+ */
+#if !defined LWIP_NETBUF_RECVINFO || defined __DOXYGEN__
+#define LWIP_NETBUF_RECVINFO            0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- TCP options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_tcp TCP
+ * @ingroup lwip_opts_callback
+ * @{
+ */
+/**
+ * LWIP_TCP==1: Turn on TCP.
+ */
+#if !defined LWIP_TCP || defined __DOXYGEN__
+#define LWIP_TCP                        1
+#endif
+
+/**
+ * TCP_TTL: Default Time-To-Live value.
+ */
+#if !defined TCP_TTL || defined __DOXYGEN__
+#define TCP_TTL                         IP_DEFAULT_TTL
+#endif
+
+/**
+ * TCP_WND: The size of a TCP window.  This must be at least
+ * (2 * TCP_MSS) for things to work well.
+ * ATTENTION: when using TCP_RCV_SCALE, TCP_WND is the total size
+ * with scaling applied. Maximum window value in the TCP header
+ * will be TCP_WND >> TCP_RCV_SCALE
+ */
+#if !defined TCP_WND || defined __DOXYGEN__
+#define TCP_WND                         (4 * TCP_MSS)
+#endif
+
+/**
+ * TCP_MAXRTX: Maximum number of retransmissions of data segments.
+ */
+#if !defined TCP_MAXRTX || defined __DOXYGEN__
+#define TCP_MAXRTX                      12
+#endif
+
+/**
+ * TCP_SYNMAXRTX: Maximum number of retransmissions of SYN segments.
+ */
+#if !defined TCP_SYNMAXRTX || defined __DOXYGEN__
+#define TCP_SYNMAXRTX                   6
+#endif
+
+/**
+ * TCP_QUEUE_OOSEQ==1: TCP will queue segments that arrive out of order.
+ * Define to 0 if your device is low on memory.
+ */
+#if !defined TCP_QUEUE_OOSEQ || defined __DOXYGEN__
+#define TCP_QUEUE_OOSEQ                 LWIP_TCP
+#endif
+
+/**
+ * LWIP_TCP_SACK_OUT==1: TCP will support sending selective acknowledgements (SACKs).
+ */
+#if !defined LWIP_TCP_SACK_OUT || defined __DOXYGEN__
+#define LWIP_TCP_SACK_OUT               0
+#endif
+
+/**
+ * LWIP_TCP_MAX_SACK_NUM: The maximum number of SACK values to include in TCP segments.
+ * Must be at least 1, but is only used if LWIP_TCP_SACK_OUT is enabled.
+ * NOTE: Even though we never send more than 3 or 4 SACK ranges in a single segment
+ * (depending on other options), setting this option to values greater than 4 is not pointless.
+ * This is basically the max number of SACK ranges we want to keep track of.
+ * As new data is delivered, some of the SACK ranges may be removed or merged.
+ * In that case some of those older SACK ranges may be used again.
+ * The amount of memory used to store SACK ranges is LWIP_TCP_MAX_SACK_NUM * 8 bytes for each TCP PCB.
+ */
+#if !defined LWIP_TCP_MAX_SACK_NUM || defined __DOXYGEN__
+#define LWIP_TCP_MAX_SACK_NUM           4
+#endif
+
+/**
+ * TCP_MSS: TCP Maximum segment size. (default is 536, a conservative default,
+ * you might want to increase this.)
+ * For the receive side, this MSS is advertised to the remote side
+ * when opening a connection. For the transmit size, this MSS sets
+ * an upper limit on the MSS advertised by the remote host.
+ */
+#if !defined TCP_MSS || defined __DOXYGEN__
+#define TCP_MSS                         536
+#endif
+
+/**
+ * TCP_CALCULATE_EFF_SEND_MSS: "The maximum size of a segment that TCP really
+ * sends, the 'effective send MSS,' MUST be the smaller of the send MSS (which
+ * reflects the available reassembly buffer size at the remote host) and the
+ * largest size permitted by the IP layer" (RFC 1122)
+ * Setting this to 1 enables code that checks TCP_MSS against the MTU of the
+ * netif used for a connection and limits the MSS if it would be too big otherwise.
+ */
+#if !defined TCP_CALCULATE_EFF_SEND_MSS || defined __DOXYGEN__
+#define TCP_CALCULATE_EFF_SEND_MSS      1
+#endif
+
+/**
+ * LWIP_TCP_RTO_TIME: Initial TCP retransmission timeout (ms).
+ * This defaults to 3 seconds as traditionally defined in the TCP protocol.
+ * For improving timely recovery on faster networks, this value could
+ * be lowered down to 1 second (RFC 6298) 
+ */
+#if !defined LWIP_TCP_RTO_TIME || defined __DOXYGEN__
+#define LWIP_TCP_RTO_TIME               3000
+#endif
+
+/**
+ * TCP_SND_BUF: TCP sender buffer space (bytes).
+ * To achieve good performance, this should be at least 2 * TCP_MSS.
+ */
+#if !defined TCP_SND_BUF || defined __DOXYGEN__
+#define TCP_SND_BUF                     (2 * TCP_MSS)
+#endif
+
+/**
+ * TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
+ * as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work.
+ */
+#if !defined TCP_SND_QUEUELEN || defined __DOXYGEN__
+#define TCP_SND_QUEUELEN                ((4 * (TCP_SND_BUF) + (TCP_MSS - 1))/(TCP_MSS))
+#endif
+
+/**
+ * TCP_SNDLOWAT: TCP writable space (bytes). This must be less than
+ * TCP_SND_BUF. It is the amount of space which must be available in the
+ * TCP snd_buf for select to return writable (combined with TCP_SNDQUEUELOWAT).
+ */
+#if !defined TCP_SNDLOWAT || defined __DOXYGEN__
+#define TCP_SNDLOWAT                    LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1)
+#endif
+
+/**
+ * TCP_SNDQUEUELOWAT: TCP writable bufs (pbuf count). This must be less
+ * than TCP_SND_QUEUELEN. If the number of pbufs queued on a pcb drops below
+ * this number, select returns writable (combined with TCP_SNDLOWAT).
+ */
+#if !defined TCP_SNDQUEUELOWAT || defined __DOXYGEN__
+#define TCP_SNDQUEUELOWAT               LWIP_MAX(((TCP_SND_QUEUELEN)/2), 5)
+#endif
+
+/**
+ * TCP_OOSEQ_MAX_BYTES: The default maximum number of bytes queued on ooseq per
+ * pcb if TCP_OOSEQ_BYTES_LIMIT is not defined. Default is 0 (no limit).
+ * Only valid for TCP_QUEUE_OOSEQ==1.
+ */
+#if !defined TCP_OOSEQ_MAX_BYTES || defined __DOXYGEN__
+#define TCP_OOSEQ_MAX_BYTES             0
+#endif
+
+/**
+ * TCP_OOSEQ_BYTES_LIMIT(pcb): Return the maximum number of bytes to be queued
+ * on ooseq per pcb, given the pcb. Only valid for TCP_QUEUE_OOSEQ==1 &&
+ * TCP_OOSEQ_MAX_BYTES==1.
+ * Use this to override TCP_OOSEQ_MAX_BYTES to a dynamic value per pcb.
+ */
+#if !defined TCP_OOSEQ_BYTES_LIMIT
+#if TCP_OOSEQ_MAX_BYTES
+#define TCP_OOSEQ_BYTES_LIMIT(pcb)      TCP_OOSEQ_MAX_BYTES
+#elif defined __DOXYGEN__
+#define TCP_OOSEQ_BYTES_LIMIT(pcb)
+#endif
+#endif
+
+/**
+ * TCP_OOSEQ_MAX_PBUFS: The default maximum number of pbufs queued on ooseq per
+ * pcb if TCP_OOSEQ_BYTES_LIMIT is not defined. Default is 0 (no limit).
+ * Only valid for TCP_QUEUE_OOSEQ==1.
+ */
+#if !defined TCP_OOSEQ_MAX_PBUFS || defined __DOXYGEN__
+#define TCP_OOSEQ_MAX_PBUFS             0
+#endif
+
+/**
+ * TCP_OOSEQ_PBUFS_LIMIT(pcb): Return the maximum number of pbufs to be queued
+ * on ooseq per pcb, given the pcb.  Only valid for TCP_QUEUE_OOSEQ==1 &&
+ * TCP_OOSEQ_MAX_PBUFS==1.
+ * Use this to override TCP_OOSEQ_MAX_PBUFS to a dynamic value per pcb.
+ */
+#if !defined TCP_OOSEQ_PBUFS_LIMIT
+#if TCP_OOSEQ_MAX_PBUFS
+#define TCP_OOSEQ_PBUFS_LIMIT(pcb)      TCP_OOSEQ_MAX_PBUFS
+#elif defined __DOXYGEN__
+#define TCP_OOSEQ_PBUFS_LIMIT(pcb)
+#endif
+#endif
+
+/**
+ * TCP_LISTEN_BACKLOG: Enable the backlog option for tcp listen pcb.
+ */
+#if !defined TCP_LISTEN_BACKLOG || defined __DOXYGEN__
+#define TCP_LISTEN_BACKLOG              0
+#endif
+
+/**
+ * The maximum allowed backlog for TCP listen netconns.
+ * This backlog is used unless another is explicitly specified.
+ * 0xff is the maximum (u8_t).
+ */
+#if !defined TCP_DEFAULT_LISTEN_BACKLOG || defined __DOXYGEN__
+#define TCP_DEFAULT_LISTEN_BACKLOG      0xff
+#endif
+
+/**
+ * TCP_OVERSIZE: The maximum number of bytes that tcp_write may
+ * allocate ahead of time in an attempt to create shorter pbuf chains
+ * for transmission. The meaningful range is 0 to TCP_MSS. Some
+ * suggested values are:
+ *
+ * 0:         Disable oversized allocation. Each tcp_write() allocates a new
+              pbuf (old behaviour).
+ * 1:         Allocate size-aligned pbufs with minimal excess. Use this if your
+ *            scatter-gather DMA requires aligned fragments.
+ * 128:       Limit the pbuf/memory overhead to 20%.
+ * TCP_MSS:   Try to create unfragmented TCP packets.
+ * TCP_MSS/4: Try to create 4 fragments or less per TCP packet.
+ */
+#if !defined TCP_OVERSIZE || defined __DOXYGEN__
+#define TCP_OVERSIZE                    TCP_MSS
+#endif
+
+/**
+ * LWIP_TCP_TIMESTAMPS==1: support the TCP timestamp option.
+ * The timestamp option is currently only used to help remote hosts, it is not
+ * really used locally. Therefore, it is only enabled when a TS option is
+ * received in the initial SYN packet from a remote host.
+ */
+#if !defined LWIP_TCP_TIMESTAMPS || defined __DOXYGEN__
+#define LWIP_TCP_TIMESTAMPS             0
+#endif
+
+/**
+ * TCP_WND_UPDATE_THRESHOLD: difference in window to trigger an
+ * explicit window update
+ */
+#if !defined TCP_WND_UPDATE_THRESHOLD || defined __DOXYGEN__
+#define TCP_WND_UPDATE_THRESHOLD        LWIP_MIN((TCP_WND / 4), (TCP_MSS * 4))
+#endif
+
+/**
+ * LWIP_EVENT_API and LWIP_CALLBACK_API: Only one of these should be set to 1.
+ *     LWIP_EVENT_API==1: The user defines lwip_tcp_event() to receive all
+ *         events (accept, sent, etc) that happen in the system.
+ *     LWIP_CALLBACK_API==1: The PCB callback function is called directly
+ *         for the event. This is the default.
+ */
+#if !defined(LWIP_EVENT_API) && !defined(LWIP_CALLBACK_API) || defined __DOXYGEN__
+#define LWIP_EVENT_API                  0
+#define LWIP_CALLBACK_API               1
+#else
+#ifndef LWIP_EVENT_API
+#define LWIP_EVENT_API                  0
+#endif
+#ifndef LWIP_CALLBACK_API
+#define LWIP_CALLBACK_API               0
+#endif
+#endif
+
+/**
+ * LWIP_WND_SCALE and TCP_RCV_SCALE:
+ * Set LWIP_WND_SCALE to 1 to enable window scaling.
+ * Set TCP_RCV_SCALE to the desired scaling factor (shift count in the
+ * range of [0..14]).
+ * When LWIP_WND_SCALE is enabled but TCP_RCV_SCALE is 0, we can use a large
+ * send window while having a small receive window only.
+ */
+#if !defined LWIP_WND_SCALE || defined __DOXYGEN__
+#define LWIP_WND_SCALE                  0
+#define TCP_RCV_SCALE                   0
+#endif
+
+/**
+ * LWIP_TCP_PCB_NUM_EXT_ARGS:
+ * When this is > 0, every tcp pcb (including listen pcb) includes a number of
+ * additional argument entries in an array (see tcp_ext_arg_alloc_id)
+ */
+#if !defined LWIP_TCP_PCB_NUM_EXT_ARGS || defined __DOXYGEN__
+#define LWIP_TCP_PCB_NUM_EXT_ARGS       0
+#endif
+
+/** LWIP_ALTCP==1: enable the altcp API.
+ * altcp is an abstraction layer that prevents applications linking against the
+ * tcp.h functions but provides the same functionality. It is used to e.g. add
+ * SSL/TLS or proxy-connect support to an application written for the tcp callback
+ * API without that application knowing the protocol details.
+ *
+ * With LWIP_ALTCP==0, applications written against the altcp API can still be
+ * compiled but are directly linked against the tcp.h callback API and then
+ * cannot use layered protocols.
+ *
+ * See @ref altcp_api
+ */
+#if !defined LWIP_ALTCP || defined __DOXYGEN__
+#define LWIP_ALTCP                      0
+#endif
+
+/** LWIP_ALTCP_TLS==1: enable TLS support for altcp API.
+ * This needs a port of the functions in altcp_tls.h to a TLS library.
+ * A port to ARM mbedtls is provided with lwIP, see apps/altcp_tls/ directory
+ * and LWIP_ALTCP_TLS_MBEDTLS option.
+ */
+#if !defined LWIP_ALTCP_TLS || defined __DOXYGEN__
+#define LWIP_ALTCP_TLS                  0
+#endif
+
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- Pbuf options ----------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_pbuf PBUF
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * PBUF_LINK_HLEN: the number of bytes that should be allocated for a
+ * link level header. The default is 14, the standard value for
+ * Ethernet.
+ */
+#if !defined PBUF_LINK_HLEN || defined __DOXYGEN__
+#if defined LWIP_HOOK_VLAN_SET && !defined __DOXYGEN__
+#define PBUF_LINK_HLEN                  (18 + ETH_PAD_SIZE)
+#else /* LWIP_HOOK_VLAN_SET */
+#define PBUF_LINK_HLEN                  (14 + ETH_PAD_SIZE)
+#endif /* LWIP_HOOK_VLAN_SET */
+#endif
+
+/**
+ * PBUF_LINK_ENCAPSULATION_HLEN: the number of bytes that should be allocated
+ * for an additional encapsulation header before ethernet headers (e.g. 802.11)
+ */
+#if !defined PBUF_LINK_ENCAPSULATION_HLEN || defined __DOXYGEN__
+#define PBUF_LINK_ENCAPSULATION_HLEN    0
+#endif
+
+/**
+ * PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. The default is
+ * designed to accommodate single full size TCP frame in one pbuf, including
+ * TCP_MSS, IP header, and link header.
+ */
+#if !defined PBUF_POOL_BUFSIZE || defined __DOXYGEN__
+#define PBUF_POOL_BUFSIZE               LWIP_MEM_ALIGN_SIZE(TCP_MSS+PBUF_IP_HLEN+PBUF_TRANSPORT_HLEN+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)
+#endif
+
+/**
+ * LWIP_PBUF_REF_T: Refcount type in pbuf.
+ * Default width of u8_t can be increased if 255 refs are not enough for you.
+ */
+#if !defined LWIP_PBUF_REF_T || defined __DOXYGEN__
+#define LWIP_PBUF_REF_T                 u8_t
+#endif
+
+/**
+ * LWIP_PBUF_CUSTOM_DATA: Store private data on pbufs (e.g. timestamps)
+ * This extends struct pbuf so user can store custom data on every pbuf.
+ */
+#if !defined LWIP_PBUF_CUSTOM_DATA || defined __DOXYGEN__
+#define LWIP_PBUF_CUSTOM_DATA
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------------------
+   ---------- Network Interfaces options ----------
+   ------------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_netif NETIF
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * LWIP_SINGLE_NETIF==1: use a single netif only. This is the common case for
+ * small real-life targets. Some code like routing etc. can be left out.
+ */
+#if !defined LWIP_SINGLE_NETIF || defined __DOXYGEN__
+#define LWIP_SINGLE_NETIF               0
+#endif
+
+/**
+ * LWIP_NETIF_HOSTNAME==1: use DHCP_OPTION_HOSTNAME with netif's hostname
+ * field.
+ */
+#if !defined LWIP_NETIF_HOSTNAME || defined __DOXYGEN__
+#define LWIP_NETIF_HOSTNAME             0
+#endif
+
+/**
+ * LWIP_NETIF_API==1: Support netif api (in netifapi.c)
+ */
+#if !defined LWIP_NETIF_API || defined __DOXYGEN__
+#define LWIP_NETIF_API                  0
+#endif
+
+/**
+ * LWIP_NETIF_STATUS_CALLBACK==1: Support a callback function whenever an interface
+ * changes its up/down status (i.e., due to DHCP IP acquisition)
+ */
+#if !defined LWIP_NETIF_STATUS_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_STATUS_CALLBACK      0
+#endif
+
+/**
+ * LWIP_NETIF_EXT_STATUS_CALLBACK==1: Support an extended callback function 
+ * for several netif related event that supports multiple subscribers.
+ * @see netif_ext_status_callback
+ */
+#if !defined LWIP_NETIF_EXT_STATUS_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_EXT_STATUS_CALLBACK  0
+#endif
+
+/**
+ * LWIP_NETIF_LINK_CALLBACK==1: Support a callback function from an interface
+ * whenever the link changes (i.e., link down)
+ */
+#if !defined LWIP_NETIF_LINK_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_LINK_CALLBACK        0
+#endif
+
+/**
+ * LWIP_NETIF_REMOVE_CALLBACK==1: Support a callback function that is called
+ * when a netif has been removed
+ */
+#if !defined LWIP_NETIF_REMOVE_CALLBACK || defined __DOXYGEN__
+#define LWIP_NETIF_REMOVE_CALLBACK      0
+#endif
+
+/**
+ * LWIP_NETIF_HWADDRHINT==1: Cache link-layer-address hints (e.g. table
+ * indices) in struct netif. TCP and UDP can make use of this to prevent
+ * scanning the ARP table for every sent packet. While this is faster for big
+ * ARP tables or many concurrent connections, it might be counterproductive
+ * if you have a tiny ARP table or if there never are concurrent connections.
+ */
+#if !defined LWIP_NETIF_HWADDRHINT || defined __DOXYGEN__
+#define LWIP_NETIF_HWADDRHINT           0
+#endif
+
+/**
+ * LWIP_NETIF_TX_SINGLE_PBUF: if this is set to 1, lwIP *tries* to put all data
+ * to be sent into one single pbuf. This is for compatibility with DMA-enabled
+ * MACs that do not support scatter-gather.
+ * Beware that this might involve CPU-memcpy before transmitting that would not
+ * be needed without this flag! Use this only if you need to!
+ *
+ * ATTENTION: a driver should *NOT* rely on getting single pbufs but check TX
+ * pbufs for being in one piece. If not, @ref pbuf_clone can be used to get
+ * a single pbuf:
+ *   if (p->next != NULL) {
+ *     struct pbuf *q = pbuf_clone(PBUF_RAW, PBUF_RAM, p);
+ *     if (q == NULL) {
+ *       return ERR_MEM;
+ *     }
+ *     p = q; ATTENTION: do NOT free the old 'p' as the ref belongs to the caller!
+ *   }
+ */
+#if !defined LWIP_NETIF_TX_SINGLE_PBUF || defined __DOXYGEN__
+#define LWIP_NETIF_TX_SINGLE_PBUF       0
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+/**
+ * LWIP_NUM_NETIF_CLIENT_DATA: Number of clients that may store
+ * data in client_data member array of struct netif (max. 256).
+ */
+#if !defined LWIP_NUM_NETIF_CLIENT_DATA || defined __DOXYGEN__
+#define LWIP_NUM_NETIF_CLIENT_DATA      0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- LOOPIF options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_loop Loopback interface
+ * @ingroup lwip_opts_netif
+ * @{
+ */
+/**
+ * LWIP_HAVE_LOOPIF==1: Support loop interface (127.0.0.1).
+ * This is only needed when no real netifs are available. If at least one other
+ * netif is available, loopback traffic uses this netif.
+ */
+#if !defined LWIP_HAVE_LOOPIF || defined __DOXYGEN__
+#define LWIP_HAVE_LOOPIF                (LWIP_NETIF_LOOPBACK && !LWIP_SINGLE_NETIF)
+#endif
+
+/**
+ * LWIP_LOOPIF_MULTICAST==1: Support multicast/IGMP on loop interface (127.0.0.1).
+ */
+#if !defined LWIP_LOOPIF_MULTICAST || defined __DOXYGEN__
+#define LWIP_LOOPIF_MULTICAST           0
+#endif
+
+/**
+ * LWIP_NETIF_LOOPBACK==1: Support sending packets with a destination IP
+ * address equal to the netif IP address, looping them back up the stack.
+ */
+#if !defined LWIP_NETIF_LOOPBACK || defined __DOXYGEN__
+#define LWIP_NETIF_LOOPBACK             0
+#endif
+
+/**
+ * LWIP_LOOPBACK_MAX_PBUFS: Maximum number of pbufs on queue for loopback
+ * sending for each netif (0 = disabled)
+ */
+#if !defined LWIP_LOOPBACK_MAX_PBUFS || defined __DOXYGEN__
+#define LWIP_LOOPBACK_MAX_PBUFS         0
+#endif
+
+/**
+ * LWIP_NETIF_LOOPBACK_MULTITHREADING: Indicates whether threading is enabled in
+ * the system, as netifs must change how they behave depending on this setting
+ * for the LWIP_NETIF_LOOPBACK option to work.
+ * Setting this is needed to avoid reentering non-reentrant functions like
+ * tcp_input().
+ *    LWIP_NETIF_LOOPBACK_MULTITHREADING==1: Indicates that the user is using a
+ *       multithreaded environment like tcpip.c. In this case, netif->input()
+ *       is called directly.
+ *    LWIP_NETIF_LOOPBACK_MULTITHREADING==0: Indicates a polling (or NO_SYS) setup.
+ *       The packets are put on a list and netif_poll() must be called in
+ *       the main application loop.
+ */
+#if !defined LWIP_NETIF_LOOPBACK_MULTITHREADING || defined __DOXYGEN__
+#define LWIP_NETIF_LOOPBACK_MULTITHREADING    (!NO_SYS)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- Thread options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_thread Threading
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * TCPIP_THREAD_NAME: The name assigned to the main tcpip thread.
+ */
+#if !defined TCPIP_THREAD_NAME || defined __DOXYGEN__
+#define TCPIP_THREAD_NAME               "tcpip_thread"
+#endif
+
+/**
+ * TCPIP_THREAD_STACKSIZE: The stack size used by the main tcpip thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined TCPIP_THREAD_STACKSIZE || defined __DOXYGEN__
+#define TCPIP_THREAD_STACKSIZE          0
+#endif
+
+/**
+ * TCPIP_THREAD_PRIO: The priority assigned to the main tcpip thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined TCPIP_THREAD_PRIO || defined __DOXYGEN__
+#define TCPIP_THREAD_PRIO               1
+#endif
+
+/**
+ * TCPIP_MBOX_SIZE: The mailbox size for the tcpip thread messages
+ * The queue size value itself is platform-dependent, but is passed to
+ * sys_mbox_new() when tcpip_init is called.
+ */
+#if !defined TCPIP_MBOX_SIZE || defined __DOXYGEN__
+#define TCPIP_MBOX_SIZE                 0
+#endif
+
+/**
+ * Define this to something that triggers a watchdog. This is called from
+ * tcpip_thread after processing a message.
+ */
+#if !defined LWIP_TCPIP_THREAD_ALIVE || defined __DOXYGEN__
+#define LWIP_TCPIP_THREAD_ALIVE()
+#endif
+
+/**
+ * SLIPIF_THREAD_NAME: The name assigned to the slipif_loop thread.
+ */
+#if !defined SLIPIF_THREAD_NAME || defined __DOXYGEN__
+#define SLIPIF_THREAD_NAME              "slipif_loop"
+#endif
+
+/**
+ * SLIP_THREAD_STACKSIZE: The stack size used by the slipif_loop thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined SLIPIF_THREAD_STACKSIZE || defined __DOXYGEN__
+#define SLIPIF_THREAD_STACKSIZE         0
+#endif
+
+/**
+ * SLIPIF_THREAD_PRIO: The priority assigned to the slipif_loop thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined SLIPIF_THREAD_PRIO || defined __DOXYGEN__
+#define SLIPIF_THREAD_PRIO              1
+#endif
+
+/**
+ * DEFAULT_THREAD_NAME: The name assigned to any other lwIP thread.
+ */
+#if !defined DEFAULT_THREAD_NAME || defined __DOXYGEN__
+#define DEFAULT_THREAD_NAME             "lwIP"
+#endif
+
+/**
+ * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined DEFAULT_THREAD_STACKSIZE || defined __DOXYGEN__
+#define DEFAULT_THREAD_STACKSIZE        0
+#endif
+
+/**
+ * DEFAULT_THREAD_PRIO: The priority assigned to any other lwIP thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#if !defined DEFAULT_THREAD_PRIO || defined __DOXYGEN__
+#define DEFAULT_THREAD_PRIO             1
+#endif
+
+/**
+ * DEFAULT_RAW_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_RAW. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#if !defined DEFAULT_RAW_RECVMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_RAW_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_UDP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_UDP. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#if !defined DEFAULT_UDP_RECVMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_UDP_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_TCP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_TCP. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#if !defined DEFAULT_TCP_RECVMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_TCP_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_ACCEPTMBOX_SIZE: The mailbox size for the incoming connections.
+ * The queue size value itself is platform-dependent, but is passed to
+ * sys_mbox_new() when the acceptmbox is created.
+ */
+#if !defined DEFAULT_ACCEPTMBOX_SIZE || defined __DOXYGEN__
+#define DEFAULT_ACCEPTMBOX_SIZE         0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------------------
+   ---------- Sequential layer options ----------
+   ----------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_netconn Netconn
+ * @ingroup lwip_opts_threadsafe_apis
+ * @{
+ */
+/**
+ * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
+ */
+#if !defined LWIP_NETCONN || defined __DOXYGEN__
+#define LWIP_NETCONN                    1
+#endif
+
+/** LWIP_TCPIP_TIMEOUT==1: Enable tcpip_timeout/tcpip_untimeout to create
+ * timers running in tcpip_thread from another thread.
+ */
+#if !defined LWIP_TCPIP_TIMEOUT || defined __DOXYGEN__
+#define LWIP_TCPIP_TIMEOUT              0
+#endif
+
+/** LWIP_NETCONN_SEM_PER_THREAD==1: Use one (thread-local) semaphore per
+ * thread calling socket/netconn functions instead of allocating one
+ * semaphore per netconn (and per select etc.)
+ * ATTENTION: a thread-local semaphore for API calls is needed:
+ * - LWIP_NETCONN_THREAD_SEM_GET() returning a sys_sem_t*
+ * - LWIP_NETCONN_THREAD_SEM_ALLOC() creating the semaphore
+ * - LWIP_NETCONN_THREAD_SEM_FREE() freeing the semaphore
+ * The latter 2 can be invoked up by calling netconn_thread_init()/netconn_thread_cleanup().
+ * Ports may call these for threads created with sys_thread_new().
+ */
+#if !defined LWIP_NETCONN_SEM_PER_THREAD || defined __DOXYGEN__
+#define LWIP_NETCONN_SEM_PER_THREAD     0
+#endif
+
+/** LWIP_NETCONN_FULLDUPLEX==1: Enable code that allows reading from one thread,
+ * writing from a 2nd thread and closing from a 3rd thread at the same time.
+ * LWIP_NETCONN_SEM_PER_THREAD==1 is required to use one socket/netconn from
+ * multiple threads at once!
+ */
+#if !defined LWIP_NETCONN_FULLDUPLEX || defined __DOXYGEN__
+#define LWIP_NETCONN_FULLDUPLEX         0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- Socket options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_socket Sockets
+ * @ingroup lwip_opts_threadsafe_apis
+ * @{
+ */
+/**
+ * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
+ */
+#if !defined LWIP_SOCKET || defined __DOXYGEN__
+#define LWIP_SOCKET                     1
+#endif
+
+/**
+ * LWIP_COMPAT_SOCKETS==1: Enable BSD-style sockets functions names through defines.
+ * LWIP_COMPAT_SOCKETS==2: Same as ==1 but correctly named functions are created.
+ * While this helps code completion, it might conflict with existing libraries.
+ * (only used if you use sockets.c)
+ */
+#if !defined LWIP_COMPAT_SOCKETS || defined __DOXYGEN__
+#define LWIP_COMPAT_SOCKETS             1
+#endif
+
+/**
+ * LWIP_POSIX_SOCKETS_IO_NAMES==1: Enable POSIX-style sockets functions names.
+ * Disable this option if you use a POSIX operating system that uses the same
+ * names (read, write & close). (only used if you use sockets.c)
+ */
+#if !defined LWIP_POSIX_SOCKETS_IO_NAMES || defined __DOXYGEN__
+#define LWIP_POSIX_SOCKETS_IO_NAMES     1
+#endif
+
+/**
+ * LWIP_SOCKET_OFFSET==n: Increases the file descriptor number created by LwIP with n.
+ * This can be useful when there are multiple APIs which create file descriptors.
+ * When they all start with a different offset and you won't make them overlap you can
+ * re implement read/write/close/ioctl/fnctl to send the requested action to the right
+ * library (sharing select will need more work though).
+ */
+#if !defined LWIP_SOCKET_OFFSET || defined __DOXYGEN__
+#define LWIP_SOCKET_OFFSET              0
+#endif
+
+/**
+ * LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
+ * options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set
+ * in seconds. (does not require sockets.c, and will affect tcp.c)
+ */
+#if !defined LWIP_TCP_KEEPALIVE || defined __DOXYGEN__
+#define LWIP_TCP_KEEPALIVE              0
+#endif
+
+/**
+ * LWIP_SO_SNDTIMEO==1: Enable send timeout for sockets/netconns and
+ * SO_SNDTIMEO processing.
+ */
+#if !defined LWIP_SO_SNDTIMEO || defined __DOXYGEN__
+#define LWIP_SO_SNDTIMEO                0
+#endif
+
+/**
+ * LWIP_SO_RCVTIMEO==1: Enable receive timeout for sockets/netconns and
+ * SO_RCVTIMEO processing.
+ */
+#if !defined LWIP_SO_RCVTIMEO || defined __DOXYGEN__
+#define LWIP_SO_RCVTIMEO                0
+#endif
+
+/**
+ * LWIP_SO_SNDRCVTIMEO_NONSTANDARD==1: SO_RCVTIMEO/SO_SNDTIMEO take an int
+ * (milliseconds, much like winsock does) instead of a struct timeval (default).
+ */
+#if !defined LWIP_SO_SNDRCVTIMEO_NONSTANDARD || defined __DOXYGEN__
+#define LWIP_SO_SNDRCVTIMEO_NONSTANDARD 0
+#endif
+
+/**
+ * LWIP_SO_RCVBUF==1: Enable SO_RCVBUF processing.
+ */
+#if !defined LWIP_SO_RCVBUF || defined __DOXYGEN__
+#define LWIP_SO_RCVBUF                  0
+#endif
+
+/**
+ * LWIP_SO_LINGER==1: Enable SO_LINGER processing.
+ */
+#if !defined LWIP_SO_LINGER || defined __DOXYGEN__
+#define LWIP_SO_LINGER                  0
+#endif
+
+/**
+ * If LWIP_SO_RCVBUF is used, this is the default value for recv_bufsize.
+ */
+#if !defined RECV_BUFSIZE_DEFAULT || defined __DOXYGEN__
+#define RECV_BUFSIZE_DEFAULT            INT_MAX
+#endif
+
+/**
+ * By default, TCP socket/netconn close waits 20 seconds max to send the FIN
+ */
+#if !defined LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT || defined __DOXYGEN__
+#define LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT 20000
+#endif
+
+/**
+ * SO_REUSE==1: Enable SO_REUSEADDR option.
+ */
+#if !defined SO_REUSE || defined __DOXYGEN__
+#define SO_REUSE                        0
+#endif
+
+/**
+ * SO_REUSE_RXTOALL==1: Pass a copy of incoming broadcast/multicast packets
+ * to all local matches if SO_REUSEADDR is turned on.
+ * WARNING: Adds a memcpy for every packet if passing to more than one pcb!
+ */
+#if !defined SO_REUSE_RXTOALL || defined __DOXYGEN__
+#define SO_REUSE_RXTOALL                0
+#endif
+
+/**
+ * LWIP_FIONREAD_LINUXMODE==0 (default): ioctl/FIONREAD returns the amount of
+ * pending data in the network buffer. This is the way windows does it. It's
+ * the default for lwIP since it is smaller.
+ * LWIP_FIONREAD_LINUXMODE==1: ioctl/FIONREAD returns the size of the next
+ * pending datagram in bytes. This is the way linux does it. This code is only
+ * here for compatibility.
+ */
+#if !defined LWIP_FIONREAD_LINUXMODE || defined __DOXYGEN__
+#define LWIP_FIONREAD_LINUXMODE         0
+#endif
+
+/**
+ * LWIP_SOCKET_SELECT==1 (default): enable select() for sockets (uses a netconn
+ * callback to keep track of events).
+ * This saves RAM (counters per socket) and code (netconn event callback), which
+ * should improve performance a bit).
+ */
+#if !defined LWIP_SOCKET_SELECT || defined __DOXYGEN__
+#define LWIP_SOCKET_SELECT              1
+#endif
+
+/**
+ * LWIP_SOCKET_POLL==1 (default): enable poll() for sockets (including
+ * struct pollfd, nfds_t, and constants)
+ */
+#if !defined LWIP_SOCKET_POLL || defined __DOXYGEN__
+#define LWIP_SOCKET_POLL                1
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------------
+   ---------- Statistics options ----------
+   ----------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_stats Statistics
+ * @ingroup lwip_opts_debug
+ * @{
+ */
+/**
+ * LWIP_STATS==1: Enable statistics collection in lwip_stats.
+ */
+#if !defined LWIP_STATS || defined __DOXYGEN__
+#define LWIP_STATS                      1
+#endif
+
+#if LWIP_STATS
+
+/**
+ * LWIP_STATS_DISPLAY==1: Compile in the statistics output functions.
+ */
+#if !defined LWIP_STATS_DISPLAY || defined __DOXYGEN__
+#define LWIP_STATS_DISPLAY              0
+#endif
+
+/**
+ * LINK_STATS==1: Enable link stats.
+ */
+#if !defined LINK_STATS || defined __DOXYGEN__
+#define LINK_STATS                      1
+#endif
+
+/**
+ * ETHARP_STATS==1: Enable etharp stats.
+ */
+#if !defined ETHARP_STATS || defined __DOXYGEN__
+#define ETHARP_STATS                    (LWIP_ARP)
+#endif
+
+/**
+ * IP_STATS==1: Enable IP stats.
+ */
+#if !defined IP_STATS || defined __DOXYGEN__
+#define IP_STATS                        1
+#endif
+
+/**
+ * IPFRAG_STATS==1: Enable IP fragmentation stats. Default is
+ * on if using either frag or reass.
+ */
+#if !defined IPFRAG_STATS || defined __DOXYGEN__
+#define IPFRAG_STATS                    (IP_REASSEMBLY || IP_FRAG)
+#endif
+
+/**
+ * ICMP_STATS==1: Enable ICMP stats.
+ */
+#if !defined ICMP_STATS || defined __DOXYGEN__
+#define ICMP_STATS                      1
+#endif
+
+/**
+ * IGMP_STATS==1: Enable IGMP stats.
+ */
+#if !defined IGMP_STATS || defined __DOXYGEN__
+#define IGMP_STATS                      (LWIP_IGMP)
+#endif
+
+/**
+ * UDP_STATS==1: Enable UDP stats. Default is on if
+ * UDP enabled, otherwise off.
+ */
+#if !defined UDP_STATS || defined __DOXYGEN__
+#define UDP_STATS                       (LWIP_UDP)
+#endif
+
+/**
+ * TCP_STATS==1: Enable TCP stats. Default is on if TCP
+ * enabled, otherwise off.
+ */
+#if !defined TCP_STATS || defined __DOXYGEN__
+#define TCP_STATS                       (LWIP_TCP)
+#endif
+
+/**
+ * MEM_STATS==1: Enable mem.c stats.
+ */
+#if !defined MEM_STATS || defined __DOXYGEN__
+#define MEM_STATS                       ((MEM_LIBC_MALLOC == 0) && (MEM_USE_POOLS == 0))
+#endif
+
+/**
+ * MEMP_STATS==1: Enable memp.c pool stats.
+ */
+#if !defined MEMP_STATS || defined __DOXYGEN__
+#define MEMP_STATS                      (MEMP_MEM_MALLOC == 0)
+#endif
+
+/**
+ * SYS_STATS==1: Enable system stats (sem and mbox counts, etc).
+ */
+#if !defined SYS_STATS || defined __DOXYGEN__
+#define SYS_STATS                       (NO_SYS == 0)
+#endif
+
+/**
+ * IP6_STATS==1: Enable IPv6 stats.
+ */
+#if !defined IP6_STATS || defined __DOXYGEN__
+#define IP6_STATS                       (LWIP_IPV6)
+#endif
+
+/**
+ * ICMP6_STATS==1: Enable ICMP for IPv6 stats.
+ */
+#if !defined ICMP6_STATS || defined __DOXYGEN__
+#define ICMP6_STATS                     (LWIP_IPV6 && LWIP_ICMP6)
+#endif
+
+/**
+ * IP6_FRAG_STATS==1: Enable IPv6 fragmentation stats.
+ */
+#if !defined IP6_FRAG_STATS || defined __DOXYGEN__
+#define IP6_FRAG_STATS                  (LWIP_IPV6 && (LWIP_IPV6_FRAG || LWIP_IPV6_REASS))
+#endif
+
+/**
+ * MLD6_STATS==1: Enable MLD for IPv6 stats.
+ */
+#if !defined MLD6_STATS || defined __DOXYGEN__
+#define MLD6_STATS                      (LWIP_IPV6 && LWIP_IPV6_MLD)
+#endif
+
+/**
+ * ND6_STATS==1: Enable Neighbor discovery for IPv6 stats.
+ */
+#if !defined ND6_STATS || defined __DOXYGEN__
+#define ND6_STATS                       (LWIP_IPV6)
+#endif
+
+/**
+ * MIB2_STATS==1: Stats for SNMP MIB2.
+ */
+#if !defined MIB2_STATS || defined __DOXYGEN__
+#define MIB2_STATS                      0
+#endif
+
+/**
+ * IP_NAPT_STATS==1: Stats for IP NAPT.
+ */
+#if !defined IP_NAPT_STATS || defined __DOXYGEN__
+#define IP_NAPT_STATS                   (IP_NAPT)
+#endif
+
+
+#else
+
+#define LINK_STATS                      0
+#define ETHARP_STATS                    0
+#define IP_STATS                        0
+#define IPFRAG_STATS                    0
+#define ICMP_STATS                      0
+#define IGMP_STATS                      0
+#define UDP_STATS                       0
+#define TCP_STATS                       0
+#define MEM_STATS                       0
+#define MEMP_STATS                      0
+#define SYS_STATS                       0
+#define LWIP_STATS_DISPLAY              0
+#define IP6_STATS                       0
+#define ICMP6_STATS                     0
+#define IP6_FRAG_STATS                  0
+#define MLD6_STATS                      0
+#define ND6_STATS                       0
+#define MIB2_STATS                      0
+#define IP_NAPT_STATS                   0
+
+#endif /* LWIP_STATS */
+/**
+ * @}
+ */
+
+/*
+   --------------------------------------
+   ---------- Checksum options ----------
+   --------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_checksum Checksum
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_CHECKSUM_CTRL_PER_NETIF==1: Checksum generation/check can be enabled/disabled
+ * per netif.
+ * ATTENTION: if enabled, the CHECKSUM_GEN_* and CHECKSUM_CHECK_* defines must be enabled!
+ */
+#if !defined LWIP_CHECKSUM_CTRL_PER_NETIF || defined __DOXYGEN__
+#define LWIP_CHECKSUM_CTRL_PER_NETIF    0
+#endif
+
+/**
+ * CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.
+ */
+#if !defined CHECKSUM_GEN_IP || defined __DOXYGEN__
+#define CHECKSUM_GEN_IP                 1
+#endif
+
+/**
+ * CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.
+ */
+#if !defined CHECKSUM_GEN_UDP || defined __DOXYGEN__
+#define CHECKSUM_GEN_UDP                1
+#endif
+
+/**
+ * CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.
+ */
+#if !defined CHECKSUM_GEN_TCP || defined __DOXYGEN__
+#define CHECKSUM_GEN_TCP                1
+#endif
+
+/**
+ * CHECKSUM_GEN_ICMP==1: Generate checksums in software for outgoing ICMP packets.
+ */
+#if !defined CHECKSUM_GEN_ICMP || defined __DOXYGEN__
+#define CHECKSUM_GEN_ICMP               1
+#endif
+
+/**
+ * CHECKSUM_GEN_ICMP6==1: Generate checksums in software for outgoing ICMP6 packets.
+ */
+#if !defined CHECKSUM_GEN_ICMP6 || defined __DOXYGEN__
+#define CHECKSUM_GEN_ICMP6              1
+#endif
+
+/**
+ * CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.
+ */
+#if !defined CHECKSUM_CHECK_IP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_IP               1
+#endif
+
+/**
+ * CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.
+ */
+#if !defined CHECKSUM_CHECK_UDP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_UDP              1
+#endif
+
+/**
+ * CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.
+ */
+#if !defined CHECKSUM_CHECK_TCP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_TCP              1
+#endif
+
+/**
+ * CHECKSUM_CHECK_ICMP==1: Check checksums in software for incoming ICMP packets.
+ */
+#if !defined CHECKSUM_CHECK_ICMP || defined __DOXYGEN__
+#define CHECKSUM_CHECK_ICMP             1
+#endif
+
+/**
+ * CHECKSUM_CHECK_ICMP6==1: Check checksums in software for incoming ICMPv6 packets
+ */
+#if !defined CHECKSUM_CHECK_ICMP6 || defined __DOXYGEN__
+#define CHECKSUM_CHECK_ICMP6            1
+#endif
+
+/**
+ * LWIP_CHECKSUM_ON_COPY==1: Calculate checksum when copying data from
+ * application buffers to pbufs.
+ */
+#if !defined LWIP_CHECKSUM_ON_COPY || defined __DOXYGEN__
+#define LWIP_CHECKSUM_ON_COPY           0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------------
+   ---------- IPv6 options ---------------
+   ---------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_ipv6 IPv6
+ * @ingroup lwip_opts
+ * @{
+ */
+/**
+ * LWIP_IPV6==1: Enable IPv6
+ */
+#if !defined LWIP_IPV6 || defined __DOXYGEN__
+#define LWIP_IPV6                       0
+#endif
+
+/**
+ * when LWIP_FORCE_ROUTER_FORWARDING is enbaled in lwip, the router flag in NA packet will always
+ * set to 1, otherwise, never set router flag for NA packets.
+ */
+#if !defined LWIP_FORCE_ROUTER_FORWARDING || defined __DOXYGEN__
+#define LWIP_FORCE_ROUTER_FORWARDING 0
+#endif
+
+/**
+ * LWIP_ND6==1: Enable NDP
+ * when LWIP_IPV6 is enabled in lwIP, NDP timer is enabled by default with a timeout of 1 second.
+ * However, in the case of sleepy end-device, NDP is not required.
+ * This leads to CPU waking up every 1 second, resulting in increased power consumption.
+ * Therefore, add a option to control nd6, using LWIP_ND6 enable/disable ND6 protocol.
+ * Unless you are very clear that you do not need to use ND6, please do not disable it!
+ */
+#if !defined LWIP_ND6 || defined __DOXYGEN__
+#define LWIP_ND6                       1
+#endif
+
+/**
+ * IPV6_REASS_MAXAGE: Maximum time (in multiples of IP6_REASS_TMR_INTERVAL - so seconds, normally)
+ * a fragmented IP packet waits for all fragments to arrive. If not all fragments arrived
+ * in this time, the whole packet is discarded.
+ */
+#if !defined IPV6_REASS_MAXAGE || defined __DOXYGEN__
+#define IPV6_REASS_MAXAGE               60
+#endif
+
+/**
+ * LWIP_IPV6_SCOPES==1: Enable support for IPv6 address scopes, ensuring that
+ * e.g. link-local addresses are really treated as link-local. Disable this
+ * setting only for single-interface configurations.
+ * All addresses that have a scope according to the default policy (link-local
+ * unicast addresses, interface-local and link-local multicast addresses) should
+ * now have a zone set on them before being passed to the core API, although
+ * lwIP will currently attempt to select a zone on the caller's behalf when 
+ * necessary. Applications that directly assign IPv6 addresses to interfaces
+ * (which is NOT recommended) must now ensure that link-local addresses carry
+ * the netif's zone. See the new ip6_zone.h header file for more information and
+ * relevant macros. For now it is still possible to turn off scopes support
+ * through the new LWIP_IPV6_SCOPES option. When upgrading an implementation that
+ * uses the core API directly, it is highly recommended to enable
+ * LWIP_IPV6_SCOPES_DEBUG at least for a while, to ensure e.g. proper address
+ * initialization.
+ */
+#if !defined LWIP_IPV6_SCOPES || defined __DOXYGEN__
+#define LWIP_IPV6_SCOPES                (LWIP_IPV6 && !LWIP_SINGLE_NETIF)
+#endif
+
+/**
+ * LWIP_IPV6_SCOPES_DEBUG==1: Perform run-time checks to verify that addresses
+ * are properly zoned (see ip6_zone.h on what that means) where it matters.
+ * Enabling this setting is highly recommended when upgrading from an existing
+ * installation that is not yet scope-aware; otherwise it may be too expensive.
+ */
+#if !defined LWIP_IPV6_SCOPES_DEBUG || defined __DOXYGEN__
+#define LWIP_IPV6_SCOPES_DEBUG          0
+#endif
+
+/**
+ * LWIP_IPV6_NUM_ADDRESSES: Number of IPv6 addresses per netif.
+ */
+#if !defined LWIP_IPV6_NUM_ADDRESSES || defined __DOXYGEN__
+#define LWIP_IPV6_NUM_ADDRESSES         3
+#endif
+
+/**
+ * LWIP_IPV6_FORWARD==1: Forward IPv6 packets across netifs
+ */
+#if !defined LWIP_IPV6_FORWARD || defined __DOXYGEN__
+#define LWIP_IPV6_FORWARD               0
+#endif
+
+/**
+ * LWIP_IPV6_FRAG==1: Fragment outgoing IPv6 packets that are too big.
+ */
+#if !defined LWIP_IPV6_FRAG || defined __DOXYGEN__
+#define LWIP_IPV6_FRAG                  1
+#endif
+
+/**
+ * LWIP_IPV6_REASS==1: reassemble incoming IPv6 packets that fragmented
+ */
+#if !defined LWIP_IPV6_REASS || defined __DOXYGEN__
+#define LWIP_IPV6_REASS                 LWIP_IPV6
+#endif
+
+/**
+ * LWIP_IPV6_SEND_ROUTER_SOLICIT==1: Send router solicitation messages during
+ * network startup.
+ */
+#if !defined LWIP_IPV6_SEND_ROUTER_SOLICIT || defined __DOXYGEN__
+#define LWIP_IPV6_SEND_ROUTER_SOLICIT   LWIP_IPV6
+#endif
+
+/**
+ * LWIP_IPV6_AUTOCONFIG==1: Enable stateless address autoconfiguration as per RFC 4862.
+ */
+#if !defined LWIP_IPV6_AUTOCONFIG || defined __DOXYGEN__
+#define LWIP_IPV6_AUTOCONFIG            LWIP_IPV6
+#endif
+
+/**
+ * LWIP_IPV6_ADDRESS_LIFETIMES==1: Keep valid and preferred lifetimes for each
+ * IPv6 address. Required for LWIP_IPV6_AUTOCONFIG. May still be enabled
+ * otherwise, in which case the application may assign address lifetimes with
+ * the appropriate macros. Addresses with no lifetime are assumed to be static.
+ * If this option is disabled, all addresses are assumed to be static.
+ */
+#if !defined LWIP_IPV6_ADDRESS_LIFETIMES || defined __DOXYGEN__
+#define LWIP_IPV6_ADDRESS_LIFETIMES     LWIP_IPV6_AUTOCONFIG
+#endif
+
+/**
+ * LWIP_IPV6_DUP_DETECT_ATTEMPTS=[0..7]: Number of duplicate address detection attempts.
+ */
+#if !defined LWIP_IPV6_DUP_DETECT_ATTEMPTS || defined __DOXYGEN__
+#define LWIP_IPV6_DUP_DETECT_ATTEMPTS   1
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_icmp6 ICMP6
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_ICMP6==1: Enable ICMPv6 (mandatory per RFC)
+ */
+#if !defined LWIP_ICMP6 || defined __DOXYGEN__
+#define LWIP_ICMP6                      LWIP_IPV6
+#endif
+
+/**
+ * LWIP_ICMP6_DATASIZE: bytes from original packet to send back in
+ * ICMPv6 error messages (0 = default of IP6_MIN_MTU_LENGTH)
+ * ATTENTION: RFC4443 section 2.4 says IP6_MIN_MTU_LENGTH is a MUST,
+ * so override this only if you absolutely have to!
+ */
+#if !defined LWIP_ICMP6_DATASIZE || defined __DOXYGEN__
+#define LWIP_ICMP6_DATASIZE             0
+#endif
+
+/**
+ * LWIP_ICMP6_HL: default hop limit for ICMPv6 messages
+ */
+#if !defined LWIP_ICMP6_HL || defined __DOXYGEN__
+#define LWIP_ICMP6_HL                   255
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_mld6 Multicast listener discovery
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_IPV6_MLD==1: Enable multicast listener discovery protocol.
+ * If LWIP_IPV6 is enabled but this setting is disabled, the MAC layer must
+ * indiscriminately pass all inbound IPv6 multicast traffic to lwIP.
+ */
+#if !defined LWIP_IPV6_MLD || defined __DOXYGEN__
+#define LWIP_IPV6_MLD                   LWIP_IPV6
+#endif
+
+/**
+ * MEMP_NUM_MLD6_GROUP: Max number of IPv6 multicast groups that can be joined.
+ * There must be enough groups so that each netif can join the solicited-node
+ * multicast group for each of its local addresses, plus one for MDNS if
+ * applicable, plus any number of groups to be joined on UDP sockets.
+ */
+#if !defined MEMP_NUM_MLD6_GROUP || defined __DOXYGEN__
+#define MEMP_NUM_MLD6_GROUP             4
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_nd6 Neighbor discovery
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address
+ * is being resolved.
+ */
+#if !defined LWIP_ND6_QUEUEING || defined __DOXYGEN__
+#define LWIP_ND6_QUEUEING               LWIP_IPV6
+#endif
+
+/**
+ * ESP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address
+ * is being resolved.
+ */
+#if !defined ESP_ND6_QUEUEING || defined __DOXYGEN__
+#define ESP_ND6_QUEUEING               LWIP_IPV6
+#endif
+
+/**
+ * MEMP_NUM_ND6_QUEUE: Max number of IPv6 packets to queue during MAC resolution.
+ */
+#if !defined MEMP_NUM_ND6_QUEUE || defined __DOXYGEN__
+#define MEMP_NUM_ND6_QUEUE              20
+#endif
+
+/**
+ * LWIP_ND6_NUM_NEIGHBORS: Number of entries in IPv6 neighbor cache
+ */
+#if !defined LWIP_ND6_NUM_NEIGHBORS || defined __DOXYGEN__
+#define LWIP_ND6_NUM_NEIGHBORS          10
+#endif
+
+/**
+ * LWIP_ND6_NUM_DESTINATIONS: number of entries in IPv6 destination cache
+ */
+#if !defined LWIP_ND6_NUM_DESTINATIONS || defined __DOXYGEN__
+#define LWIP_ND6_NUM_DESTINATIONS       10
+#endif
+
+/**
+ * LWIP_ND6_NUM_PREFIXES: number of entries in IPv6 on-link prefixes cache
+ */
+#if !defined LWIP_ND6_NUM_PREFIXES || defined __DOXYGEN__
+#define LWIP_ND6_NUM_PREFIXES           5
+#endif
+
+/**
+ * LWIP_ND6_NUM_ROUTERS: number of entries in IPv6 default router cache
+ */
+#if !defined LWIP_ND6_NUM_ROUTERS || defined __DOXYGEN__
+#define LWIP_ND6_NUM_ROUTERS            3
+#endif
+
+/**
+ * LWIP_ND6_MAX_MULTICAST_SOLICIT: max number of multicast solicit messages to send
+ * (neighbor solicit and router solicit)
+ */
+#if !defined LWIP_ND6_MAX_MULTICAST_SOLICIT || defined __DOXYGEN__
+#define LWIP_ND6_MAX_MULTICAST_SOLICIT  3
+#endif
+
+/**
+ * LWIP_ND6_MAX_UNICAST_SOLICIT: max number of unicast neighbor solicitation messages
+ * to send during neighbor reachability detection.
+ */
+#if !defined LWIP_ND6_MAX_UNICAST_SOLICIT || defined __DOXYGEN__
+#define LWIP_ND6_MAX_UNICAST_SOLICIT    3
+#endif
+
+/**
+ * Unused: See ND RFC (time in milliseconds).
+ */
+#if !defined LWIP_ND6_MAX_ANYCAST_DELAY_TIME || defined __DOXYGEN__
+#define LWIP_ND6_MAX_ANYCAST_DELAY_TIME 1000
+#endif
+
+/**
+ * Unused: See ND RFC
+ */
+#if !defined LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT || defined __DOXYGEN__
+#define LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT  3
+#endif
+
+/**
+ * LWIP_ND6_REACHABLE_TIME: default neighbor reachable time (in milliseconds).
+ * May be updated by router advertisement messages.
+ */
+#if !defined LWIP_ND6_REACHABLE_TIME || defined __DOXYGEN__
+#define LWIP_ND6_REACHABLE_TIME         30000
+#endif
+
+/**
+ * LWIP_ND6_RETRANS_TIMER: default retransmission timer for solicitation messages
+ */
+#if !defined LWIP_ND6_RETRANS_TIMER || defined __DOXYGEN__
+#define LWIP_ND6_RETRANS_TIMER          1000
+#endif
+
+/**
+ * LWIP_ND6_DELAY_FIRST_PROBE_TIME: Delay before first unicast neighbor solicitation
+ * message is sent, during neighbor reachability detection.
+ */
+#if !defined LWIP_ND6_DELAY_FIRST_PROBE_TIME || defined __DOXYGEN__
+#define LWIP_ND6_DELAY_FIRST_PROBE_TIME 5000
+#endif
+
+/**
+ * LWIP_ND6_ALLOW_RA_UPDATES==1: Allow Router Advertisement messages to update
+ * Reachable time and retransmission timers, and netif MTU.
+ */
+#if !defined LWIP_ND6_ALLOW_RA_UPDATES || defined __DOXYGEN__
+#define LWIP_ND6_ALLOW_RA_UPDATES       1
+#endif
+
+/**
+ * LWIP_ND6_TCP_REACHABILITY_HINTS==1: Allow TCP to provide Neighbor Discovery
+ * with reachability hints for connected destinations. This helps avoid sending
+ * unicast neighbor solicitation messages.
+ */
+#if !defined LWIP_ND6_TCP_REACHABILITY_HINTS || defined __DOXYGEN__
+#define LWIP_ND6_TCP_REACHABILITY_HINTS 1
+#endif
+
+/**
+ * LWIP_ND6_RDNSS_MAX_DNS_SERVERS > 0: Use IPv6 Router Advertisement Recursive
+ * DNS Server Option (as per RFC 6106) to copy a defined maximum number of DNS
+ * servers to the DNS module.
+ */
+#if !defined LWIP_ND6_RDNSS_MAX_DNS_SERVERS || defined __DOXYGEN__
+#define LWIP_ND6_RDNSS_MAX_DNS_SERVERS  0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_dhcpv6 DHCPv6
+ * @ingroup lwip_opts_ipv6
+ * @{
+ */
+/**
+ * LWIP_IPV6_DHCP6==1: enable DHCPv6 stateful/stateless address autoconfiguration.
+ */
+#if !defined LWIP_IPV6_DHCP6 || defined __DOXYGEN__
+#define LWIP_IPV6_DHCP6                 0
+#endif
+
+/**
+ * LWIP_IPV6_DHCP6_STATEFUL==1: enable DHCPv6 stateful address autoconfiguration.
+ * (not supported, yet!)
+ */
+#if !defined LWIP_IPV6_DHCP6_STATEFUL || defined __DOXYGEN__
+#define LWIP_IPV6_DHCP6_STATEFUL        0
+#endif
+
+/**
+ * LWIP_IPV6_DHCP6_STATELESS==1: enable DHCPv6 stateless address autoconfiguration.
+ */
+#if !defined LWIP_IPV6_DHCP6_STATELESS || defined __DOXYGEN__
+#define LWIP_IPV6_DHCP6_STATELESS       LWIP_IPV6_DHCP6
+#endif
+
+/**
+ * LWIP_DHCP6_GETS_NTP==1: Request NTP servers via DHCPv6. For each
+ * response packet, a callback is called, which has to be provided by the port:
+ * void dhcp6_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs);
+*/
+#if !defined LWIP_DHCP6_GET_NTP_SRV || defined __DOXYGEN__
+#define LWIP_DHCP6_GET_NTP_SRV          0
+#endif
+
+/**
+ * The maximum of NTP servers requested
+ */
+#if !defined LWIP_DHCP6_MAX_NTP_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP6_MAX_NTP_SERVERS      1
+#endif
+
+/**
+ * LWIP_DHCP6_MAX_DNS_SERVERS > 0: Request DNS servers via DHCPv6.
+ * DNS servers received in the response are passed to DNS via @ref dns_setserver()
+ * (up to the maximum limit defined here).
+ */
+#if !defined LWIP_DHCP6_MAX_DNS_SERVERS || defined __DOXYGEN__
+#define LWIP_DHCP6_MAX_DNS_SERVERS      DNS_MAX_SERVERS
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------------
+   ---------- Hook options ---------------
+   ---------------------------------------
+*/
+
+/**
+ * @defgroup lwip_opts_hooks Hooks
+ * @ingroup lwip_opts_infrastructure
+ * Hooks are undefined by default, define them to a function if you need them.
+ * @{
+ */
+
+/**
+ * LWIP_HOOK_FILENAME: Custom filename to \#include in files that provide hooks.
+ * Declare your hook function prototypes in there, you may also \#include all headers
+ * providing data types that are need in this file.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_FILENAME "path/to/my/lwip_hooks.h"
+#endif
+
+/**
+ * LWIP_HOOK_TCP_ISN:
+ * Hook for generation of the Initial Sequence Number (ISN) for a new TCP
+ * connection. The default lwIP ISN generation algorithm is very basic and may
+ * allow for TCP spoofing attacks. This hook provides the means to implement
+ * the standardized ISN generation algorithm from RFC 6528 (see contrib/adons/tcp_isn),
+ * or any other desired algorithm as a replacement.
+ * Called from tcp_connect() and tcp_listen_input() when an ISN is needed for
+ * a new TCP connection, if TCP support (@ref LWIP_TCP) is enabled.\n
+ * Signature:\code{.c}
+ * u32_t my_hook_tcp_isn(const ip_addr_t* local_ip, u16_t local_port, const ip_addr_t* remote_ip, u16_t remote_port);
+ * \endcode
+ * - it may be necessary to use "struct ip_addr" (ip4_addr, ip6_addr) instead of "ip_addr_t" in function declarations\n
+ * Arguments:
+ * - local_ip: pointer to the local IP address of the connection
+ * - local_port: local port number of the connection (host-byte order)
+ * - remote_ip: pointer to the remote IP address of the connection
+ * - remote_port: remote port number of the connection (host-byte order)\n
+ * Return value:
+ * - the 32-bit Initial Sequence Number to use for the new TCP connection.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_ISN(local_ip, local_port, remote_ip, remote_port)
+#endif
+
+/**
+ * LWIP_HOOK_TCP_INPACKET_PCB:
+ * Hook for intercepting incoming packets before they are passed to a pcb. This
+ * allows updating some state or even dropping a packet.
+ * Signature:\code{.c}
+ * err_t my_hook_tcp_inpkt(struct tcp_pcb *pcb, struct tcp_hdr *hdr, u16_t optlen, u16_t opt1len, u8_t *opt2, struct pbuf *p);
+ * \endcode
+ * Arguments:
+ * - pcb: tcp_pcb selected for input of this packet (ATTENTION: this may be
+ *        struct tcp_pcb_listen if pcb->state == LISTEN)
+ * - hdr: pointer to tcp header (ATTENTION: tcp options may not be in one piece!)
+ * - optlen: tcp option length
+ * - opt1len: tcp option length 1st part
+ * - opt2: if this is != NULL, tcp options are split among 2 pbufs. In that case,
+ *         options start at right after the tcp header ('(u8_t*)(hdr + 1)') for
+ *         the first 'opt1len' bytes and the rest starts at 'opt2'. opt2len can
+ *         be simply calculated: 'opt2len = optlen - opt1len;'
+ * - p: input packet, p->payload points to application data (that's why tcp hdr
+ *      and options are passed in seperately)
+ * Return value:
+ * - ERR_OK: continue input of this packet as normal
+ * - != ERR_OK: drop this packet for input (don't continue input processing)
+ *
+ * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
+ * state or any pcb lists) from this callback!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_INPACKET_PCB(pcb, hdr, optlen, opt1len, opt2, p)
+#endif
+
+/**
+ * LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH:
+ * Hook for increasing the size of the options allocated with a tcp header.
+ * Together with LWIP_HOOK_TCP_OUT_ADD_TCPOPTS, this can be used to add custom
+ * options to outgoing tcp segments.
+ * Signature:\code{.c}
+ * u8_t my_hook_tcp_out_tcpopt_length(const struct tcp_pcb *pcb, u8_t internal_option_length);
+ * \endcode
+ * Arguments:
+ * - pcb: tcp_pcb that transmits (ATTENTION: this may be NULL or
+ *        struct tcp_pcb_listen if pcb->state == LISTEN)
+ * - internal_option_length: tcp option length used by the stack internally
+ * Return value:
+ * - a number of bytes to allocate for tcp options (internal_option_length <= ret <= 40)
+ *
+ * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
+ * state or any pcb lists) from this callback!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH(pcb, internal_len)
+#endif
+
+/**
+ * LWIP_HOOK_TCP_OUT_ADD_TCPOPTS:
+ * Hook for adding custom options to outgoing tcp segments.
+ * Space for these custom options has to be reserved via LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH.
+ * Signature:\code{.c}
+ * u32_t *my_hook_tcp_out_add_tcpopts(struct pbuf *p, struct tcp_hdr *hdr, const struct tcp_pcb *pcb, u32_t *opts);
+ * \endcode
+ * Arguments:
+ * - p: output packet, p->payload pointing to tcp header, data follows
+ * - hdr: tcp header
+ * - pcb: tcp_pcb that transmits (ATTENTION: this may be NULL or
+ *        struct tcp_pcb_listen if pcb->state == LISTEN)
+ * - opts: pointer where to add the custom options (there may already be options
+ *         between the header and these)
+ * Return value:
+ * - pointer pointing directly after the inserted options
+ *
+ * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
+ * state or any pcb lists) from this callback!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_TCP_OUT_ADD_TCPOPTS(p, hdr, pcb, opts)
+#endif
+
+/**
+ * LWIP_HOOK_IP4_INPUT(pbuf, input_netif):
+ * Called from ip_input() (IPv4)
+ * Signature:\code{.c}
+ *   int my_hook(struct pbuf *pbuf, struct netif *input_netif);
+ * \endcode
+ * Arguments:
+ * - pbuf: received struct pbuf passed to ip_input()
+ * - input_netif: struct netif on which the packet has been received
+ * Return values:
+ * - 0: Hook has not consumed the packet, packet is processed as normal
+ * - != 0: Hook has consumed the packet.
+ * If the hook consumed the packet, 'pbuf' is in the responsibility of the hook
+ * (i.e. free it when done).
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_INPUT(pbuf, input_netif)
+#endif
+
+/**
+ * LWIP_HOOK_IP4_ROUTE(dest):
+ * Called from ip_route() (IPv4)
+ * Signature:\code{.c}
+ *   struct netif *my_hook(const ip4_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - dest: destination IPv4 address
+ * Returns values:
+ * - the destination netif
+ * - NULL if no destination netif is found. In that case, ip_route() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_ROUTE()
+#endif
+
+/**
+ * LWIP_HOOK_IP4_ROUTE_SRC(src, dest):
+ * Source-based routing for IPv4 - called from ip_route() (IPv4)
+ * Signature:\code{.c}
+ *   struct netif *my_hook(const ip4_addr_t *src, const ip4_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - src: local/source IPv4 address
+ * - dest: destination IPv4 address
+ * Returns values:
+ * - the destination netif
+ * - NULL if no destination netif is found. In that case, ip_route() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_ROUTE_SRC(src, dest)
+#endif
+
+/**
+ * LWIP_HOOK_IP4_CANFORWARD(src, dest):
+ * Check if an IPv4 can be forwarded - called from:
+ * ip4_input() -> ip4_forward() -> ip4_canforward() (IPv4)
+ * - source address is available via ip4_current_src_addr()
+ * - calling an output function in this context (e.g. multicast router) is allowed
+ * Signature:\code{.c}
+ *   int my_hook(struct pbuf *p, u32_t dest_addr_hostorder);
+ * \endcode
+ * Arguments:
+ * - p: packet to forward
+ * - dest: destination IPv4 address
+ * Returns values:
+ * - 1: forward
+ * - 0: don't forward
+ * - -1: no decision. In that case, ip4_canforward() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP4_CANFORWARD(src, dest)
+#endif
+
+/**
+ * LWIP_HOOK_ETHARP_GET_GW(netif, dest):
+ * Called from etharp_output() (IPv4)
+ * Signature:\code{.c}
+ *   const ip4_addr_t *my_hook(struct netif *netif, const ip4_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - netif: the netif used for sending
+ * - dest: the destination IPv4 address
+ * Return values:
+ * - the IPv4 address of the gateway to handle the specified destination IPv4 address
+ * - NULL, in which case the netif's default gateway is used
+ *
+ * The returned address MUST be directly reachable on the specified netif!
+ * This function is meant to implement advanced IPv4 routing together with
+ * LWIP_HOOK_IP4_ROUTE(). The actual routing/gateway table implementation is
+ * not part of lwIP but can e.g. be hidden in the netif's state argument.
+*/
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_ETHARP_GET_GW(netif, dest)
+#endif
+
+/**
+ * LWIP_HOOK_IP6_INPUT(pbuf, input_netif):
+ * Called from ip6_input() (IPv6)
+ * Signature:\code{.c}
+ *   int my_hook(struct pbuf *pbuf, struct netif *input_netif);
+ * \endcode
+ * Arguments:
+ * - pbuf: received struct pbuf passed to ip6_input()
+ * - input_netif: struct netif on which the packet has been received
+ * Return values:
+ * - 0: Hook has not consumed the packet, packet is processed as normal
+ * - != 0: Hook has consumed the packet.
+ * If the hook consumed the packet, 'pbuf' is in the responsibility of the hook
+ * (i.e. free it when done).
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP6_INPUT(pbuf, input_netif)
+#endif
+
+/**
+ * LWIP_HOOK_IP6_ROUTE(src, dest):
+ * Called from ip_route() (IPv6)
+ * Signature:\code{.c}
+ *   struct netif *my_hook(const ip6_addr_t *dest, const ip6_addr_t *src);
+ * \endcode
+ * Arguments:
+ * - src: source IPv6 address
+ * - dest: destination IPv6 address
+ * Return values:
+ * - the destination netif
+ * - NULL if no destination netif is found. In that case, ip6_route() continues as normal.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP6_ROUTE(src, dest)
+#endif
+
+/**
+ * LWIP_HOOK_ND6_GET_GW(netif, dest):
+ * Called from nd6_get_next_hop_entry() (IPv6)
+ * Signature:\code{.c}
+ *   const ip6_addr_t *my_hook(struct netif *netif, const ip6_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - netif: the netif used for sending
+ * - dest: the destination IPv6 address
+ * Return values:
+ * - the IPv6 address of the next hop to handle the specified destination IPv6 address
+ * - NULL, in which case a NDP-discovered router is used instead
+ *
+ * The returned address MUST be directly reachable on the specified netif!
+ * This function is meant to implement advanced IPv6 routing together with
+ * LWIP_HOOK_IP6_ROUTE(). The actual routing/gateway table implementation is
+ * not part of lwIP but can e.g. be hidden in the netif's state argument.
+*/
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_ND6_GET_GW(netif, dest)
+#endif
+
+/**
+ * LWIP_HOOK_IP6_SELECT_SRC_ADDR(netif, dest):
+ * Called from ip6_select_source_address() (IPv6)
+ * Signature:\code{.c}
+ *   const ip6_addr_t *my_hook(struct netif *netif, const ip6_addr_t *dest);
+ * \endcode
+ * Arguments:
+ * - netif: the netif used for selecting
+ * - dest: the destination IPv6 address
+ * Return values:
+ * - the preferred source IPv6 address of the specified destination IPv6 address
+ * - NULL, in which case none source address is preferred by the hook, lwip
+ *         will determine a source address based RFC 6724.
+ *
+ * The returned address MUST be on the specified netif!
+ * This function is meant to implement advanced IPv6 source address selection with
+ * LWIP_HOOK_IP6_SELECT_SRC_ADDR().
+*/
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_IP6_SELECT_SRC_ADDR(netif, dest)
+#endif
+
+/**
+ * LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr):
+ * Called from ethernet_input() if VLAN support is enabled
+ * Signature:\code{.c}
+ *   int my_hook(struct netif *netif, struct eth_hdr *eth_hdr, struct eth_vlan_hdr *vlan_hdr);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif on which the packet has been received
+ * - eth_hdr: struct eth_hdr of the packet
+ * - vlan_hdr: struct eth_vlan_hdr of the packet
+ * Return values:
+ * - 0: Packet must be dropped.
+ * - != 0: Packet must be accepted.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr)
+#endif
+
+/**
+ * LWIP_HOOK_VLAN_SET:
+ * Hook can be used to set prio_vid field of vlan_hdr. If you need to store data
+ * on per-netif basis to implement this callback, see @ref netif_cd.
+ * Called from ethernet_output() if VLAN support (@ref ETHARP_SUPPORT_VLAN) is enabled.\n
+ * Signature:\code{.c}
+ *   s32_t my_hook_vlan_set(struct netif* netif, struct pbuf* pbuf, const struct eth_addr* src, const struct eth_addr* dst, u16_t eth_type);\n
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - p: struct pbuf packet to be sent
+ * - src: source eth address
+ * - dst: destination eth address
+ * - eth_type: ethernet type to packet to be sent\n
+ * 
+ * 
+ * Return values:
+ * - &lt;0: Packet shall not contain VLAN header.
+ * - 0 &lt;= return value &lt;= 0xFFFF: Packet shall contain VLAN header. Return value is prio_vid in host byte order.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_VLAN_SET(netif, p, src, dst, eth_type)
+#endif
+
+/**
+ * LWIP_HOOK_MEMP_AVAILABLE(memp_t_type):
+ * Called from memp_free() when a memp pool was empty and an item is now available
+ * Signature:\code{.c}
+ *   void my_hook(memp_t type);
+ * \endcode
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_MEMP_AVAILABLE(memp_t_type)
+#endif
+
+/**
+ * LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(pbuf, netif):
+ * Called from ethernet_input() when an unknown eth type is encountered.
+ * Signature:\code{.c}
+ *   err_t my_hook(struct pbuf* pbuf, struct netif* netif);
+ * \endcode
+ * Arguments:
+ * - p: rx packet with unknown eth type
+ * - netif: netif on which the packet has been received
+ * Return values:
+ * - ERR_OK if packet is accepted (hook function now owns the pbuf)
+ * - any error code otherwise (pbuf is freed)
+ *
+ * Payload points to ethernet header!
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(pbuf, netif)
+#endif
+
+/**
+ * LWIP_HOOK_DHCP_APPEND_OPTIONS(netif, dhcp, state, msg, msg_type, options_len_ptr):
+ * Called from various dhcp functions when sending a DHCP message.
+ * This hook is called just before the DHCP message trailer is added, so the
+ * options are at the end of a DHCP message.
+ * Signature:\code{.c}
+ *   void my_hook(struct netif *netif, struct dhcp *dhcp, u8_t state, struct dhcp_msg *msg,
+ *                u8_t msg_type, u16_t *options_len_ptr);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - dhcp: struct dhcp on that netif
+ * - state: current dhcp state (dhcp_state_enum_t as an u8_t)
+ * - msg: struct dhcp_msg that will be sent
+ * - msg_type: dhcp message type to be sent (u8_t)
+ * - options_len_ptr: pointer to the current length of options in the dhcp_msg "msg"
+ *                    (must be increased when options are added!)
+ *
+ * Options need to appended like this:
+ *   LWIP_ASSERT("dhcp option overflow", *options_len_ptr + option_len + 2 <= DHCP_OPTIONS_LEN);
+ *   msg->options[(*options_len_ptr)++] = &lt;option_number&gt;;
+ *   msg->options[(*options_len_ptr)++] = &lt;option_len&gt;;
+ *   msg->options[(*options_len_ptr)++] = &lt;option_bytes&gt;;
+ *   [...]
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_DHCP_APPEND_OPTIONS(netif, dhcp, state, msg, msg_type, options_len_ptr)
+#endif
+
+/**
+ * LWIP_HOOK_DHCP_PARSE_OPTION(netif, dhcp, state, msg, msg_type, option, len, pbuf, option_value_offset):
+ * Called from dhcp_parse_reply when receiving a DHCP message.
+ * This hook is called for every option in the received message that is not handled internally.
+ * Signature:\code{.c}
+ *   void my_hook(struct netif *netif, struct dhcp *dhcp, u8_t state, struct dhcp_msg *msg,
+ *                u8_t msg_type, u8_t option, u8_t option_len, struct pbuf *pbuf, u16_t option_value_offset);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - dhcp: struct dhcp on that netif
+ * - state: current dhcp state (dhcp_state_enum_t as an u8_t)
+ * - msg: struct dhcp_msg that was received
+ * - msg_type: dhcp message type received (u8_t, ATTENTION: only valid after
+ *             the message type option has been parsed!)
+ * - option: option value (u8_t)
+ * - len: option data length (u8_t)
+ * - pbuf: pbuf where option data is contained
+ * - option_value_offset: offset in pbuf where option data begins
+ *
+ * A nice way to get the option contents is pbuf_get_contiguous():
+ *  u8_t buf[32];
+ *  u8_t *ptr = (u8_t*)pbuf_get_contiguous(p, buf, sizeof(buf), LWIP_MIN(option_len, sizeof(buf)), offset);
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_DHCP_PARSE_OPTION(netif, dhcp, state, msg, msg_type, option, len, pbuf, offset)
+#endif
+
+/**
+ * LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, state, msg, msg_type, options_len_ptr, max_len):
+ * Called from various dhcp6 functions when sending a DHCP6 message.
+ * This hook is called just before the DHCP6 message is sent, so the
+ * options are at the end of a DHCP6 message.
+ * Signature:\code{.c}
+ *   void my_hook(struct netif *netif, struct dhcp6 *dhcp, u8_t state, struct dhcp6_msg *msg,
+ *                u8_t msg_type, u16_t *options_len_ptr);
+ * \endcode
+ * Arguments:
+ * - netif: struct netif that the packet will be sent through
+ * - dhcp6: struct dhcp6 on that netif
+ * - state: current dhcp6 state (dhcp6_state_enum_t as an u8_t)
+ * - msg: struct dhcp6_msg that will be sent
+ * - msg_type: dhcp6 message type to be sent (u8_t)
+ * - options_len_ptr: pointer to the current length of options in the dhcp6_msg "msg"
+ *                    (must be increased when options are added!)
+ *
+ * Options need to appended like this:
+ *   u8_t *options = (u8_t *)(msg + 1);
+ *   LWIP_ASSERT("dhcp option overflow", sizeof(struct dhcp6_msg) + *options_len_ptr + newoptlen <= max_len);
+ *   options[(*options_len_ptr)++] = &lt;option_data&gt;;
+ *   [...]
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, state, msg, msg_type, options_len_ptr, max_len)
+#endif
+
+/**
+ * LWIP_HOOK_SOCKETS_SETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+ * Called from socket API to implement setsockopt() for options not provided by lwIP.
+ * Core lock is held when this hook is called.
+ * Signature:\code{.c}
+ *   int my_hook(int s, struct lwip_sock *sock, int level, int optname, const void *optval, socklen_t optlen, int *err)
+ * \endcode
+ * Arguments:
+ * - s: socket file descriptor
+ * - sock: internal socket descriptor (see lwip/priv/sockets_priv.h)
+ * - level: protocol level at which the option resides
+ * - optname: option to set
+ * - optval: value to set
+ * - optlen: size of optval
+ * - err: output error
+ * Return values:
+ * - 0: Hook has not consumed the option, code continues as normal (to internal options)
+ * - != 0: Hook has consumed the option, 'err' is returned
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_SOCKETS_SETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+#endif
+
+/**
+ * LWIP_HOOK_SOCKETS_GETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+ * Called from socket API to implement getsockopt() for options not provided by lwIP.
+ * Core lock is held when this hook is called.
+ * Signature:\code{.c}
+ *   int my_hook(int s, struct lwip_sock *sock, int level, int optname, void *optval, socklen_t *optlen, int *err)
+ * \endcode
+ * Arguments:
+ * - s: socket file descriptor
+ * - sock: internal socket descriptor (see lwip/priv/sockets_priv.h)
+ * - level: protocol level at which the option resides
+ * - optname: option to get
+ * - optval: value to get
+ * - optlen: size of optval
+ * - err: output error
+ * Return values:
+ * - 0: Hook has not consumed the option, code continues as normal (to internal options)
+ * - != 0: Hook has consumed the option, 'err' is returned
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_SOCKETS_GETSOCKOPT(s, sock, level, optname, optval, optlen, err)
+#endif
+
+/**
+ * LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE(name, addr, addrtype, err)
+ * Called from netconn APIs (not usable with callback apps) allowing an
+ * external DNS resolver (which uses sequential API) to handle the query.
+ * Signature:\code{.c}
+ *   int my_hook(const char *name, ip_addr_t *addr, u8_t addrtype, err_t *err)
+ * \endcode
+ * Arguments:
+ * - name: hostname to resolve
+ * - addr: output host address
+ * - addrtype: type of address to query
+ * - err: output error
+ * Return values:
+ * - 0: Hook has not consumed hostname query, query continues into DNS module
+ * - != 0: Hook has consumed the query
+ *
+ * err must also be checked to determine if the hook consumed the query, but
+ * the query failed
+ */
+#ifdef __DOXYGEN__
+#define LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE(name, addr, addrtype, err)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------------
+   ---------- Debugging options ----------
+   ---------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_debugmsg Debug messages
+ * @ingroup lwip_opts_debug
+ * @{
+ */
+/**
+ * LWIP_DBG_MIN_LEVEL: After masking, the value of the debug is
+ * compared against this value. If it is smaller, then debugging
+ * messages are written.
+ * @see debugging_levels
+ */
+#if !defined LWIP_DBG_MIN_LEVEL || defined __DOXYGEN__
+#define LWIP_DBG_MIN_LEVEL              LWIP_DBG_LEVEL_ALL
+#endif
+
+/**
+ * LWIP_DBG_TYPES_ON: A mask that can be used to globally enable/disable
+ * debug messages of certain types.
+ * @see debugging_levels
+ */
+#if !defined LWIP_DBG_TYPES_ON || defined __DOXYGEN__
+#define LWIP_DBG_TYPES_ON               LWIP_DBG_ON
+#endif
+
+/**
+ * ETHARP_DEBUG: Enable debugging in etharp.c.
+ */
+#if !defined ETHARP_DEBUG || defined __DOXYGEN__
+#define ETHARP_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * NETIF_DEBUG: Enable debugging in netif.c.
+ */
+#if !defined NETIF_DEBUG || defined __DOXYGEN__
+#define NETIF_DEBUG                     LWIP_DBG_OFF
+#endif
+
+/**
+ * PBUF_DEBUG: Enable debugging in pbuf.c.
+ */
+#if !defined PBUF_DEBUG || defined __DOXYGEN__
+#define PBUF_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * API_LIB_DEBUG: Enable debugging in api_lib.c.
+ */
+#if !defined API_LIB_DEBUG || defined __DOXYGEN__
+#define API_LIB_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * API_MSG_DEBUG: Enable debugging in api_msg.c.
+ */
+#if !defined API_MSG_DEBUG || defined __DOXYGEN__
+#define API_MSG_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * SOCKETS_DEBUG: Enable debugging in sockets.c.
+ */
+#if !defined SOCKETS_DEBUG || defined __DOXYGEN__
+#define SOCKETS_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * ICMP_DEBUG: Enable debugging in icmp.c.
+ */
+#if !defined ICMP_DEBUG || defined __DOXYGEN__
+#define ICMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * IGMP_DEBUG: Enable debugging in igmp.c.
+ */
+#if !defined IGMP_DEBUG || defined __DOXYGEN__
+#define IGMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * INET_DEBUG: Enable debugging in inet.c.
+ */
+#if !defined INET_DEBUG || defined __DOXYGEN__
+#define INET_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * IP_DEBUG: Enable debugging for IP.
+ */
+#if !defined IP_DEBUG || defined __DOXYGEN__
+#define IP_DEBUG                        LWIP_DBG_OFF
+#endif
+
+/**
+ * IP_REASS_DEBUG: Enable debugging in ip_frag.c for both frag & reass.
+ */
+#if !defined IP_REASS_DEBUG || defined __DOXYGEN__
+#define IP_REASS_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * RAW_DEBUG: Enable debugging in raw.c.
+ */
+#if !defined RAW_DEBUG || defined __DOXYGEN__
+#define RAW_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * MEM_DEBUG: Enable debugging in mem.c.
+ */
+#if !defined MEM_DEBUG || defined __DOXYGEN__
+#define MEM_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * MEMP_DEBUG: Enable debugging in memp.c.
+ */
+#if !defined MEMP_DEBUG || defined __DOXYGEN__
+#define MEMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * SYS_DEBUG: Enable debugging in sys.c.
+ */
+#if !defined SYS_DEBUG || defined __DOXYGEN__
+#define SYS_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TIMERS_DEBUG: Enable debugging in timers.c.
+ */
+#if !defined TIMERS_DEBUG || defined __DOXYGEN__
+#define TIMERS_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_DEBUG: Enable debugging for TCP.
+ */
+#if !defined TCP_DEBUG || defined __DOXYGEN__
+#define TCP_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_INPUT_DEBUG: Enable debugging in tcp_in.c for incoming debug.
+ */
+#if !defined TCP_INPUT_DEBUG || defined __DOXYGEN__
+#define TCP_INPUT_DEBUG                 LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_FR_DEBUG: Enable debugging in tcp_in.c for fast retransmit.
+ */
+#if !defined TCP_FR_DEBUG || defined __DOXYGEN__
+#define TCP_FR_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_RTO_DEBUG: Enable debugging in TCP for retransmit
+ * timeout.
+ */
+#if !defined TCP_RTO_DEBUG || defined __DOXYGEN__
+#define TCP_RTO_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_CWND_DEBUG: Enable debugging for TCP congestion window.
+ */
+#if !defined TCP_CWND_DEBUG || defined __DOXYGEN__
+#define TCP_CWND_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_WND_DEBUG: Enable debugging in tcp_in.c for window updating.
+ */
+#if !defined TCP_WND_DEBUG || defined __DOXYGEN__
+#define TCP_WND_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_OUTPUT_DEBUG: Enable debugging in tcp_out.c output functions.
+ */
+#if !defined TCP_OUTPUT_DEBUG || defined __DOXYGEN__
+#define TCP_OUTPUT_DEBUG                LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_RST_DEBUG: Enable debugging for TCP with the RST message.
+ */
+#if !defined TCP_RST_DEBUG || defined __DOXYGEN__
+#define TCP_RST_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_QLEN_DEBUG: Enable debugging for TCP queue lengths.
+ */
+#if !defined TCP_QLEN_DEBUG || defined __DOXYGEN__
+#define TCP_QLEN_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * UDP_DEBUG: Enable debugging in UDP.
+ */
+#if !defined UDP_DEBUG || defined __DOXYGEN__
+#define UDP_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TCPIP_DEBUG: Enable debugging in tcpip.c.
+ */
+#if !defined TCPIP_DEBUG || defined __DOXYGEN__
+#define TCPIP_DEBUG                     LWIP_DBG_OFF
+#endif
+
+/**
+ * SLIP_DEBUG: Enable debugging in slipif.c.
+ */
+#if !defined SLIP_DEBUG || defined __DOXYGEN__
+#define SLIP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * DHCP_DEBUG: Enable debugging in dhcp.c.
+ */
+#if !defined DHCP_DEBUG || defined __DOXYGEN__
+#define DHCP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * AUTOIP_DEBUG: Enable debugging in autoip.c.
+ */
+#if !defined AUTOIP_DEBUG || defined __DOXYGEN__
+#define AUTOIP_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * DNS_DEBUG: Enable debugging for DNS.
+ */
+#if !defined DNS_DEBUG || defined __DOXYGEN__
+#define DNS_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * IP6_DEBUG: Enable debugging for IPv6.
+ */
+#if !defined IP6_DEBUG || defined __DOXYGEN__
+#define IP6_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * DHCP6_DEBUG: Enable debugging in dhcp6.c.
+ */
+#if !defined DHCP6_DEBUG || defined __DOXYGEN__
+#define DHCP6_DEBUG                     LWIP_DBG_OFF
+#endif
+/**
+ * @}
+ */
+
+/**
+ * LWIP_TESTMODE: Changes to make unit test possible
+ */
+#if !defined LWIP_TESTMODE
+#define LWIP_TESTMODE                   0
+#endif
+
+/**
+ * NAPT_DEBUG: Enable debugging for NAPT.
+ */
+#ifndef NAPT_DEBUG
+#define NAPT_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/*
+   --------------------------------------------------
+   ---------- Performance tracking options ----------
+   --------------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_perf Performance
+ * @ingroup lwip_opts_debug
+ * @{
+ */
+/**
+ * LWIP_PERF: Enable performance testing for lwIP
+ * (if enabled, arch/perf.h is included)
+ */
+#if !defined LWIP_PERF || defined __DOXYGEN__
+#define LWIP_PERF                       0
+#endif
+/**
+ * @}
+ */
+
+#endif /* LWIP_HDR_OPT_H */
diff --git a/arduinoIDE_esp32_boards/boards.local.txt b/arduinoIDE_esp32_boards/boards.local.txt
index c95e8c912e27f004f6ef7e9d44a17255f2ee9d5f..f16e2a4f34143dde95b877ede6dc8b17dde88d63 100644
--- a/arduinoIDE_esp32_boards/boards.local.txt
+++ b/arduinoIDE_esp32_boards/boards.local.txt
@@ -41,7 +41,6 @@ neOSensor.build.boot=dio
 
 # onboard LED2 (except on ESP32 38pins we don't use)
 # DISABLE_SSL: flag to disable SSL comms.
-# DISABLE_ADC_CAL: flag to disable esp32 ADC advanced calibration
 # DISABLE_AP_PASSWD: flag to disable access point password (i.e free to connect to)
 # [apr.21] MQTT specs sets through API (buffer_size, timeout ...)
 neOSensor.build.defines=-DNEOSENSOR_BOARD -DDISABLE_SSL -DDISABLE_AP_PASSWD
@@ -124,7 +123,6 @@ neOSensor-airquality.build.flash_freq=80m
 neOSensor-airquality.build.boot=dio
 neOSensor-airquality.build.partitions=default
 # [aug.20] for tests, we enable LED2 and we disable SSL
-# DISABLE_ADC_CAL: flag to disable esp32 ADC advanced calibration
 # DISABLE_AP_PASSWD: flag to disable access point password (i.e free to connect to)
 #neOSensor-airquality.build.defines=-DNEOSENSOR_AIRQUALITY_BOARD -DMQTT_KEEPALIVE=60 -DMQTT_SOCKET_TIMEOUT=60 -DMQTT_MAX_PACKET_SIZE=256 -DLED=2 -DDISABLE_SSL -DDISABLE_AP_PASSWD
 neOSensor-airquality.build.defines=-DNEOSENSOR_AIRQUALITY_BOARD -DMQTT_KEEPALIVE=60 -DMQTT_SOCKET_TIMEOUT=60 -DMQTT_MAX_PACKET_SIZE=256 -DDISABLE_I2C -DDISABLE_SSL -DDISABLE_AP_PASSWD
diff --git a/arduinoIDE_esp32_boards/liblwip.a b/arduinoIDE_esp32_boards/liblwip.a
deleted file mode 100644
index 21fde8173ea5b5a2285b8993028a15826a03a739..0000000000000000000000000000000000000000
Binary files a/arduinoIDE_esp32_boards/liblwip.a and /dev/null differ
diff --git a/arduinoIDE_esp32_boards/liblwip.a b/arduinoIDE_esp32_boards/liblwip.a
new file mode 120000
index 0000000000000000000000000000000000000000..58751928cbbb7eee1242885cadf87a3c46fd62d7
--- /dev/null
+++ b/arduinoIDE_esp32_boards/liblwip.a
@@ -0,0 +1 @@
+2.0.17/liblwip.a
\ No newline at end of file
diff --git a/arduinoIDE_esp32_boards/local_deploy.sh b/arduinoIDE_esp32_boards/local_deploy.sh
index 25c1694b38aaceee699df9d874118c2d88dbfcbc..89ab683bc63f235e1e6714e84f2f30611c40b3ec 100755
--- a/arduinoIDE_esp32_boards/local_deploy.sh
+++ b/arduinoIDE_esp32_boards/local_deploy.sh
@@ -4,6 +4,7 @@
 #
 # Deploying specific settings for board
 #
+# F.Thiebolt    jul.24  updated for ESP32 IDF new dirs architecture
 # F.Thiebolt    may.23  updated comments
 # F.Thiebolt    aug.22  update for 2.0.4 sdk with esp32 target only
 #                       i.e does not apply to esp32 derivatives like esp32c3
@@ -18,19 +19,40 @@
 
 echo -e "\t[addon] Copying additional libraries/headers files ..."
 
+_done=0
 
 #
 # copying lwip + header(s)
-cp -af opt.h $1/esp32/include/lwip/lwip/src/include/lwip/
-[ $? -ne 0 ] && { echo -e "\n###ERROR copying some files to '$1/esp32/include/lwip/lwip/src/include/lwip/'" >&2; exit 1; }
-chmod a+r $1/esp32/include/lwip/lwip/src/include/lwip/opt.h
-
-cp -af *.a $1/esp32/lib/
-[ $? -ne 0 ] && { echo -e "\n###ERROR copying some files to '$1/esp32/lib/'" >&2; exit 1; }
-chmod a+r $1/esp32/lib/*.a
-
-echo -e "\t[addon] successfully copied lwip+headers :)"
 
+# [header] opt.h
+_localFile="opt.h"
+if [ -f ${_localFile} ]; then
+    # find file in hierarchy and replace it
+    _target=$(find $1 -name ${_localFile})
+    #_target="$1/include/lwip/lwip/src/include/lwip/${_localFile}"
+    [ -f ${_target} ] || { echo -e "\n###ERROR: unable to find file '${_target}' ... aborting" >&2; exit 1; }
+    mv ${_target} ${_target}.orig
+    cp -af -L ${_localFile} ${_target}
+    [ $? -ne 0 ] && { echo -e "\n###ERROR copying some files to '${_target}'" >&2; exit 1; }
+    _done=1
+    chmod a+r ${_target}
+fi
+
+# [libraries]
+_targetLibDir="$1/lib"
+[ -d ${_targetLibDir} ] || { echo -e "\n###ERROR: unable to find dir '${_targetLibDir}' ... aborting" >&2; exit 1; }
+for _localLib in $(ls *.a 2>/dev/null); do
+    cp -af -L ${_localLib} ${_targetLibDir}
+    [ $? -ne 0 ] && { echo -e "\n###ERROR copying file '${_localLib}' to '${_targetLibDir}'" >&2; exit 1; }
+    _done=1
+    chmod a+r ${_targetLibDir}/${_localLib}
+done
+
+if [ ${_done} -eq 1 ]; then
+    echo -e "\t[addon] successfully copied lwip+headers :)"
+else
+    echo -e "\t[addon] not found any additional files ..."
+fi
 
 
 #set +x
diff --git a/arduinoIDE_esp32_boards/opt.h b/arduinoIDE_esp32_boards/opt.h
deleted file mode 100644
index df302dad33c5d2e8d731cb74b98b7e49ad2b509b..0000000000000000000000000000000000000000
--- a/arduinoIDE_esp32_boards/opt.h
+++ /dev/null
@@ -1,3588 +0,0 @@
-/**
- * @file
- *
- * lwIP Options Configuration
- */
-
-/*
- * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- *    derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
- * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
- * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- *
- * This file is part of the lwIP TCP/IP stack.
- *
- * Author: Adam Dunkels <adam@sics.se>
- *
- */
-
-/*
- * NOTE: || defined __DOXYGEN__ is a workaround for doxygen bug -
- * without this, doxygen does not see the actual #define
- */
-
-#if !defined LWIP_HDR_OPT_H
-#define LWIP_HDR_OPT_H
-
-/*
- * Include user defined options first. Anything not defined in these files
- * will be set to standard values. Override anything you don't like!
- */
-#include "lwipopts.h"
-#include "lwip/debug.h"
-
-/**
- * @defgroup lwip_opts Options (lwipopts.h)
- * @ingroup lwip
- *
- * @defgroup lwip_opts_debug Debugging
- * @ingroup lwip_opts
- *
- * @defgroup lwip_opts_infrastructure Infrastructure
- * @ingroup lwip_opts
- *
- * @defgroup lwip_opts_callback Callback-style APIs
- * @ingroup lwip_opts
- *
- * @defgroup lwip_opts_threadsafe_apis Thread-safe APIs
- * @ingroup lwip_opts
- */
-
- /*
-   ------------------------------------
-   -------------- NO SYS --------------
-   ------------------------------------
-*/
-/**
- * @defgroup lwip_opts_nosys NO_SYS
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * NO_SYS==1: Use lwIP without OS-awareness (no thread, semaphores, mutexes or
- * mboxes). This means threaded APIs cannot be used (socket, netconn,
- * i.e. everything in the 'api' folder), only the callback-style raw API is
- * available (and you have to watch out for yourself that you don't access
- * lwIP functions/structures from more than one context at a time!)
- */
-#if !defined NO_SYS || defined __DOXYGEN__
-#define NO_SYS                          0
-#endif
-/**
- * @}
- */
-
-/**
- * @defgroup lwip_opts_timers Timers
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * LWIP_TIMERS==0: Drop support for sys_timeout and lwip-internal cyclic timers.
- * (the array of lwip-internal cyclic timers is still provided)
- * (check NO_SYS_NO_TIMERS for compatibility to old versions)
- */
-#if !defined LWIP_TIMERS || defined __DOXYGEN__
-#ifdef NO_SYS_NO_TIMERS
-#define LWIP_TIMERS                     (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS))
-#else
-#define LWIP_TIMERS                     1
-#endif
-#endif
-
-/**
- * LWIP_TIMERS_CUSTOM==1: Provide your own timer implementation.
- * Function prototypes in timeouts.h and the array of lwip-internal cyclic timers
- * are still included, but the implementation is not. The following functions
- * will be required: sys_timeouts_init(), sys_timeout(), sys_untimeout(),
- *                   sys_timeouts_mbox_fetch()
- */
-#if !defined LWIP_TIMERS_CUSTOM || defined __DOXYGEN__
-#define LWIP_TIMERS_CUSTOM              0
-#endif
-/**
- * @}
- */
-
-/**
- * @defgroup lwip_opts_memcpy memcpy
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * MEMCPY: override this if you have a faster implementation at hand than the
- * one included in your C library
- */
-#if !defined MEMCPY || defined __DOXYGEN__
-#define MEMCPY(dst,src,len)             memcpy(dst,src,len)
-#endif
-
-/**
- * SMEMCPY: override this with care! Some compilers (e.g. gcc) can inline a
- * call to memcpy() if the length is known at compile time and is small.
- */
-#if !defined SMEMCPY || defined __DOXYGEN__
-#define SMEMCPY(dst,src,len)            memcpy(dst,src,len)
-#endif
-
-/**
- * MEMMOVE: override this if you have a faster implementation at hand than the
- * one included in your C library.  lwIP currently uses MEMMOVE only when IPv6
- * fragmentation support is enabled.
- */
-#if !defined MEMMOVE || defined __DOXYGEN__
-#define MEMMOVE(dst,src,len)            memmove(dst,src,len)
-#endif
-/**
- * @}
- */
-
-/*
-   ------------------------------------
-   ----------- Core locking -----------
-   ------------------------------------
-*/
-/**
- * @defgroup lwip_opts_lock Core locking and MPU
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * LWIP_MPU_COMPATIBLE: enables special memory management mechanism
- * which makes lwip able to work on MPU (Memory Protection Unit) system
- * by not passing stack-pointers to other threads
- * (this decreases performance as memory is allocated from pools instead
- * of keeping it on the stack)
- */
-#if !defined LWIP_MPU_COMPATIBLE || defined __DOXYGEN__
-#define LWIP_MPU_COMPATIBLE             0
-#endif
-
-/**
- * LWIP_TCPIP_CORE_LOCKING
- * Creates a global mutex that is held during TCPIP thread operations.
- * Can be locked by client code to perform lwIP operations without changing
- * into TCPIP thread using callbacks. See LOCK_TCPIP_CORE() and
- * UNLOCK_TCPIP_CORE().
- * Your system should provide mutexes supporting priority inversion to use this.
- */
-#if !defined LWIP_TCPIP_CORE_LOCKING || defined __DOXYGEN__
-#define LWIP_TCPIP_CORE_LOCKING         1
-#endif
-
-/**
- * LWIP_TCPIP_CORE_LOCKING_INPUT: when LWIP_TCPIP_CORE_LOCKING is enabled,
- * this lets tcpip_input() grab the mutex for input packets as well,
- * instead of allocating a message and passing it to tcpip_thread.
- *
- * ATTENTION: this does not work when tcpip_input() is called from
- * interrupt context!
- */
-#if !defined LWIP_TCPIP_CORE_LOCKING_INPUT || defined __DOXYGEN__
-#define LWIP_TCPIP_CORE_LOCKING_INPUT   0
-#endif
-
-/**
- * SYS_LIGHTWEIGHT_PROT==1: enable inter-task protection (and task-vs-interrupt
- * protection) for certain critical regions during buffer allocation, deallocation
- * and memory allocation and deallocation.
- * ATTENTION: This is required when using lwIP from more than one context! If
- * you disable this, you must be sure what you are doing!
- */
-#if !defined SYS_LIGHTWEIGHT_PROT || defined __DOXYGEN__
-#define SYS_LIGHTWEIGHT_PROT            1
-#endif
-
-/**
- * Macro/function to check whether lwIP's threading/locking
- * requirements are satisfied during current function call.
- * This macro usually calls a function that is implemented in the OS-dependent
- * sys layer and performs the following checks:
- * - Not in ISR (this should be checked for NO_SYS==1, too!)
- * - If @ref LWIP_TCPIP_CORE_LOCKING = 1: TCPIP core lock is held
- * - If @ref LWIP_TCPIP_CORE_LOCKING = 0: function is called from TCPIP thread
- * @see @ref multithreading
- */
-#if !defined LWIP_ASSERT_CORE_LOCKED || defined __DOXYGEN__
-#define LWIP_ASSERT_CORE_LOCKED()
-#endif
-
-/**
- * Called as first thing in the lwIP TCPIP thread. Can be used in conjunction
- * with @ref LWIP_ASSERT_CORE_LOCKED to check core locking.
- * @see @ref multithreading
- */
-#if !defined LWIP_MARK_TCPIP_THREAD || defined __DOXYGEN__
-#define LWIP_MARK_TCPIP_THREAD()
-#endif
-/**
- * @}
- */
-
-/*
-   ------------------------------------
-   ---------- Memory options ----------
-   ------------------------------------
-*/
-/**
- * @defgroup lwip_opts_mem Heap and memory pools
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by your C-library
- * instead of the lwip internal allocator. Can save code size if you
- * already use it.
- */
-#if !defined MEM_LIBC_MALLOC || defined __DOXYGEN__
-#define MEM_LIBC_MALLOC                 0
-#endif
-
-/**
- * MEMP_MEM_MALLOC==1: Use mem_malloc/mem_free instead of the lwip pool allocator.
- * Especially useful with MEM_LIBC_MALLOC but handle with care regarding execution
- * speed (heap alloc can be much slower than pool alloc) and usage from interrupts
- * (especially if your netif driver allocates PBUF_POOL pbufs for received frames
- * from interrupt)!
- * ATTENTION: Currently, this uses the heap for ALL pools (also for private pools,
- * not only for internal pools defined in memp_std.h)!
- */
-#if !defined MEMP_MEM_MALLOC || defined __DOXYGEN__
-#define MEMP_MEM_MALLOC                 0
-#endif
-
-/**
- * MEMP_MEM_INIT==1: Force use of memset to initialize pool memory.
- * Useful if pool are moved in uninitialized section of memory. This will ensure
- * default values in pcbs struct are well initialized in all conditions.
- */
-#if !defined MEMP_MEM_INIT || defined __DOXYGEN__
-#define MEMP_MEM_INIT                   0
-#endif
-
-/**
- * MEM_ALIGNMENT: should be set to the alignment of the CPU
- *    4 byte alignment -> \#define MEM_ALIGNMENT 4
- *    2 byte alignment -> \#define MEM_ALIGNMENT 2
- */
-#if !defined MEM_ALIGNMENT || defined __DOXYGEN__
-#define MEM_ALIGNMENT                   1
-#endif
-
-/**
- * MEM_SIZE: the size of the heap memory. If the application will send
- * a lot of data that needs to be copied, this should be set high.
- */
-#if !defined MEM_SIZE || defined __DOXYGEN__
-#define MEM_SIZE                        1600
-#endif
-
-/**
- * MEMP_OVERFLOW_CHECK: memp overflow protection reserves a configurable
- * amount of bytes before and after each memp element in every pool and fills
- * it with a prominent default value.
- *    MEMP_OVERFLOW_CHECK == 0 no checking
- *    MEMP_OVERFLOW_CHECK == 1 checks each element when it is freed
- *    MEMP_OVERFLOW_CHECK >= 2 checks each element in every pool every time
- *      memp_malloc() or memp_free() is called (useful but slow!)
- */
-#if !defined MEMP_OVERFLOW_CHECK || defined __DOXYGEN__
-#define MEMP_OVERFLOW_CHECK             0
-#endif
-
-/**
- * MEMP_SANITY_CHECK==1: run a sanity check after each memp_free() to make
- * sure that there are no cycles in the linked lists.
- */
-#if !defined MEMP_SANITY_CHECK || defined __DOXYGEN__
-#define MEMP_SANITY_CHECK               0
-#endif
-
-/**
- * MEM_OVERFLOW_CHECK: mem overflow protection reserves a configurable
- * amount of bytes before and after each heap allocation chunk and fills
- * it with a prominent default value.
- *    MEM_OVERFLOW_CHECK == 0 no checking
- *    MEM_OVERFLOW_CHECK == 1 checks each element when it is freed
- *    MEM_OVERFLOW_CHECK >= 2 checks all heap elements every time
- *      mem_malloc() or mem_free() is called (useful but slow!)
- */
-#if !defined MEM_OVERFLOW_CHECK || defined __DOXYGEN__
-#define MEM_OVERFLOW_CHECK              0
-#endif
-
-/**
- * MEM_SANITY_CHECK==1: run a sanity check after each mem_free() to make
- * sure that the linked list of heap elements is not corrupted.
- */
-#if !defined MEM_SANITY_CHECK || defined __DOXYGEN__
-#define MEM_SANITY_CHECK                0
-#endif
-
-/**
- * MEM_USE_POOLS==1: Use an alternative to malloc() by allocating from a set
- * of memory pools of various sizes. When mem_malloc is called, an element of
- * the smallest pool that can provide the length needed is returned.
- * To use this, MEMP_USE_CUSTOM_POOLS also has to be enabled.
- */
-#if !defined MEM_USE_POOLS || defined __DOXYGEN__
-#define MEM_USE_POOLS                   0
-#endif
-
-/**
- * MEM_USE_POOLS_TRY_BIGGER_POOL==1: if one malloc-pool is empty, try the next
- * bigger pool - WARNING: THIS MIGHT WASTE MEMORY but it can make a system more
- * reliable. */
-#if !defined MEM_USE_POOLS_TRY_BIGGER_POOL || defined __DOXYGEN__
-#define MEM_USE_POOLS_TRY_BIGGER_POOL   0
-#endif
-
-/**
- * MEMP_USE_CUSTOM_POOLS==1: whether to include a user file lwippools.h
- * that defines additional pools beyond the "standard" ones required
- * by lwIP. If you set this to 1, you must have lwippools.h in your
- * include path somewhere.
- */
-#if !defined MEMP_USE_CUSTOM_POOLS || defined __DOXYGEN__
-#define MEMP_USE_CUSTOM_POOLS           0
-#endif
-
-/**
- * Set this to 1 if you want to free PBUF_RAM pbufs (or call mem_free()) from
- * interrupt context (or another context that doesn't allow waiting for a
- * semaphore).
- * If set to 1, mem_malloc will be protected by a semaphore and SYS_ARCH_PROTECT,
- * while mem_free will only use SYS_ARCH_PROTECT. mem_malloc SYS_ARCH_UNPROTECTs
- * with each loop so that mem_free can run.
- *
- * ATTENTION: As you can see from the above description, this leads to dis-/
- * enabling interrupts often, which can be slow! Also, on low memory, mem_malloc
- * can need longer.
- *
- * If you don't want that, at least for NO_SYS=0, you can still use the following
- * functions to enqueue a deallocation call which then runs in the tcpip_thread
- * context:
- * - pbuf_free_callback(p);
- * - mem_free_callback(m);
- */
-#if !defined LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT || defined __DOXYGEN__
-#define LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 0
-#endif
-/**
- * @}
- */
-
-/*
-   ------------------------------------------------
-   ---------- Internal Memory Pool Sizes ----------
-   ------------------------------------------------
-*/
-/**
- * @defgroup lwip_opts_memp Internal memory pools
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * MEMP_NUM_PBUF: the number of memp struct pbufs (used for PBUF_ROM and PBUF_REF).
- * If the application sends a lot of data out of ROM (or other static memory),
- * this should be set high.
- */
-#if !defined MEMP_NUM_PBUF || defined __DOXYGEN__
-#define MEMP_NUM_PBUF                   16
-#endif
-
-/**
- * MEMP_NUM_RAW_PCB: Number of raw connection PCBs
- * (requires the LWIP_RAW option)
- */
-#if !defined MEMP_NUM_RAW_PCB || defined __DOXYGEN__
-#define MEMP_NUM_RAW_PCB                4
-#endif
-
-/**
- * MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
- * per active UDP "connection".
- * (requires the LWIP_UDP option)
- */
-#if !defined MEMP_NUM_UDP_PCB || defined __DOXYGEN__
-#define MEMP_NUM_UDP_PCB                4
-#endif
-
-/**
- * MEMP_NUM_TCP_PCB: the number of simultaneously active TCP connections.
- * (requires the LWIP_TCP option)
- */
-#if !defined MEMP_NUM_TCP_PCB || defined __DOXYGEN__
-#define MEMP_NUM_TCP_PCB                5
-#endif
-
-/**
- * MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections.
- * (requires the LWIP_TCP option)
- */
-#if !defined MEMP_NUM_TCP_PCB_LISTEN || defined __DOXYGEN__
-#define MEMP_NUM_TCP_PCB_LISTEN         8
-#endif
-
-/**
- * MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP segments.
- * (requires the LWIP_TCP option)
- */
-#if !defined MEMP_NUM_TCP_SEG || defined __DOXYGEN__
-#define MEMP_NUM_TCP_SEG                16
-#endif
-
-/**
- * MEMP_NUM_ALTCP_PCB: the number of simultaneously active altcp layer pcbs.
- * (requires the LWIP_ALTCP option)
- * Connections with multiple layers require more than one altcp_pcb (e.g. TLS
- * over TCP requires 2 altcp_pcbs, one for TLS and one for TCP).
- */
-#if !defined MEMP_NUM_ALTCP_PCB || defined __DOXYGEN__
-#define MEMP_NUM_ALTCP_PCB              MEMP_NUM_TCP_PCB
-#endif
-
-/**
- * MEMP_NUM_REASSDATA: the number of IP packets simultaneously queued for
- * reassembly (whole packets, not fragments!)
- */
-#if !defined MEMP_NUM_REASSDATA || defined __DOXYGEN__
-#define MEMP_NUM_REASSDATA              5
-#endif
-
-/**
- * MEMP_NUM_FRAG_PBUF: the number of IP fragments simultaneously sent
- * (fragments, not whole packets!).
- * This is only used with LWIP_NETIF_TX_SINGLE_PBUF==0 and only has to be > 1
- * with DMA-enabled MACs where the packet is not yet sent when netif->output
- * returns.
- */
-#if !defined MEMP_NUM_FRAG_PBUF || defined __DOXYGEN__
-#define MEMP_NUM_FRAG_PBUF              15
-#endif
-
-/**
- * MEMP_NUM_ARP_QUEUE: the number of simultaneously queued outgoing
- * packets (pbufs) that are waiting for an ARP request (to resolve
- * their destination address) to finish.
- * (requires the ARP_QUEUEING option)
- */
-#if !defined MEMP_NUM_ARP_QUEUE || defined __DOXYGEN__
-#define MEMP_NUM_ARP_QUEUE              30
-#endif
-
-/**
- * MEMP_NUM_IGMP_GROUP: The number of multicast groups whose network interfaces
- * can be members at the same time (one per netif - allsystems group -, plus one
- * per netif membership).
- * (requires the LWIP_IGMP option)
- */
-#if !defined MEMP_NUM_IGMP_GROUP || defined __DOXYGEN__
-#define MEMP_NUM_IGMP_GROUP             8
-#endif
-
-/**
- * The number of sys timeouts used by the core stack (not apps)
- * The default number of timeouts is calculated here for all enabled modules.
- */
-#if ESP_LWIP
-#define LWIP_NUM_SYS_TIMEOUT_INTERNAL   (LWIP_TCP + IP_REASSEMBLY + (LWIP_ARP + (ESP_GRATUITOUS_ARP ? 1 : 0)) + (ESP_LWIP_DHCP_FINE_TIMERS_ONDEMAND ? LWIP_DHCP : 2*LWIP_DHCP + (ESP_DHCPS_TIMER ? 1 : 0)) + LWIP_AUTOIP + LWIP_IGMP + (ESP_LWIP_DNS_TIMERS_ONDEMAND ? 0 : LWIP_DNS) + PPP_NUM_TIMEOUTS + (LWIP_IPV6 * (1 + LWIP_IPV6_REASS + LWIP_IPV6_MLD)))
-#else
-#define LWIP_NUM_SYS_TIMEOUT_INTERNAL   (LWIP_TCP + IP_REASSEMBLY + LWIP_ARP + (2*LWIP_DHCP) + LWIP_AUTOIP + LWIP_IGMP + LWIP_DNS + PPP_NUM_TIMEOUTS + (LWIP_IPV6 * (1 + LWIP_IPV6_REASS + LWIP_IPV6_MLD)))
-#endif
-/**
- * MEMP_NUM_SYS_TIMEOUT: the number of simultaneously active timeouts.
- * The default number of timeouts is calculated here for all enabled modules.
- * The formula expects settings to be either '0' or '1'.
- */
-#if !defined MEMP_NUM_SYS_TIMEOUT || defined __DOXYGEN__
-#define MEMP_NUM_SYS_TIMEOUT            LWIP_NUM_SYS_TIMEOUT_INTERNAL
-#endif
-
-/**
- * MEMP_NUM_NETBUF: the number of struct netbufs.
- * (only needed if you use the sequential API, like api_lib.c)
- */
-#if !defined MEMP_NUM_NETBUF || defined __DOXYGEN__
-#define MEMP_NUM_NETBUF                 2
-#endif
-
-/**
- * MEMP_NUM_NETCONN: the number of struct netconns.
- * (only needed if you use the sequential API, like api_lib.c)
- */
-#if !defined MEMP_NUM_NETCONN || defined __DOXYGEN__
-#define MEMP_NUM_NETCONN                4
-#endif
-
-/**
- * MEMP_NUM_SELECT_CB: the number of struct lwip_select_cb.
- * (Only needed if you have LWIP_MPU_COMPATIBLE==1 and use the socket API.
- * In that case, you need one per thread calling lwip_select.)
- */
-#if !defined MEMP_NUM_SELECT_CB || defined __DOXYGEN__
-#define MEMP_NUM_SELECT_CB              4
-#endif
-
-/**
- * MEMP_NUM_TCPIP_MSG_API: the number of struct tcpip_msg, which are used
- * for callback/timeout API communication.
- * (only needed if you use tcpip.c)
- */
-#if !defined MEMP_NUM_TCPIP_MSG_API || defined __DOXYGEN__
-#define MEMP_NUM_TCPIP_MSG_API          8
-#endif
-
-/**
- * MEMP_NUM_TCPIP_MSG_INPKT: the number of struct tcpip_msg, which are used
- * for incoming packets.
- * (only needed if you use tcpip.c)
- */
-#if !defined MEMP_NUM_TCPIP_MSG_INPKT || defined __DOXYGEN__
-#define MEMP_NUM_TCPIP_MSG_INPKT        8
-#endif
-
-/**
- * MEMP_NUM_NETDB: the number of concurrently running lwip_addrinfo() calls
- * (before freeing the corresponding memory using lwip_freeaddrinfo()).
- */
-#if !defined MEMP_NUM_NETDB || defined __DOXYGEN__
-#define MEMP_NUM_NETDB                  1
-#endif
-
-/**
- * MEMP_NUM_LOCALHOSTLIST: the number of host entries in the local host list
- * if DNS_LOCAL_HOSTLIST_IS_DYNAMIC==1.
- */
-#if !defined MEMP_NUM_LOCALHOSTLIST || defined __DOXYGEN__
-#define MEMP_NUM_LOCALHOSTLIST          1
-#endif
-
-/**
- * PBUF_POOL_SIZE: the number of buffers in the pbuf pool.
- */
-#if !defined PBUF_POOL_SIZE || defined __DOXYGEN__
-#define PBUF_POOL_SIZE                  16
-#endif
-
-/** MEMP_NUM_API_MSG: the number of concurrently active calls to various
- * socket, netconn, and tcpip functions
- */
-#if !defined MEMP_NUM_API_MSG || defined __DOXYGEN__
-#define MEMP_NUM_API_MSG                MEMP_NUM_TCPIP_MSG_API
-#endif
-
-/** MEMP_NUM_DNS_API_MSG: the number of concurrently active calls to netconn_gethostbyname
- */
-#if !defined MEMP_NUM_DNS_API_MSG || defined __DOXYGEN__
-#define MEMP_NUM_DNS_API_MSG            MEMP_NUM_TCPIP_MSG_API
-#endif
-
-/** MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA: the number of concurrently active calls
- * to getsockopt/setsockopt
- */
-#if !defined MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA || defined __DOXYGEN__
-#define MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA MEMP_NUM_TCPIP_MSG_API
-#endif
-
-/** MEMP_NUM_NETIFAPI_MSG: the number of concurrently active calls to the
- * netifapi functions
- */
-#if !defined MEMP_NUM_NETIFAPI_MSG || defined __DOXYGEN__
-#define MEMP_NUM_NETIFAPI_MSG           MEMP_NUM_TCPIP_MSG_API
-#endif
-/**
- * @}
- */
-
-/*
-   ---------------------------------
-   ---------- ARP options ----------
-   ---------------------------------
-*/
-/**
- * @defgroup lwip_opts_arp ARP
- * @ingroup lwip_opts_ipv4
- * @{
- */
-/**
- * LWIP_ARP==1: Enable ARP functionality.
- */
-#if !defined LWIP_ARP || defined __DOXYGEN__
-#define LWIP_ARP                        1
-#endif
-
-/**
- * ARP_TABLE_SIZE: Number of active MAC-IP address pairs cached.
- */
-#if !defined ARP_TABLE_SIZE || defined __DOXYGEN__
-#define ARP_TABLE_SIZE                  10
-#endif
-
-/** the time an ARP entry stays valid after its last update,
- *  for ARP_TMR_INTERVAL = 1000, this is
- *  (60 * 5) seconds = 5 minutes.
- */
-#if !defined ARP_MAXAGE || defined __DOXYGEN__
-#define ARP_MAXAGE                      300
-#endif
-
-/**
- * ARP_QUEUEING==1: Multiple outgoing packets are queued during hardware address
- * resolution. By default, only the most recent packet is queued per IP address.
- * This is sufficient for most protocols and mainly reduces TCP connection
- * startup time. Set this to 1 if you know your application sends more than one
- * packet in a row to an IP address that is not in the ARP cache.
- */
-#if !defined ARP_QUEUEING || defined __DOXYGEN__
-#define ARP_QUEUEING                    0
-#endif
-
-/** The maximum number of packets which may be queued for each
- *  unresolved address by other network layers. Defaults to 3, 0 means disabled.
- *  Old packets are dropped, new packets are queued.
- */
-#if !defined ARP_QUEUE_LEN || defined __DOXYGEN__
-#define ARP_QUEUE_LEN                   3
-#endif
-
-/**
- * ETHARP_SUPPORT_VLAN==1: support receiving and sending ethernet packets with
- * VLAN header. See the description of LWIP_HOOK_VLAN_CHECK and
- * LWIP_HOOK_VLAN_SET hooks to check/set VLAN headers.
- * Additionally, you can define ETHARP_VLAN_CHECK to an u16_t VLAN ID to check.
- * If ETHARP_VLAN_CHECK is defined, only VLAN-traffic for this VLAN is accepted.
- * If ETHARP_VLAN_CHECK is not defined, all traffic is accepted.
- * Alternatively, define a function/define ETHARP_VLAN_CHECK_FN(eth_hdr, vlan)
- * that returns 1 to accept a packet or 0 to drop a packet.
- */
-#if !defined ETHARP_SUPPORT_VLAN || defined __DOXYGEN__
-#define ETHARP_SUPPORT_VLAN             0
-#endif
-
-/** LWIP_ETHERNET==1: enable ethernet support even though ARP might be disabled
- */
-#if !defined LWIP_ETHERNET || defined __DOXYGEN__
-#define LWIP_ETHERNET                   LWIP_ARP
-#endif
-
-/** ETH_PAD_SIZE: number of bytes added before the ethernet header to ensure
- * alignment of payload after that header. Since the header is 14 bytes long,
- * without this padding e.g. addresses in the IP header will not be aligned
- * on a 32-bit boundary, so setting this to 2 can speed up 32-bit-platforms.
- */
-#if !defined ETH_PAD_SIZE || defined __DOXYGEN__
-#define ETH_PAD_SIZE                    0
-#endif
-
-/** ETHARP_SUPPORT_STATIC_ENTRIES==1: enable code to support static ARP table
- * entries (using etharp_add_static_entry/etharp_remove_static_entry).
- */
-#if !defined ETHARP_SUPPORT_STATIC_ENTRIES || defined __DOXYGEN__
-#define ETHARP_SUPPORT_STATIC_ENTRIES   0
-#endif
-
-/** ETHARP_TABLE_MATCH_NETIF==1: Match netif for ARP table entries.
- * If disabled, duplicate IP address on multiple netifs are not supported
- * (but this should only occur for AutoIP).
- */
-#if !defined ETHARP_TABLE_MATCH_NETIF || defined __DOXYGEN__
-#define ETHARP_TABLE_MATCH_NETIF        !LWIP_SINGLE_NETIF
-#endif
-/**
- * @}
- */
-
-/*
-   --------------------------------
-   ---------- IP options ----------
-   --------------------------------
-*/
-/**
- * @defgroup lwip_opts_ipv4 IPv4
- * @ingroup lwip_opts
- * @{
- */
-/**
- * LWIP_IPV4==1: Enable IPv4
- */
-#if !defined LWIP_IPV4 || defined __DOXYGEN__
-#define LWIP_IPV4                       1
-#endif
-
-/**
- * IP_FORWARD==1: Enables the ability to forward IP packets across network
- * interfaces. If you are going to run lwIP on a device with only one network
- * interface, define this to 0.
- */
-#if !defined IP_FORWARD || defined __DOXYGEN__
-#define IP_FORWARD                      0
-#endif
-
-/**
- * IP_REASSEMBLY==1: Reassemble incoming fragmented IP packets. Note that
- * this option does not affect outgoing packet sizes, which can be controlled
- * via IP_FRAG.
- */
-#if !defined IP_REASSEMBLY || defined __DOXYGEN__
-#define IP_REASSEMBLY                   1
-#endif
-
-/**
- * IP_FRAG==1: Fragment outgoing IP packets if their size exceeds MTU. Note
- * that this option does not affect incoming packet sizes, which can be
- * controlled via IP_REASSEMBLY.
- */
-#if !defined IP_FRAG || defined __DOXYGEN__
-#define IP_FRAG                         1
-#endif
-
-#if !LWIP_IPV4
-/* disable IPv4 extensions when IPv4 is disabled */
-#undef IP_FORWARD
-#define IP_FORWARD                      0
-#undef IP_REASSEMBLY
-#define IP_REASSEMBLY                   0
-#undef IP_FRAG
-#define IP_FRAG                         0
-#endif /* !LWIP_IPV4 */
-
-/**
- * IP_NAPT==1: Enables IPv4 Network Address and Port Translation
- * Note that IP_FORWARD needs to be enabled for NAPT to work
- */
-#if !defined IP_NAPT || defined __DOXYGEN__
-#define IP_NAPT                      0
-#endif
-
-/**
- * IP_OPTIONS_ALLOWED: Defines the behavior for IP options.
- *      IP_OPTIONS_ALLOWED==0: All packets with IP options are dropped.
- *      IP_OPTIONS_ALLOWED==1: IP options are allowed (but not parsed).
- */
-#if !defined IP_OPTIONS_ALLOWED || defined __DOXYGEN__
-#define IP_OPTIONS_ALLOWED              1
-#endif
-
-/**
- * IP_REASS_MAXAGE: Maximum time (in multiples of IP_TMR_INTERVAL - so seconds, normally)
- * a fragmented IP packet waits for all fragments to arrive. If not all fragments arrived
- * in this time, the whole packet is discarded.
- */
-#if !defined IP_REASS_MAXAGE || defined __DOXYGEN__
-#define IP_REASS_MAXAGE                 15
-#endif
-
-/**
- * IP_REASS_MAX_PBUFS: Total maximum amount of pbufs waiting to be reassembled.
- * Since the received pbufs are enqueued, be sure to configure
- * PBUF_POOL_SIZE > IP_REASS_MAX_PBUFS so that the stack is still able to receive
- * packets even if the maximum amount of fragments is enqueued for reassembly!
- * When IPv4 *and* IPv6 are enabled, this even changes to
- * (PBUF_POOL_SIZE > 2 * IP_REASS_MAX_PBUFS)!
- */
-#if !defined IP_REASS_MAX_PBUFS || defined __DOXYGEN__
-#define IP_REASS_MAX_PBUFS              10
-#endif
-
-/**
- * IP_DEFAULT_TTL: Default value for Time-To-Live used by transport layers.
- */
-#if !defined IP_DEFAULT_TTL || defined __DOXYGEN__
-#define IP_DEFAULT_TTL                  255
-#endif
-
-/**
- * IP_SOF_BROADCAST=1: Use the SOF_BROADCAST field to enable broadcast
- * filter per pcb on udp and raw send operations. To enable broadcast filter
- * on recv operations, you also have to set IP_SOF_BROADCAST_RECV=1.
- */
-#if !defined IP_SOF_BROADCAST || defined __DOXYGEN__
-#define IP_SOF_BROADCAST                0
-#endif
-
-/**
- * IP_SOF_BROADCAST_RECV (requires IP_SOF_BROADCAST=1) enable the broadcast
- * filter on recv operations.
- */
-#if !defined IP_SOF_BROADCAST_RECV || defined __DOXYGEN__
-#define IP_SOF_BROADCAST_RECV           0
-#endif
-
-/**
- * IP_FORWARD_ALLOW_TX_ON_RX_NETIF==1: allow ip_forward() to send packets back
- * out on the netif where it was received. This should only be used for
- * wireless networks.
- * ATTENTION: When this is 1, make sure your netif driver correctly marks incoming
- * link-layer-broadcast/multicast packets as such using the corresponding pbuf flags!
- */
-#if !defined IP_FORWARD_ALLOW_TX_ON_RX_NETIF || defined __DOXYGEN__
-#define IP_FORWARD_ALLOW_TX_ON_RX_NETIF 0
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------
-   ---------- ICMP options ----------
-   ----------------------------------
-*/
-/**
- * @defgroup lwip_opts_icmp ICMP
- * @ingroup lwip_opts_ipv4
- * @{
- */
-/**
- * LWIP_ICMP==1: Enable ICMP module inside the IP stack.
- * Be careful, disable that make your product non-compliant to RFC1122
- */
-#if !defined LWIP_ICMP || defined __DOXYGEN__
-#define LWIP_ICMP                       1
-#endif
-
-/**
- * ICMP_TTL: Default value for Time-To-Live used by ICMP packets.
- */
-#if !defined ICMP_TTL || defined __DOXYGEN__
-#define ICMP_TTL                        IP_DEFAULT_TTL
-#endif
-
-/**
- * LWIP_BROADCAST_PING==1: respond to broadcast pings (default is unicast only)
- */
-#if !defined LWIP_BROADCAST_PING || defined __DOXYGEN__
-#define LWIP_BROADCAST_PING             0
-#endif
-
-/**
- * LWIP_MULTICAST_PING==1: respond to multicast pings (default is unicast only)
- */
-#if !defined LWIP_MULTICAST_PING || defined __DOXYGEN__
-#define LWIP_MULTICAST_PING             0
-#endif
-/**
- * @}
- */
-
-/*
-   ---------------------------------
-   ---------- RAW options ----------
-   ---------------------------------
-*/
-/**
- * @defgroup lwip_opts_raw RAW
- * @ingroup lwip_opts_callback
- * @{
- */
-/**
- * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
- */
-#if !defined LWIP_RAW || defined __DOXYGEN__
-#define LWIP_RAW                        0
-#endif
-
-/**
- * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
- */
-#if !defined RAW_TTL || defined __DOXYGEN__
-#define RAW_TTL                         IP_DEFAULT_TTL
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------
-   ---------- DHCP options ----------
-   ----------------------------------
-*/
-/**
- * @defgroup lwip_opts_dhcp DHCP
- * @ingroup lwip_opts_ipv4
- * @{
- */
-/**
- * LWIP_DHCP==1: Enable DHCP module.
- */
-#if !defined LWIP_DHCP || defined __DOXYGEN__
-#define LWIP_DHCP                       0
-#endif
-#if !LWIP_IPV4
-/* disable DHCP when IPv4 is disabled */
-#undef LWIP_DHCP
-#define LWIP_DHCP                       0
-#endif /* !LWIP_IPV4 */
-
-/**
- * DHCP_DOES_ARP_CHECK==1: Do an ARP check on the offered address.
- */
-#if !defined DHCP_DOES_ARP_CHECK || defined __DOXYGEN__
-#define DHCP_DOES_ARP_CHECK             (LWIP_DHCP && LWIP_ARP)
-#endif
-
-/**
- * LWIP_DHCP_BOOTP_FILE==1: Store offered_si_addr and boot_file_name.
- */
-#if !defined LWIP_DHCP_BOOTP_FILE || defined __DOXYGEN__
-#define LWIP_DHCP_BOOTP_FILE            0
-#endif
-
-/**
- * LWIP_DHCP_GETS_NTP==1: Request NTP servers with discover/select. For each
- * response packet, an callback is called, which has to be provided by the port:
- * void dhcp_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs);
-*/
-#if !defined LWIP_DHCP_GET_NTP_SRV || defined __DOXYGEN__
-#define LWIP_DHCP_GET_NTP_SRV           1
-#endif
-
-/**
- * The maximum of NTP servers requested
- */
-#if !defined LWIP_DHCP_MAX_NTP_SERVERS || defined __DOXYGEN__
-#define LWIP_DHCP_MAX_NTP_SERVERS       3
-#endif
-
-/**
- * LWIP_DHCP_MAX_DNS_SERVERS > 0: Request DNS servers with discover/select.
- * DNS servers received in the response are passed to DNS via @ref dns_setserver()
- * (up to the maximum limit defined here).
- */
-#if !defined LWIP_DHCP_MAX_DNS_SERVERS || defined __DOXYGEN__
-#define LWIP_DHCP_MAX_DNS_SERVERS       DNS_MAX_SERVERS
-#endif
-/**
- * @}
- */
-
-#ifndef LWIP_DHCP_IP_ADDR_RESTORE
-#define LWIP_DHCP_IP_ADDR_RESTORE()       0
-#endif
-
-#ifndef LWIP_DHCP_IP_ADDR_STORE
-#define LWIP_DHCP_IP_ADDR_STORE()
-#endif
-
-#ifndef LWIP_DHCP_IP_ADDR_ERASE
-#define LWIP_DHCP_IP_ADDR_ERASE(esp_netif)
-#endif
-
-/*
-   ------------------------------------
-   ---------- AUTOIP options ----------
-   ------------------------------------
-*/
-/**
- * @defgroup lwip_opts_autoip AUTOIP
- * @ingroup lwip_opts_ipv4
- * @{
- */
-/**
- * LWIP_AUTOIP==1: Enable AUTOIP module.
- */
-#if !defined LWIP_AUTOIP || defined __DOXYGEN__
-#define LWIP_AUTOIP                     0
-#endif
-#if !LWIP_IPV4
-/* disable AUTOIP when IPv4 is disabled */
-#undef LWIP_AUTOIP
-#define LWIP_AUTOIP                     0
-#endif /* !LWIP_IPV4 */
-
-/**
- * LWIP_DHCP_AUTOIP_COOP==1: Allow DHCP and AUTOIP to be both enabled on
- * the same interface at the same time.
- */
-#if !defined LWIP_DHCP_AUTOIP_COOP || defined __DOXYGEN__
-#define LWIP_DHCP_AUTOIP_COOP           0
-#endif
-
-/**
- * ESP_IPV6_AUTOCONFIG==1: Enable stateless address autoconfiguration as per RFC 4862.
- */
-#if !defined ESP_IPV6_AUTOCONFIG
-#define ESP_IPV6_AUTOCONFIG 0
-#endif
-
-/**
- * LWIP_DHCP_AUTOIP_COOP_TRIES: Set to the number of DHCP DISCOVER probes
- * that should be sent before falling back on AUTOIP (the DHCP client keeps
- * running in this case). This can be set as low as 1 to get an AutoIP address
- * very  quickly, but you should be prepared to handle a changing IP address
- * when DHCP overrides AutoIP.
- */
-#if !defined LWIP_DHCP_AUTOIP_COOP_TRIES || defined __DOXYGEN__
-#define LWIP_DHCP_AUTOIP_COOP_TRIES     9
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------
-   ----- SNMP MIB2 support      -----
-   ----------------------------------
-*/
-/**
- * @defgroup lwip_opts_mib2 SNMP MIB2 callbacks
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * LWIP_MIB2_CALLBACKS==1: Turn on SNMP MIB2 callbacks.
- * Turn this on to get callbacks needed to implement MIB2.
- * Usually MIB2_STATS should be enabled, too.
- */
-#if !defined LWIP_MIB2_CALLBACKS || defined __DOXYGEN__
-#define LWIP_MIB2_CALLBACKS             0
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------
-   -------- Multicast options -------
-   ----------------------------------
-*/
-/**
- * @defgroup lwip_opts_multicast Multicast
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * LWIP_MULTICAST_TX_OPTIONS==1: Enable multicast TX support like the socket options
- * IP_MULTICAST_TTL/IP_MULTICAST_IF/IP_MULTICAST_LOOP, as well as (currently only)
- * core support for the corresponding IPv6 options.
- */
-#if !defined LWIP_MULTICAST_TX_OPTIONS || defined __DOXYGEN__
-#define LWIP_MULTICAST_TX_OPTIONS       ((LWIP_IGMP || LWIP_IPV6_MLD) && (LWIP_UDP || LWIP_RAW))
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------
-   ---------- IGMP options ----------
-   ----------------------------------
-*/
-/**
- * @defgroup lwip_opts_igmp IGMP
- * @ingroup lwip_opts_ipv4
- * @{
- */
-/**
- * LWIP_IGMP==1: Turn on IGMP module.
- */
-#if !defined LWIP_IGMP || defined __DOXYGEN__
-#define LWIP_IGMP                       0
-#endif
-#if !LWIP_IPV4
-#undef LWIP_IGMP
-#define LWIP_IGMP                       0
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------
-   ---------- DNS options -----------
-   ----------------------------------
-*/
-/**
- * @defgroup lwip_opts_dns DNS
- * @ingroup lwip_opts_callback
- * @{
- */
-/**
- * LWIP_DNS==1: Turn on DNS module. UDP must be available for DNS
- * transport.
- */
-#if !defined LWIP_DNS || defined __DOXYGEN__
-#define LWIP_DNS                        0
-#endif
-
-/** DNS maximum number of entries to maintain locally. */
-#if !defined DNS_TABLE_SIZE || defined __DOXYGEN__
-#define DNS_TABLE_SIZE                  4
-#endif
-
-/** DNS maximum host name length supported in the name table. */
-#if !defined DNS_MAX_NAME_LENGTH || defined __DOXYGEN__
-#define DNS_MAX_NAME_LENGTH             256
-#endif
-
-/** The maximum of DNS servers
- * The first server can be initialized automatically by defining
- * DNS_SERVER_ADDRESS(ipaddr), where 'ipaddr' is an 'ip_addr_t*'
- */
-#if !defined DNS_MAX_SERVERS || defined __DOXYGEN__
-#define DNS_MAX_SERVERS                 2
-#endif
-
-/** DNS maximum number of retries when asking for a name, before "timeout". */
-#if !defined DNS_MAX_RETRIES || defined __DOXYGEN__
-#define DNS_MAX_RETRIES                 4
-#endif
-
-/** DNS do a name checking between the query and the response. */
-#if !defined DNS_DOES_NAME_CHECK || defined __DOXYGEN__
-#define DNS_DOES_NAME_CHECK             1
-#endif
-
-/** LWIP_DNS_SECURE: controls the security level of the DNS implementation
- * Use all DNS security features by default.
- * This is overridable but should only be needed by very small targets
- * or when using against non standard DNS servers. */
-#if !defined LWIP_DNS_SECURE || defined __DOXYGEN__
-#define LWIP_DNS_SECURE (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT)
-#endif
-
-/* A list of DNS security features follows */
-#define LWIP_DNS_SECURE_RAND_XID                1
-#define LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING 2
-#define LWIP_DNS_SECURE_RAND_SRC_PORT           4
-
-/** DNS_LOCAL_HOSTLIST: Implements a local host-to-address list. If enabled, you have to define an initializer:
- *  \#define DNS_LOCAL_HOSTLIST_INIT {DNS_LOCAL_HOSTLIST_ELEM("host_ip4", IPADDR4_INIT_BYTES(1,2,3,4)), \
- *                                    DNS_LOCAL_HOSTLIST_ELEM("host_ip6", IPADDR6_INIT_HOST(123, 234, 345, 456)}
- *
- *  Instead, you can also use an external function:
- *  \#define DNS_LOOKUP_LOCAL_EXTERN(x) extern err_t my_lookup_function(const char *name, ip_addr_t *addr, u8_t dns_addrtype)
- *  that looks up the IP address and returns ERR_OK if found (LWIP_DNS_ADDRTYPE_xxx is passed in dns_addrtype).
- */
-#if !defined DNS_LOCAL_HOSTLIST || defined __DOXYGEN__
-#define DNS_LOCAL_HOSTLIST              0
-#endif /* DNS_LOCAL_HOSTLIST */
-
-/** If this is turned on, the local host-list can be dynamically changed
- *  at runtime. */
-#if !defined DNS_LOCAL_HOSTLIST_IS_DYNAMIC || defined __DOXYGEN__
-#define DNS_LOCAL_HOSTLIST_IS_DYNAMIC   0
-#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
-
-/** Set this to 1 to enable querying ".local" names via mDNS
- *  using a One-Shot Multicast DNS Query */
-#if !defined LWIP_DNS_SUPPORT_MDNS_QUERIES || defined __DOXYGEN__
-#define LWIP_DNS_SUPPORT_MDNS_QUERIES   0
-#endif
-/**
- * @}
- */
-
-/*
-   ---------------------------------
-   ---------- UDP options ----------
-   ---------------------------------
-*/
-/**
- * @defgroup lwip_opts_udp UDP
- * @ingroup lwip_opts_callback
- * @{
- */
-/**
- * LWIP_UDP==1: Turn on UDP.
- */
-#if !defined LWIP_UDP || defined __DOXYGEN__
-#define LWIP_UDP                        1
-#endif
-
-/**
- * LWIP_UDPLITE==1: Turn on UDP-Lite. (Requires LWIP_UDP)
- */
-#if !defined LWIP_UDPLITE || defined __DOXYGEN__
-#define LWIP_UDPLITE                    0
-#endif
-
-/**
- * UDP_TTL: Default Time-To-Live value.
- */
-#if !defined UDP_TTL || defined __DOXYGEN__
-#define UDP_TTL                         IP_DEFAULT_TTL
-#endif
-
-/**
- * LWIP_NETBUF_RECVINFO==1: append destination addr and port to every netbuf.
- */
-#if !defined LWIP_NETBUF_RECVINFO || defined __DOXYGEN__
-#define LWIP_NETBUF_RECVINFO            0
-#endif
-/**
- * @}
- */
-
-/*
-   ---------------------------------
-   ---------- TCP options ----------
-   ---------------------------------
-*/
-/**
- * @defgroup lwip_opts_tcp TCP
- * @ingroup lwip_opts_callback
- * @{
- */
-/**
- * LWIP_TCP==1: Turn on TCP.
- */
-#if !defined LWIP_TCP || defined __DOXYGEN__
-#define LWIP_TCP                        1
-#endif
-
-/**
- * TCP_TTL: Default Time-To-Live value.
- */
-#if !defined TCP_TTL || defined __DOXYGEN__
-#define TCP_TTL                         IP_DEFAULT_TTL
-#endif
-
-/**
- * TCP_WND: The size of a TCP window.  This must be at least
- * (2 * TCP_MSS) for things to work well.
- * ATTENTION: when using TCP_RCV_SCALE, TCP_WND is the total size
- * with scaling applied. Maximum window value in the TCP header
- * will be TCP_WND >> TCP_RCV_SCALE
- */
-#if !defined TCP_WND || defined __DOXYGEN__
-#define TCP_WND                         (4 * TCP_MSS)
-#endif
-
-/**
- * TCP_MAXRTX: Maximum number of retransmissions of data segments.
- */
-#if !defined TCP_MAXRTX || defined __DOXYGEN__
-#define TCP_MAXRTX                      12
-#endif
-
-/**
- * TCP_SYNMAXRTX: Maximum number of retransmissions of SYN segments.
- */
-#if !defined TCP_SYNMAXRTX || defined __DOXYGEN__
-#define TCP_SYNMAXRTX                   6
-#endif
-
-/**
- * TCP_QUEUE_OOSEQ==1: TCP will queue segments that arrive out of order.
- * Define to 0 if your device is low on memory.
- */
-#if !defined TCP_QUEUE_OOSEQ || defined __DOXYGEN__
-#define TCP_QUEUE_OOSEQ                 LWIP_TCP
-#endif
-
-/**
- * LWIP_TCP_SACK_OUT==1: TCP will support sending selective acknowledgements (SACKs).
- */
-#if !defined LWIP_TCP_SACK_OUT || defined __DOXYGEN__
-#define LWIP_TCP_SACK_OUT               0
-#endif
-
-/**
- * LWIP_TCP_MAX_SACK_NUM: The maximum number of SACK values to include in TCP segments.
- * Must be at least 1, but is only used if LWIP_TCP_SACK_OUT is enabled.
- * NOTE: Even though we never send more than 3 or 4 SACK ranges in a single segment
- * (depending on other options), setting this option to values greater than 4 is not pointless.
- * This is basically the max number of SACK ranges we want to keep track of.
- * As new data is delivered, some of the SACK ranges may be removed or merged.
- * In that case some of those older SACK ranges may be used again.
- * The amount of memory used to store SACK ranges is LWIP_TCP_MAX_SACK_NUM * 8 bytes for each TCP PCB.
- */
-#if !defined LWIP_TCP_MAX_SACK_NUM || defined __DOXYGEN__
-#define LWIP_TCP_MAX_SACK_NUM           4
-#endif
-
-/**
- * TCP_MSS: TCP Maximum segment size. (default is 536, a conservative default,
- * you might want to increase this.)
- * For the receive side, this MSS is advertised to the remote side
- * when opening a connection. For the transmit size, this MSS sets
- * an upper limit on the MSS advertised by the remote host.
- */
-#if !defined TCP_MSS || defined __DOXYGEN__
-#define TCP_MSS                         536
-#endif
-
-/**
- * TCP_CALCULATE_EFF_SEND_MSS: "The maximum size of a segment that TCP really
- * sends, the 'effective send MSS,' MUST be the smaller of the send MSS (which
- * reflects the available reassembly buffer size at the remote host) and the
- * largest size permitted by the IP layer" (RFC 1122)
- * Setting this to 1 enables code that checks TCP_MSS against the MTU of the
- * netif used for a connection and limits the MSS if it would be too big otherwise.
- */
-#if !defined TCP_CALCULATE_EFF_SEND_MSS || defined __DOXYGEN__
-#define TCP_CALCULATE_EFF_SEND_MSS      1
-#endif
-
-
-/**
- * TCP_SND_BUF: TCP sender buffer space (bytes).
- * To achieve good performance, this should be at least 2 * TCP_MSS.
- */
-#if !defined TCP_SND_BUF || defined __DOXYGEN__
-#define TCP_SND_BUF                     (2 * TCP_MSS)
-#endif
-
-/**
- * TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
- * as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work.
- */
-#if !defined TCP_SND_QUEUELEN || defined __DOXYGEN__
-#define TCP_SND_QUEUELEN                ((4 * (TCP_SND_BUF) + (TCP_MSS - 1))/(TCP_MSS))
-#endif
-
-/**
- * TCP_SNDLOWAT: TCP writable space (bytes). This must be less than
- * TCP_SND_BUF. It is the amount of space which must be available in the
- * TCP snd_buf for select to return writable (combined with TCP_SNDQUEUELOWAT).
- */
-#if !defined TCP_SNDLOWAT || defined __DOXYGEN__
-#define TCP_SNDLOWAT                    LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1)
-#endif
-
-/**
- * TCP_SNDQUEUELOWAT: TCP writable bufs (pbuf count). This must be less
- * than TCP_SND_QUEUELEN. If the number of pbufs queued on a pcb drops below
- * this number, select returns writable (combined with TCP_SNDLOWAT).
- */
-#if !defined TCP_SNDQUEUELOWAT || defined __DOXYGEN__
-#define TCP_SNDQUEUELOWAT               LWIP_MAX(((TCP_SND_QUEUELEN)/2), 5)
-#endif
-
-/**
- * TCP_OOSEQ_MAX_BYTES: The default maximum number of bytes queued on ooseq per
- * pcb if TCP_OOSEQ_BYTES_LIMIT is not defined. Default is 0 (no limit).
- * Only valid for TCP_QUEUE_OOSEQ==1.
- */
-#if !defined TCP_OOSEQ_MAX_BYTES || defined __DOXYGEN__
-#define TCP_OOSEQ_MAX_BYTES             0
-#endif
-
-/**
- * TCP_OOSEQ_BYTES_LIMIT(pcb): Return the maximum number of bytes to be queued
- * on ooseq per pcb, given the pcb. Only valid for TCP_QUEUE_OOSEQ==1 &&
- * TCP_OOSEQ_MAX_BYTES==1.
- * Use this to override TCP_OOSEQ_MAX_BYTES to a dynamic value per pcb.
- */
-#if !defined TCP_OOSEQ_BYTES_LIMIT
-#if TCP_OOSEQ_MAX_BYTES
-#define TCP_OOSEQ_BYTES_LIMIT(pcb)      TCP_OOSEQ_MAX_BYTES
-#elif defined __DOXYGEN__
-#define TCP_OOSEQ_BYTES_LIMIT(pcb)
-#endif
-#endif
-
-/**
- * TCP_OOSEQ_MAX_PBUFS: The default maximum number of pbufs queued on ooseq per
- * pcb if TCP_OOSEQ_BYTES_LIMIT is not defined. Default is 0 (no limit).
- * Only valid for TCP_QUEUE_OOSEQ==1.
- */
-#if !defined TCP_OOSEQ_MAX_PBUFS || defined __DOXYGEN__
-#define TCP_OOSEQ_MAX_PBUFS             0
-#endif
-
-/**
- * TCP_OOSEQ_PBUFS_LIMIT(pcb): Return the maximum number of pbufs to be queued
- * on ooseq per pcb, given the pcb.  Only valid for TCP_QUEUE_OOSEQ==1 &&
- * TCP_OOSEQ_MAX_PBUFS==1.
- * Use this to override TCP_OOSEQ_MAX_PBUFS to a dynamic value per pcb.
- */
-#if !defined TCP_OOSEQ_PBUFS_LIMIT
-#if TCP_OOSEQ_MAX_PBUFS
-#define TCP_OOSEQ_PBUFS_LIMIT(pcb)      TCP_OOSEQ_MAX_PBUFS
-#elif defined __DOXYGEN__
-#define TCP_OOSEQ_PBUFS_LIMIT(pcb)
-#endif
-#endif
-
-/**
- * TCP_LISTEN_BACKLOG: Enable the backlog option for tcp listen pcb.
- */
-#if !defined TCP_LISTEN_BACKLOG || defined __DOXYGEN__
-#define TCP_LISTEN_BACKLOG              0
-#endif
-
-/**
- * The maximum allowed backlog for TCP listen netconns.
- * This backlog is used unless another is explicitly specified.
- * 0xff is the maximum (u8_t).
- */
-#if !defined TCP_DEFAULT_LISTEN_BACKLOG || defined __DOXYGEN__
-#define TCP_DEFAULT_LISTEN_BACKLOG      0xff
-#endif
-
-/**
- * TCP_OVERSIZE: The maximum number of bytes that tcp_write may
- * allocate ahead of time in an attempt to create shorter pbuf chains
- * for transmission. The meaningful range is 0 to TCP_MSS. Some
- * suggested values are:
- *
- * 0:         Disable oversized allocation. Each tcp_write() allocates a new
-              pbuf (old behaviour).
- * 1:         Allocate size-aligned pbufs with minimal excess. Use this if your
- *            scatter-gather DMA requires aligned fragments.
- * 128:       Limit the pbuf/memory overhead to 20%.
- * TCP_MSS:   Try to create unfragmented TCP packets.
- * TCP_MSS/4: Try to create 4 fragments or less per TCP packet.
- */
-#if !defined TCP_OVERSIZE || defined __DOXYGEN__
-#define TCP_OVERSIZE                    TCP_MSS
-#endif
-
-/**
- * LWIP_TCP_TIMESTAMPS==1: support the TCP timestamp option.
- * The timestamp option is currently only used to help remote hosts, it is not
- * really used locally. Therefore, it is only enabled when a TS option is
- * received in the initial SYN packet from a remote host.
- */
-#if !defined LWIP_TCP_TIMESTAMPS || defined __DOXYGEN__
-#define LWIP_TCP_TIMESTAMPS             0
-#endif
-
-/**
- * TCP_WND_UPDATE_THRESHOLD: difference in window to trigger an
- * explicit window update
- */
-#if !defined TCP_WND_UPDATE_THRESHOLD || defined __DOXYGEN__
-#define TCP_WND_UPDATE_THRESHOLD        LWIP_MIN((TCP_WND / 4), (TCP_MSS * 4))
-#endif
-
-/**
- * LWIP_EVENT_API and LWIP_CALLBACK_API: Only one of these should be set to 1.
- *     LWIP_EVENT_API==1: The user defines lwip_tcp_event() to receive all
- *         events (accept, sent, etc) that happen in the system.
- *     LWIP_CALLBACK_API==1: The PCB callback function is called directly
- *         for the event. This is the default.
- */
-#if !defined(LWIP_EVENT_API) && !defined(LWIP_CALLBACK_API) || defined __DOXYGEN__
-#define LWIP_EVENT_API                  0
-#define LWIP_CALLBACK_API               1
-#else
-#ifndef LWIP_EVENT_API
-#define LWIP_EVENT_API                  0
-#endif
-#ifndef LWIP_CALLBACK_API
-#define LWIP_CALLBACK_API               0
-#endif
-#endif
-
-/**
- * LWIP_WND_SCALE and TCP_RCV_SCALE:
- * Set LWIP_WND_SCALE to 1 to enable window scaling.
- * Set TCP_RCV_SCALE to the desired scaling factor (shift count in the
- * range of [0..14]).
- * When LWIP_WND_SCALE is enabled but TCP_RCV_SCALE is 0, we can use a large
- * send window while having a small receive window only.
- */
-#if !defined LWIP_WND_SCALE || defined __DOXYGEN__
-#define LWIP_WND_SCALE                  0
-#define TCP_RCV_SCALE                   0
-#endif
-
-/**
- * LWIP_TCP_PCB_NUM_EXT_ARGS:
- * When this is > 0, every tcp pcb (including listen pcb) includes a number of
- * additional argument entries in an array (see tcp_ext_arg_alloc_id)
- */
-#if !defined LWIP_TCP_PCB_NUM_EXT_ARGS || defined __DOXYGEN__
-#define LWIP_TCP_PCB_NUM_EXT_ARGS       0
-#endif
-
-/** LWIP_ALTCP==1: enable the altcp API.
- * altcp is an abstraction layer that prevents applications linking against the
- * tcp.h functions but provides the same functionality. It is used to e.g. add
- * SSL/TLS or proxy-connect support to an application written for the tcp callback
- * API without that application knowing the protocol details.
- *
- * With LWIP_ALTCP==0, applications written against the altcp API can still be
- * compiled but are directly linked against the tcp.h callback API and then
- * cannot use layered protocols.
- *
- * See @ref altcp_api
- */
-#if !defined LWIP_ALTCP || defined __DOXYGEN__
-#define LWIP_ALTCP                      0
-#endif
-
-/** LWIP_ALTCP_TLS==1: enable TLS support for altcp API.
- * This needs a port of the functions in altcp_tls.h to a TLS library.
- * A port to ARM mbedtls is provided with lwIP, see apps/altcp_tls/ directory
- * and LWIP_ALTCP_TLS_MBEDTLS option.
- */
-#if !defined LWIP_ALTCP_TLS || defined __DOXYGEN__
-#define LWIP_ALTCP_TLS                  0
-#endif
-
-#if ESP_LWIP
-#if !defined LWIP_TCP_RTO_TIME || defined __DOXYGEN__
-#define LWIP_TCP_RTO_TIME             3000
-#endif
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------
-   ---------- Pbuf options ----------
-   ----------------------------------
-*/
-/**
- * @defgroup lwip_opts_pbuf PBUF
- * @ingroup lwip_opts
- * @{
- */
-/**
- * PBUF_LINK_HLEN: the number of bytes that should be allocated for a
- * link level header. The default is 14, the standard value for
- * Ethernet.
- */
-#if !defined PBUF_LINK_HLEN || defined __DOXYGEN__
-#if defined LWIP_HOOK_VLAN_SET && !defined __DOXYGEN__
-#define PBUF_LINK_HLEN                  (18 + ETH_PAD_SIZE)
-#else /* LWIP_HOOK_VLAN_SET */
-#define PBUF_LINK_HLEN                  (14 + ETH_PAD_SIZE)
-#endif /* LWIP_HOOK_VLAN_SET */
-#endif
-
-/**
- * PBUF_LINK_ENCAPSULATION_HLEN: the number of bytes that should be allocated
- * for an additional encapsulation header before ethernet headers (e.g. 802.11)
- */
-#if !defined PBUF_LINK_ENCAPSULATION_HLEN || defined __DOXYGEN__
-#define PBUF_LINK_ENCAPSULATION_HLEN    0
-#endif
-
-/**
- * PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. The default is
- * designed to accommodate single full size TCP frame in one pbuf, including
- * TCP_MSS, IP header, and link header.
- */
-#if !defined PBUF_POOL_BUFSIZE || defined __DOXYGEN__
-#define PBUF_POOL_BUFSIZE               LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)
-#endif
-
-/**
- * LWIP_PBUF_REF_T: Refcount type in pbuf.
- * Default width of u8_t can be increased if 255 refs are not enough for you.
- */
-#if !defined LWIP_PBUF_REF_T || defined __DOXYGEN__
-#define LWIP_PBUF_REF_T                 u8_t
-#endif
-
-/**
- * LWIP_PBUF_CUSTOM_DATA: Store private data on pbufs (e.g. timestamps)
- * This extends struct pbuf so user can store custom data on every pbuf.
- */
-#if !defined LWIP_PBUF_CUSTOM_DATA || defined __DOXYGEN__
-#define LWIP_PBUF_CUSTOM_DATA
-#endif
-/**
- * @}
- */
-
-/*
-   ------------------------------------------------
-   ---------- Network Interfaces options ----------
-   ------------------------------------------------
-*/
-/**
- * @defgroup lwip_opts_netif NETIF
- * @ingroup lwip_opts
- * @{
- */
-/**
- * LWIP_SINGLE_NETIF==1: use a single netif only. This is the common case for
- * small real-life targets. Some code like routing etc. can be left out.
- */
-#if !defined LWIP_SINGLE_NETIF || defined __DOXYGEN__
-#define LWIP_SINGLE_NETIF               0
-#endif
-
-/**
- * LWIP_NETIF_HOSTNAME==1: use DHCP_OPTION_HOSTNAME with netif's hostname
- * field.
- */
-#if !defined LWIP_NETIF_HOSTNAME || defined __DOXYGEN__
-#define LWIP_NETIF_HOSTNAME             0
-#endif
-
-/**
- * LWIP_NETIF_API==1: Support netif api (in netifapi.c)
- */
-#if !defined LWIP_NETIF_API || defined __DOXYGEN__
-#define LWIP_NETIF_API                  0
-#endif
-
-/**
- * LWIP_NETIF_STATUS_CALLBACK==1: Support a callback function whenever an interface
- * changes its up/down status (i.e., due to DHCP IP acquisition)
- */
-#if !defined LWIP_NETIF_STATUS_CALLBACK || defined __DOXYGEN__
-#define LWIP_NETIF_STATUS_CALLBACK      0
-#endif
-
-/**
- * LWIP_NETIF_EXT_STATUS_CALLBACK==1: Support an extended callback function 
- * for several netif related event that supports multiple subscribers.
- * @see netif_ext_status_callback
- */
-#if !defined LWIP_NETIF_EXT_STATUS_CALLBACK || defined __DOXYGEN__
-#define LWIP_NETIF_EXT_STATUS_CALLBACK  0
-#endif
-
-/**
- * LWIP_NETIF_LINK_CALLBACK==1: Support a callback function from an interface
- * whenever the link changes (i.e., link down)
- */
-#if !defined LWIP_NETIF_LINK_CALLBACK || defined __DOXYGEN__
-#define LWIP_NETIF_LINK_CALLBACK        0
-#endif
-
-/**
- * LWIP_NETIF_REMOVE_CALLBACK==1: Support a callback function that is called
- * when a netif has been removed
- */
-#if !defined LWIP_NETIF_REMOVE_CALLBACK || defined __DOXYGEN__
-#define LWIP_NETIF_REMOVE_CALLBACK      0
-#endif
-
-/**
- * LWIP_NETIF_HWADDRHINT==1: Cache link-layer-address hints (e.g. table
- * indices) in struct netif. TCP and UDP can make use of this to prevent
- * scanning the ARP table for every sent packet. While this is faster for big
- * ARP tables or many concurrent connections, it might be counterproductive
- * if you have a tiny ARP table or if there never are concurrent connections.
- */
-#if !defined LWIP_NETIF_HWADDRHINT || defined __DOXYGEN__
-#define LWIP_NETIF_HWADDRHINT           0
-#endif
-
-/**
- * LWIP_NETIF_TX_SINGLE_PBUF: if this is set to 1, lwIP *tries* to put all data
- * to be sent into one single pbuf. This is for compatibility with DMA-enabled
- * MACs that do not support scatter-gather.
- * Beware that this might involve CPU-memcpy before transmitting that would not
- * be needed without this flag! Use this only if you need to!
- *
- * ATTENTION: a driver should *NOT* rely on getting single pbufs but check TX
- * pbufs for being in one piece. If not, @ref pbuf_clone can be used to get
- * a single pbuf:
- *   if (p->next != NULL) {
- *     struct pbuf *q = pbuf_clone(PBUF_RAW, PBUF_RAM, p);
- *     if (q == NULL) {
- *       return ERR_MEM;
- *     }
- *     p = q; ATTENTION: do NOT free the old 'p' as the ref belongs to the caller!
- *   }
- */
-#if !defined LWIP_NETIF_TX_SINGLE_PBUF || defined __DOXYGEN__
-#define LWIP_NETIF_TX_SINGLE_PBUF       0
-#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
-
-/**
- * LWIP_NUM_NETIF_CLIENT_DATA: Number of clients that may store
- * data in client_data member array of struct netif (max. 256).
- */
-#if !defined LWIP_NUM_NETIF_CLIENT_DATA || defined __DOXYGEN__
-#define LWIP_NUM_NETIF_CLIENT_DATA      0
-#endif
-/**
- * @}
- */
-
-/*
-   ------------------------------------
-   ---------- LOOPIF options ----------
-   ------------------------------------
-*/
-/**
- * @defgroup lwip_opts_loop Loopback interface
- * @ingroup lwip_opts_netif
- * @{
- */
-/**
- * LWIP_HAVE_LOOPIF==1: Support loop interface (127.0.0.1).
- * This is only needed when no real netifs are available. If at least one other
- * netif is available, loopback traffic uses this netif.
- */
-#if !defined LWIP_HAVE_LOOPIF || defined __DOXYGEN__
-#define LWIP_HAVE_LOOPIF                (LWIP_NETIF_LOOPBACK && !LWIP_SINGLE_NETIF)
-#endif
-
-/**
- * LWIP_LOOPIF_MULTICAST==1: Support multicast/IGMP on loop interface (127.0.0.1).
- */
-#if !defined LWIP_LOOPIF_MULTICAST || defined __DOXYGEN__
-#define LWIP_LOOPIF_MULTICAST           0
-#endif
-
-/**
- * LWIP_NETIF_LOOPBACK==1: Support sending packets with a destination IP
- * address equal to the netif IP address, looping them back up the stack.
- */
-#if !defined LWIP_NETIF_LOOPBACK || defined __DOXYGEN__
-#define LWIP_NETIF_LOOPBACK             0
-#endif
-
-/**
- * LWIP_LOOPBACK_MAX_PBUFS: Maximum number of pbufs on queue for loopback
- * sending for each netif (0 = disabled)
- */
-#if !defined LWIP_LOOPBACK_MAX_PBUFS || defined __DOXYGEN__
-#define LWIP_LOOPBACK_MAX_PBUFS         0
-#endif
-
-/**
- * LWIP_NETIF_LOOPBACK_MULTITHREADING: Indicates whether threading is enabled in
- * the system, as netifs must change how they behave depending on this setting
- * for the LWIP_NETIF_LOOPBACK option to work.
- * Setting this is needed to avoid reentering non-reentrant functions like
- * tcp_input().
- *    LWIP_NETIF_LOOPBACK_MULTITHREADING==1: Indicates that the user is using a
- *       multithreaded environment like tcpip.c. In this case, netif->input()
- *       is called directly.
- *    LWIP_NETIF_LOOPBACK_MULTITHREADING==0: Indicates a polling (or NO_SYS) setup.
- *       The packets are put on a list and netif_poll() must be called in
- *       the main application loop.
- */
-#if !defined LWIP_NETIF_LOOPBACK_MULTITHREADING || defined __DOXYGEN__
-#define LWIP_NETIF_LOOPBACK_MULTITHREADING    (!NO_SYS)
-#endif
-/**
- * @}
- */
-
-/*
-   ------------------------------------
-   ---------- Thread options ----------
-   ------------------------------------
-*/
-/**
- * @defgroup lwip_opts_thread Threading
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * TCPIP_THREAD_NAME: The name assigned to the main tcpip thread.
- */
-#if !defined TCPIP_THREAD_NAME || defined __DOXYGEN__
-#define TCPIP_THREAD_NAME               "tcpip_thread"
-#endif
-
-/**
- * TCPIP_THREAD_STACKSIZE: The stack size used by the main tcpip thread.
- * The stack size value itself is platform-dependent, but is passed to
- * sys_thread_new() when the thread is created.
- */
-#if !defined TCPIP_THREAD_STACKSIZE || defined __DOXYGEN__
-#define TCPIP_THREAD_STACKSIZE          0
-#endif
-
-/**
- * TCPIP_THREAD_PRIO: The priority assigned to the main tcpip thread.
- * The priority value itself is platform-dependent, but is passed to
- * sys_thread_new() when the thread is created.
- */
-#if !defined TCPIP_THREAD_PRIO || defined __DOXYGEN__
-#define TCPIP_THREAD_PRIO               1
-#endif
-
-/**
- * TCPIP_MBOX_SIZE: The mailbox size for the tcpip thread messages
- * The queue size value itself is platform-dependent, but is passed to
- * sys_mbox_new() when tcpip_init is called.
- */
-#if !defined TCPIP_MBOX_SIZE || defined __DOXYGEN__
-#define TCPIP_MBOX_SIZE                 0
-#endif
-
-/**
- * Define this to something that triggers a watchdog. This is called from
- * tcpip_thread after processing a message.
- */
-#if !defined LWIP_TCPIP_THREAD_ALIVE || defined __DOXYGEN__
-#define LWIP_TCPIP_THREAD_ALIVE()
-#endif
-
-/**
- * SLIPIF_THREAD_NAME: The name assigned to the slipif_loop thread.
- */
-#if !defined SLIPIF_THREAD_NAME || defined __DOXYGEN__
-#define SLIPIF_THREAD_NAME              "slipif_loop"
-#endif
-
-/**
- * SLIP_THREAD_STACKSIZE: The stack size used by the slipif_loop thread.
- * The stack size value itself is platform-dependent, but is passed to
- * sys_thread_new() when the thread is created.
- */
-#if !defined SLIPIF_THREAD_STACKSIZE || defined __DOXYGEN__
-#define SLIPIF_THREAD_STACKSIZE         0
-#endif
-
-/**
- * SLIPIF_THREAD_PRIO: The priority assigned to the slipif_loop thread.
- * The priority value itself is platform-dependent, but is passed to
- * sys_thread_new() when the thread is created.
- */
-#if !defined SLIPIF_THREAD_PRIO || defined __DOXYGEN__
-#define SLIPIF_THREAD_PRIO              1
-#endif
-
-/**
- * DEFAULT_THREAD_NAME: The name assigned to any other lwIP thread.
- */
-#if !defined DEFAULT_THREAD_NAME || defined __DOXYGEN__
-#define DEFAULT_THREAD_NAME             "lwIP"
-#endif
-
-/**
- * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread.
- * The stack size value itself is platform-dependent, but is passed to
- * sys_thread_new() when the thread is created.
- */
-#if !defined DEFAULT_THREAD_STACKSIZE || defined __DOXYGEN__
-#define DEFAULT_THREAD_STACKSIZE        0
-#endif
-
-/**
- * DEFAULT_THREAD_PRIO: The priority assigned to any other lwIP thread.
- * The priority value itself is platform-dependent, but is passed to
- * sys_thread_new() when the thread is created.
- */
-#if !defined DEFAULT_THREAD_PRIO || defined __DOXYGEN__
-#define DEFAULT_THREAD_PRIO             1
-#endif
-
-/**
- * DEFAULT_RAW_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
- * NETCONN_RAW. The queue size value itself is platform-dependent, but is passed
- * to sys_mbox_new() when the recvmbox is created.
- */
-#if !defined DEFAULT_RAW_RECVMBOX_SIZE || defined __DOXYGEN__
-#define DEFAULT_RAW_RECVMBOX_SIZE       0
-#endif
-
-/**
- * DEFAULT_UDP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
- * NETCONN_UDP. The queue size value itself is platform-dependent, but is passed
- * to sys_mbox_new() when the recvmbox is created.
- */
-#if !defined DEFAULT_UDP_RECVMBOX_SIZE || defined __DOXYGEN__
-#define DEFAULT_UDP_RECVMBOX_SIZE       0
-#endif
-
-/**
- * DEFAULT_TCP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
- * NETCONN_TCP. The queue size value itself is platform-dependent, but is passed
- * to sys_mbox_new() when the recvmbox is created.
- */
-#if !defined DEFAULT_TCP_RECVMBOX_SIZE || defined __DOXYGEN__
-#define DEFAULT_TCP_RECVMBOX_SIZE       0
-#endif
-
-/**
- * DEFAULT_ACCEPTMBOX_SIZE: The mailbox size for the incoming connections.
- * The queue size value itself is platform-dependent, but is passed to
- * sys_mbox_new() when the acceptmbox is created.
- */
-#if !defined DEFAULT_ACCEPTMBOX_SIZE || defined __DOXYGEN__
-#define DEFAULT_ACCEPTMBOX_SIZE         0
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------------------
-   ---------- Sequential layer options ----------
-   ----------------------------------------------
-*/
-/**
- * @defgroup lwip_opts_netconn Netconn
- * @ingroup lwip_opts_threadsafe_apis
- * @{
- */
-/**
- * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
- */
-#if !defined LWIP_NETCONN || defined __DOXYGEN__
-#define LWIP_NETCONN                    1
-#endif
-
-/** LWIP_TCPIP_TIMEOUT==1: Enable tcpip_timeout/tcpip_untimeout to create
- * timers running in tcpip_thread from another thread.
- */
-#if !defined LWIP_TCPIP_TIMEOUT || defined __DOXYGEN__
-#define LWIP_TCPIP_TIMEOUT              0
-#endif
-
-/** LWIP_NETCONN_SEM_PER_THREAD==1: Use one (thread-local) semaphore per
- * thread calling socket/netconn functions instead of allocating one
- * semaphore per netconn (and per select etc.)
- * ATTENTION: a thread-local semaphore for API calls is needed:
- * - LWIP_NETCONN_THREAD_SEM_GET() returning a sys_sem_t*
- * - LWIP_NETCONN_THREAD_SEM_ALLOC() creating the semaphore
- * - LWIP_NETCONN_THREAD_SEM_FREE() freeing the semaphore
- * The latter 2 can be invoked up by calling netconn_thread_init()/netconn_thread_cleanup().
- * Ports may call these for threads created with sys_thread_new().
- */
-#if !defined LWIP_NETCONN_SEM_PER_THREAD || defined __DOXYGEN__
-#define LWIP_NETCONN_SEM_PER_THREAD     0
-#endif
-
-/** LWIP_NETCONN_FULLDUPLEX==1: Enable code that allows reading from one thread,
- * writing from a 2nd thread and closing from a 3rd thread at the same time.
- * ATTENTION: This is currently really alpha! Some requirements:
- * - LWIP_NETCONN_SEM_PER_THREAD==1 is required to use one socket/netconn from
- *   multiple threads at once
- * - sys_mbox_free() has to unblock receive tasks waiting on recvmbox/acceptmbox
- *   and prevent a task pending on this during/after deletion
- */
-#if !defined LWIP_NETCONN_FULLDUPLEX || defined __DOXYGEN__
-#define LWIP_NETCONN_FULLDUPLEX         0
-#endif
-/**
- * @}
- */
-
-/*
-   ------------------------------------
-   ---------- Socket options ----------
-   ------------------------------------
-*/
-/**
- * @defgroup lwip_opts_socket Sockets
- * @ingroup lwip_opts_threadsafe_apis
- * @{
- */
-/**
- * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
- */
-#if !defined LWIP_SOCKET || defined __DOXYGEN__
-#define LWIP_SOCKET                     1
-#endif
-
-/**
- * LWIP_COMPAT_SOCKETS==1: Enable BSD-style sockets functions names through defines.
- * LWIP_COMPAT_SOCKETS==2: Same as ==1 but correctly named functions are created.
- * While this helps code completion, it might conflict with existing libraries.
- * (only used if you use sockets.c)
- */
-#if !defined LWIP_COMPAT_SOCKETS || defined __DOXYGEN__
-#define LWIP_COMPAT_SOCKETS             1
-#endif
-
-/**
- * LWIP_POSIX_SOCKETS_IO_NAMES==1: Enable POSIX-style sockets functions names.
- * Disable this option if you use a POSIX operating system that uses the same
- * names (read, write & close). (only used if you use sockets.c)
- */
-#if !defined LWIP_POSIX_SOCKETS_IO_NAMES || defined __DOXYGEN__
-#define LWIP_POSIX_SOCKETS_IO_NAMES     1
-#endif
-
-/**
- * LWIP_SOCKET_OFFSET==n: Increases the file descriptor number created by LwIP with n.
- * This can be useful when there are multiple APIs which create file descriptors.
- * When they all start with a different offset and you won't make them overlap you can
- * re implement read/write/close/ioctl/fnctl to send the requested action to the right
- * library (sharing select will need more work though).
- */
-#if !defined LWIP_SOCKET_OFFSET || defined __DOXYGEN__
-#define LWIP_SOCKET_OFFSET              0
-#endif
-
-/**
- * LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
- * options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set
- * in seconds. (does not require sockets.c, and will affect tcp.c)
- */
-#if !defined LWIP_TCP_KEEPALIVE || defined __DOXYGEN__
-#define LWIP_TCP_KEEPALIVE              0
-#endif
-
-/**
- * LWIP_SO_SNDTIMEO==1: Enable send timeout for sockets/netconns and
- * SO_SNDTIMEO processing.
- */
-#if !defined LWIP_SO_SNDTIMEO || defined __DOXYGEN__
-#define LWIP_SO_SNDTIMEO                0
-#endif
-
-/**
- * LWIP_SO_RCVTIMEO==1: Enable receive timeout for sockets/netconns and
- * SO_RCVTIMEO processing.
- */
-#if !defined LWIP_SO_RCVTIMEO || defined __DOXYGEN__
-#define LWIP_SO_RCVTIMEO                0
-#endif
-
-/**
- * LWIP_SO_SNDRCVTIMEO_NONSTANDARD==1: SO_RCVTIMEO/SO_SNDTIMEO take an int
- * (milliseconds, much like winsock does) instead of a struct timeval (default).
- */
-#if !defined LWIP_SO_SNDRCVTIMEO_NONSTANDARD || defined __DOXYGEN__
-#define LWIP_SO_SNDRCVTIMEO_NONSTANDARD 0
-#endif
-
-/**
- * LWIP_SO_RCVBUF==1: Enable SO_RCVBUF processing.
- */
-#if !defined LWIP_SO_RCVBUF || defined __DOXYGEN__
-#define LWIP_SO_RCVBUF                  0
-#endif
-
-/**
- * LWIP_SO_LINGER==1: Enable SO_LINGER processing.
- */
-#if !defined LWIP_SO_LINGER || defined __DOXYGEN__
-#define LWIP_SO_LINGER                  0
-#endif
-
-/**
- * If LWIP_SO_RCVBUF is used, this is the default value for recv_bufsize.
- */
-#if !defined RECV_BUFSIZE_DEFAULT || defined __DOXYGEN__
-#define RECV_BUFSIZE_DEFAULT            INT_MAX
-#endif
-
-/**
- * By default, TCP socket/netconn close waits 20 seconds max to send the FIN
- */
-#if !defined LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT || defined __DOXYGEN__
-#define LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT 20000
-#endif
-
-/**
- * SO_REUSE==1: Enable SO_REUSEADDR option.
- */
-#if !defined SO_REUSE || defined __DOXYGEN__
-#define SO_REUSE                        0
-#endif
-
-/**
- * SO_REUSE_RXTOALL==1: Pass a copy of incoming broadcast/multicast packets
- * to all local matches if SO_REUSEADDR is turned on.
- * WARNING: Adds a memcpy for every packet if passing to more than one pcb!
- */
-#if !defined SO_REUSE_RXTOALL || defined __DOXYGEN__
-#define SO_REUSE_RXTOALL                0
-#endif
-
-/**
- * LWIP_FIONREAD_LINUXMODE==0 (default): ioctl/FIONREAD returns the amount of
- * pending data in the network buffer. This is the way windows does it. It's
- * the default for lwIP since it is smaller.
- * LWIP_FIONREAD_LINUXMODE==1: ioctl/FIONREAD returns the size of the next
- * pending datagram in bytes. This is the way linux does it. This code is only
- * here for compatibility.
- */
-#if !defined LWIP_FIONREAD_LINUXMODE || defined __DOXYGEN__
-#define LWIP_FIONREAD_LINUXMODE         0
-#endif
-
-/**
- * LWIP_SOCKET_SELECT==1 (default): enable select() for sockets (uses a netconn
- * callback to keep track of events).
- * This saves RAM (counters per socket) and code (netconn event callback), which
- * should improve performance a bit).
- */
-#if !defined LWIP_SOCKET_SELECT || defined __DOXYGEN__
-#define LWIP_SOCKET_SELECT              1
-#endif
-
-/**
- * LWIP_SOCKET_POLL==1 (default): enable poll() for sockets (including
- * struct pollfd, nfds_t, and constants)
- */
-#if !defined LWIP_SOCKET_POLL || defined __DOXYGEN__
-#define LWIP_SOCKET_POLL                1
-#endif
-/**
- * @}
- */
-
-/*
-   ----------------------------------------
-   ---------- Statistics options ----------
-   ----------------------------------------
-*/
-/**
- * @defgroup lwip_opts_stats Statistics
- * @ingroup lwip_opts_debug
- * @{
- */
-/**
- * LWIP_STATS==1: Enable statistics collection in lwip_stats.
- */
-#if !defined LWIP_STATS || defined __DOXYGEN__
-#define LWIP_STATS                      1
-#endif
-
-#if LWIP_STATS
-
-/**
- * LWIP_STATS_DISPLAY==1: Compile in the statistics output functions.
- */
-#if !defined LWIP_STATS_DISPLAY || defined __DOXYGEN__
-#define LWIP_STATS_DISPLAY              0
-#endif
-
-/**
- * LINK_STATS==1: Enable link stats.
- */
-#if !defined LINK_STATS || defined __DOXYGEN__
-#define LINK_STATS                      1
-#endif
-
-/**
- * ETHARP_STATS==1: Enable etharp stats.
- */
-#if !defined ETHARP_STATS || defined __DOXYGEN__
-#define ETHARP_STATS                    (LWIP_ARP)
-#endif
-
-/**
- * IP_STATS==1: Enable IP stats.
- */
-#if !defined IP_STATS || defined __DOXYGEN__
-#define IP_STATS                        1
-#endif
-
-/**
- * IPFRAG_STATS==1: Enable IP fragmentation stats. Default is
- * on if using either frag or reass.
- */
-#if !defined IPFRAG_STATS || defined __DOXYGEN__
-#define IPFRAG_STATS                    (IP_REASSEMBLY || IP_FRAG)
-#endif
-
-/**
- * ICMP_STATS==1: Enable ICMP stats.
- */
-#if !defined ICMP_STATS || defined __DOXYGEN__
-#define ICMP_STATS                      1
-#endif
-
-/**
- * IGMP_STATS==1: Enable IGMP stats.
- */
-#if !defined IGMP_STATS || defined __DOXYGEN__
-#define IGMP_STATS                      (LWIP_IGMP)
-#endif
-
-/**
- * UDP_STATS==1: Enable UDP stats. Default is on if
- * UDP enabled, otherwise off.
- */
-#if !defined UDP_STATS || defined __DOXYGEN__
-#define UDP_STATS                       (LWIP_UDP)
-#endif
-
-/**
- * TCP_STATS==1: Enable TCP stats. Default is on if TCP
- * enabled, otherwise off.
- */
-#if !defined TCP_STATS || defined __DOXYGEN__
-#define TCP_STATS                       (LWIP_TCP)
-#endif
-
-/**
- * MEM_STATS==1: Enable mem.c stats.
- */
-#if !defined MEM_STATS || defined __DOXYGEN__
-#define MEM_STATS                       ((MEM_LIBC_MALLOC == 0) && (MEM_USE_POOLS == 0))
-#endif
-
-/**
- * MEMP_STATS==1: Enable memp.c pool stats.
- */
-#if !defined MEMP_STATS || defined __DOXYGEN__
-#define MEMP_STATS                      (MEMP_MEM_MALLOC == 0)
-#endif
-
-/**
- * SYS_STATS==1: Enable system stats (sem and mbox counts, etc).
- */
-#if !defined SYS_STATS || defined __DOXYGEN__
-#define SYS_STATS                       (NO_SYS == 0)
-#endif
-
-/**
- * IP6_STATS==1: Enable IPv6 stats.
- */
-#if !defined IP6_STATS || defined __DOXYGEN__
-#define IP6_STATS                       (LWIP_IPV6)
-#endif
-
-/**
- * ICMP6_STATS==1: Enable ICMP for IPv6 stats.
- */
-#if !defined ICMP6_STATS || defined __DOXYGEN__
-#define ICMP6_STATS                     (LWIP_IPV6 && LWIP_ICMP6)
-#endif
-
-/**
- * IP6_FRAG_STATS==1: Enable IPv6 fragmentation stats.
- */
-#if !defined IP6_FRAG_STATS || defined __DOXYGEN__
-#define IP6_FRAG_STATS                  (LWIP_IPV6 && (LWIP_IPV6_FRAG || LWIP_IPV6_REASS))
-#endif
-
-/**
- * MLD6_STATS==1: Enable MLD for IPv6 stats.
- */
-#if !defined MLD6_STATS || defined __DOXYGEN__
-#define MLD6_STATS                      (LWIP_IPV6 && LWIP_IPV6_MLD)
-#endif
-
-/**
- * ND6_STATS==1: Enable Neighbor discovery for IPv6 stats.
- */
-#if !defined ND6_STATS || defined __DOXYGEN__
-#define ND6_STATS                       (LWIP_IPV6)
-#endif
-
-/**
- * MIB2_STATS==1: Stats for SNMP MIB2.
- */
-#if !defined MIB2_STATS || defined __DOXYGEN__
-#define MIB2_STATS                      0
-#endif
-
-/**
- * IP_NAPT_STATS==1: Stats for IP NAPT.
- */
-#if !defined IP_NAPT_STATS || defined __DOXYGEN__
-#define IP_NAPT_STATS                   (IP_NAPT)
-#endif
-
-
-#else
-
-#define LINK_STATS                      0
-#define ETHARP_STATS                    0
-#define IP_STATS                        0
-#define IPFRAG_STATS                    0
-#define ICMP_STATS                      0
-#define IGMP_STATS                      0
-#define UDP_STATS                       0
-#define TCP_STATS                       0
-#define MEM_STATS                       0
-#define MEMP_STATS                      0
-#define SYS_STATS                       0
-#define LWIP_STATS_DISPLAY              0
-#define IP6_STATS                       0
-#define ICMP6_STATS                     0
-#define IP6_FRAG_STATS                  0
-#define MLD6_STATS                      0
-#define ND6_STATS                       0
-#define MIB2_STATS                      0
-#define IP_NAPT_STATS                   0
-
-#endif /* LWIP_STATS */
-/**
- * @}
- */
-
-/*
-   --------------------------------------
-   ---------- Checksum options ----------
-   --------------------------------------
-*/
-/**
- * @defgroup lwip_opts_checksum Checksum
- * @ingroup lwip_opts_infrastructure
- * @{
- */
-/**
- * LWIP_CHECKSUM_CTRL_PER_NETIF==1: Checksum generation/check can be enabled/disabled
- * per netif.
- * ATTENTION: if enabled, the CHECKSUM_GEN_* and CHECKSUM_CHECK_* defines must be enabled!
- */
-#if !defined LWIP_CHECKSUM_CTRL_PER_NETIF || defined __DOXYGEN__
-#define LWIP_CHECKSUM_CTRL_PER_NETIF    0
-#endif
-
-/**
- * CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.
- */
-#if !defined CHECKSUM_GEN_IP || defined __DOXYGEN__
-#define CHECKSUM_GEN_IP                 1
-#endif
-
-/**
- * CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.
- */
-#if !defined CHECKSUM_GEN_UDP || defined __DOXYGEN__
-#define CHECKSUM_GEN_UDP                1
-#endif
-
-/**
- * CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.
- */
-#if !defined CHECKSUM_GEN_TCP || defined __DOXYGEN__
-#define CHECKSUM_GEN_TCP                1
-#endif
-
-/**
- * CHECKSUM_GEN_ICMP==1: Generate checksums in software for outgoing ICMP packets.
- */
-#if !defined CHECKSUM_GEN_ICMP || defined __DOXYGEN__
-#define CHECKSUM_GEN_ICMP               1
-#endif
-
-/**
- * CHECKSUM_GEN_ICMP6==1: Generate checksums in software for outgoing ICMP6 packets.
- */
-#if !defined CHECKSUM_GEN_ICMP6 || defined __DOXYGEN__
-#define CHECKSUM_GEN_ICMP6              1
-#endif
-
-/**
- * CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.
- */
-#if !defined CHECKSUM_CHECK_IP || defined __DOXYGEN__
-#define CHECKSUM_CHECK_IP               1
-#endif
-
-/**
- * CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.
- */
-#if !defined CHECKSUM_CHECK_UDP || defined __DOXYGEN__
-#define CHECKSUM_CHECK_UDP              1
-#endif
-
-/**
- * CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.
- */
-#if !defined CHECKSUM_CHECK_TCP || defined __DOXYGEN__
-#define CHECKSUM_CHECK_TCP              1
-#endif
-
-/**
- * CHECKSUM_CHECK_ICMP==1: Check checksums in software for incoming ICMP packets.
- */
-#if !defined CHECKSUM_CHECK_ICMP || defined __DOXYGEN__
-#define CHECKSUM_CHECK_ICMP             1
-#endif
-
-/**
- * CHECKSUM_CHECK_ICMP6==1: Check checksums in software for incoming ICMPv6 packets
- */
-#if !defined CHECKSUM_CHECK_ICMP6 || defined __DOXYGEN__
-#define CHECKSUM_CHECK_ICMP6            1
-#endif
-
-/**
- * LWIP_CHECKSUM_ON_COPY==1: Calculate checksum when copying data from
- * application buffers to pbufs.
- */
-#if !defined LWIP_CHECKSUM_ON_COPY || defined __DOXYGEN__
-#define LWIP_CHECKSUM_ON_COPY           0
-#endif
-/**
- * @}
- */
-
-/*
-   ---------------------------------------
-   ---------- IPv6 options ---------------
-   ---------------------------------------
-*/
-/**
- * @defgroup lwip_opts_ipv6 IPv6
- * @ingroup lwip_opts
- * @{
- */
-/**
- * LWIP_IPV6==1: Enable IPv6
- */
-#if !defined LWIP_IPV6 || defined __DOXYGEN__
-#define LWIP_IPV6                       0
-#endif
-
-/**
- * IPV6_REASS_MAXAGE: Maximum time (in multiples of IP6_REASS_TMR_INTERVAL - so seconds, normally)
- * a fragmented IP packet waits for all fragments to arrive. If not all fragments arrived
- * in this time, the whole packet is discarded.
- */
-#if !defined IPV6_REASS_MAXAGE || defined __DOXYGEN__
-#define IPV6_REASS_MAXAGE               60
-#endif
-
-/**
- * LWIP_IPV6_SCOPES==1: Enable support for IPv6 address scopes, ensuring that
- * e.g. link-local addresses are really treated as link-local. Disable this
- * setting only for single-interface configurations.
- * All addresses that have a scope according to the default policy (link-local
- * unicast addresses, interface-local and link-local multicast addresses) should
- * now have a zone set on them before being passed to the core API, although
- * lwIP will currently attempt to select a zone on the caller's behalf when 
- * necessary. Applications that directly assign IPv6 addresses to interfaces
- * (which is NOT recommended) must now ensure that link-local addresses carry
- * the netif's zone. See the new ip6_zone.h header file for more information and
- * relevant macros. For now it is still possible to turn off scopes support
- * through the new LWIP_IPV6_SCOPES option. When upgrading an implementation that
- * uses the core API directly, it is highly recommended to enable
- * LWIP_IPV6_SCOPES_DEBUG at least for a while, to ensure e.g. proper address
- * initialization.
- */
-#if !defined LWIP_IPV6_SCOPES || defined __DOXYGEN__
-#define LWIP_IPV6_SCOPES                (LWIP_IPV6 && !LWIP_SINGLE_NETIF)
-#endif
-
-/**
- * LWIP_IPV6_SCOPES_DEBUG==1: Perform run-time checks to verify that addresses
- * are properly zoned (see ip6_zone.h on what that means) where it matters.
- * Enabling this setting is highly recommended when upgrading from an existing
- * installation that is not yet scope-aware; otherwise it may be too expensive.
- */
-#if !defined LWIP_IPV6_SCOPES_DEBUG || defined __DOXYGEN__
-#define LWIP_IPV6_SCOPES_DEBUG          0
-#endif
-
-/**
- * LWIP_IPV6_NUM_ADDRESSES: Number of IPv6 addresses per netif.
- */
-#if !defined LWIP_IPV6_NUM_ADDRESSES || defined __DOXYGEN__
-#define LWIP_IPV6_NUM_ADDRESSES         3
-#endif
-
-/**
- * LWIP_IPV6_FORWARD==1: Forward IPv6 packets across netifs
- */
-#if !defined LWIP_IPV6_FORWARD || defined __DOXYGEN__
-#define LWIP_IPV6_FORWARD               0
-#endif
-
-/**
- * LWIP_IPV6_FRAG==1: Fragment outgoing IPv6 packets that are too big.
- */
-#if !defined LWIP_IPV6_FRAG || defined __DOXYGEN__
-#define LWIP_IPV6_FRAG                  1
-#endif
-
-/**
- * LWIP_IPV6_REASS==1: reassemble incoming IPv6 packets that fragmented
- */
-#if !defined LWIP_IPV6_REASS || defined __DOXYGEN__
-#define LWIP_IPV6_REASS                 LWIP_IPV6
-#endif
-
-/**
- * LWIP_IPV6_SEND_ROUTER_SOLICIT==1: Send router solicitation messages during
- * network startup.
- */
-#if !defined LWIP_IPV6_SEND_ROUTER_SOLICIT || defined __DOXYGEN__
-#define LWIP_IPV6_SEND_ROUTER_SOLICIT   1
-#endif
-
-/**
- * LWIP_IPV6_AUTOCONFIG==1: Enable stateless address autoconfiguration as per RFC 4862.
- */
-#if !defined LWIP_IPV6_AUTOCONFIG || defined __DOXYGEN__
-#define LWIP_IPV6_AUTOCONFIG            LWIP_IPV6
-#endif
-
-/**
- * LWIP_IPV6_ADDRESS_LIFETIMES==1: Keep valid and preferred lifetimes for each
- * IPv6 address. Required for LWIP_IPV6_AUTOCONFIG. May still be enabled
- * otherwise, in which case the application may assign address lifetimes with
- * the appropriate macros. Addresses with no lifetime are assumed to be static.
- * If this option is disabled, all addresses are assumed to be static.
- */
-#if !defined LWIP_IPV6_ADDRESS_LIFETIMES || defined __DOXYGEN__
-#define LWIP_IPV6_ADDRESS_LIFETIMES     LWIP_IPV6_AUTOCONFIG
-#endif
-
-/**
- * LWIP_IPV6_DUP_DETECT_ATTEMPTS=[0..7]: Number of duplicate address detection attempts.
- */
-#if !defined LWIP_IPV6_DUP_DETECT_ATTEMPTS || defined __DOXYGEN__
-#define LWIP_IPV6_DUP_DETECT_ATTEMPTS   1
-#endif
-/**
- * @}
- */
-
-/**
- * @defgroup lwip_opts_icmp6 ICMP6
- * @ingroup lwip_opts_ipv6
- * @{
- */
-/**
- * LWIP_ICMP6==1: Enable ICMPv6 (mandatory per RFC)
- */
-#if !defined LWIP_ICMP6 || defined __DOXYGEN__
-#define LWIP_ICMP6                      LWIP_IPV6
-#endif
-
-/**
- * LWIP_ICMP6_DATASIZE: bytes from original packet to send back in
- * ICMPv6 error messages (0 = default of IP6_MIN_MTU_LENGTH)
- * ATTENTION: RFC4443 section 2.4 says IP6_MIN_MTU_LENGTH is a MUST,
- * so override this only if you absolutely have to!
- */
-#if !defined LWIP_ICMP6_DATASIZE || defined __DOXYGEN__
-#define LWIP_ICMP6_DATASIZE             0
-#endif
-
-/**
- * LWIP_ICMP6_HL: default hop limit for ICMPv6 messages
- */
-#if !defined LWIP_ICMP6_HL || defined __DOXYGEN__
-#define LWIP_ICMP6_HL                   255
-#endif
-/**
- * @}
- */
-
-/**
- * @defgroup lwip_opts_mld6 Multicast listener discovery
- * @ingroup lwip_opts_ipv6
- * @{
- */
-/**
- * LWIP_IPV6_MLD==1: Enable multicast listener discovery protocol.
- * If LWIP_IPV6 is enabled but this setting is disabled, the MAC layer must
- * indiscriminately pass all inbound IPv6 multicast traffic to lwIP.
- */
-#if !defined LWIP_IPV6_MLD || defined __DOXYGEN__
-#define LWIP_IPV6_MLD                   LWIP_IPV6
-#endif
-
-/**
- * MEMP_NUM_MLD6_GROUP: Max number of IPv6 multicast groups that can be joined.
- * There must be enough groups so that each netif can join the solicited-node
- * multicast group for each of its local addresses, plus one for MDNS if
- * applicable, plus any number of groups to be joined on UDP sockets.
- */
-#if !defined MEMP_NUM_MLD6_GROUP || defined __DOXYGEN__
-#define MEMP_NUM_MLD6_GROUP             4
-#endif
-/**
- * @}
- */
-
-/**
- * @defgroup lwip_opts_nd6 Neighbor discovery
- * @ingroup lwip_opts_ipv6
- * @{
- */
-/**
- * LWIP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address
- * is being resolved.
- */
-#if !defined LWIP_ND6_QUEUEING || defined __DOXYGEN__
-#define LWIP_ND6_QUEUEING               LWIP_IPV6
-#endif
-
-/**
- * ESP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address
- * is being resolved.
- */
-#if !defined ESP_ND6_QUEUEING || defined __DOXYGEN__
-#define ESP_ND6_QUEUEING               LWIP_IPV6
-#endif
-
-/**
- * MEMP_NUM_ND6_QUEUE: Max number of IPv6 packets to queue during MAC resolution.
- */
-#if !defined MEMP_NUM_ND6_QUEUE || defined __DOXYGEN__
-#define MEMP_NUM_ND6_QUEUE              20
-#endif
-
-/**
- * LWIP_ND6_NUM_NEIGHBORS: Number of entries in IPv6 neighbor cache
- */
-#if !defined LWIP_ND6_NUM_NEIGHBORS || defined __DOXYGEN__
-#define LWIP_ND6_NUM_NEIGHBORS          10
-#endif
-
-/**
- * LWIP_ND6_NUM_DESTINATIONS: number of entries in IPv6 destination cache
- */
-#if !defined LWIP_ND6_NUM_DESTINATIONS || defined __DOXYGEN__
-#define LWIP_ND6_NUM_DESTINATIONS       10
-#endif
-
-/**
- * LWIP_ND6_NUM_PREFIXES: number of entries in IPv6 on-link prefixes cache
- */
-#if !defined LWIP_ND6_NUM_PREFIXES || defined __DOXYGEN__
-#define LWIP_ND6_NUM_PREFIXES           5
-#endif
-
-/**
- * LWIP_ND6_NUM_ROUTERS: number of entries in IPv6 default router cache
- */
-#if !defined LWIP_ND6_NUM_ROUTERS || defined __DOXYGEN__
-#define LWIP_ND6_NUM_ROUTERS            3
-#endif
-
-/**
- * LWIP_ND6_MAX_MULTICAST_SOLICIT: max number of multicast solicit messages to send
- * (neighbor solicit and router solicit)
- */
-#if !defined LWIP_ND6_MAX_MULTICAST_SOLICIT || defined __DOXYGEN__
-#define LWIP_ND6_MAX_MULTICAST_SOLICIT  3
-#endif
-
-/**
- * LWIP_ND6_MAX_UNICAST_SOLICIT: max number of unicast neighbor solicitation messages
- * to send during neighbor reachability detection.
- */
-#if !defined LWIP_ND6_MAX_UNICAST_SOLICIT || defined __DOXYGEN__
-#define LWIP_ND6_MAX_UNICAST_SOLICIT    3
-#endif
-
-/**
- * Unused: See ND RFC (time in milliseconds).
- */
-#if !defined LWIP_ND6_MAX_ANYCAST_DELAY_TIME || defined __DOXYGEN__
-#define LWIP_ND6_MAX_ANYCAST_DELAY_TIME 1000
-#endif
-
-/**
- * Unused: See ND RFC
- */
-#if !defined LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT || defined __DOXYGEN__
-#define LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT  3
-#endif
-
-/**
- * LWIP_ND6_REACHABLE_TIME: default neighbor reachable time (in milliseconds).
- * May be updated by router advertisement messages.
- */
-#if !defined LWIP_ND6_REACHABLE_TIME || defined __DOXYGEN__
-#define LWIP_ND6_REACHABLE_TIME         30000
-#endif
-
-/**
- * LWIP_ND6_RETRANS_TIMER: default retransmission timer for solicitation messages
- */
-#if !defined LWIP_ND6_RETRANS_TIMER || defined __DOXYGEN__
-#define LWIP_ND6_RETRANS_TIMER          1000
-#endif
-
-/**
- * LWIP_ND6_DELAY_FIRST_PROBE_TIME: Delay before first unicast neighbor solicitation
- * message is sent, during neighbor reachability detection.
- */
-#if !defined LWIP_ND6_DELAY_FIRST_PROBE_TIME || defined __DOXYGEN__
-#define LWIP_ND6_DELAY_FIRST_PROBE_TIME 5000
-#endif
-
-/**
- * LWIP_ND6_ALLOW_RA_UPDATES==1: Allow Router Advertisement messages to update
- * Reachable time and retransmission timers, and netif MTU.
- */
-#if !defined LWIP_ND6_ALLOW_RA_UPDATES || defined __DOXYGEN__
-#define LWIP_ND6_ALLOW_RA_UPDATES       1
-#endif
-
-/**
- * LWIP_ND6_TCP_REACHABILITY_HINTS==1: Allow TCP to provide Neighbor Discovery
- * with reachability hints for connected destinations. This helps avoid sending
- * unicast neighbor solicitation messages.
- */
-#if !defined LWIP_ND6_TCP_REACHABILITY_HINTS || defined __DOXYGEN__
-#define LWIP_ND6_TCP_REACHABILITY_HINTS 1
-#endif
-
-/**
- * LWIP_ND6_RDNSS_MAX_DNS_SERVERS > 0: Use IPv6 Router Advertisement Recursive
- * DNS Server Option (as per RFC 6106) to copy a defined maximum number of DNS
- * servers to the DNS module.
- */
-#if !defined LWIP_ND6_RDNSS_MAX_DNS_SERVERS || defined __DOXYGEN__
-#define LWIP_ND6_RDNSS_MAX_DNS_SERVERS  0
-#endif
-/**
- * @}
- */
-
-/**
- * @defgroup lwip_opts_dhcpv6 DHCPv6
- * @ingroup lwip_opts_ipv6
- * @{
- */
-/**
- * LWIP_IPV6_DHCP6==1: enable DHCPv6 stateful/stateless address autoconfiguration.
- */
-#if !defined LWIP_IPV6_DHCP6 || defined __DOXYGEN__
-#define LWIP_IPV6_DHCP6                 0
-#endif
-
-/**
- * LWIP_IPV6_DHCP6_STATEFUL==1: enable DHCPv6 stateful address autoconfiguration.
- * (not supported, yet!)
- */
-#if !defined LWIP_IPV6_DHCP6_STATEFUL || defined __DOXYGEN__
-#define LWIP_IPV6_DHCP6_STATEFUL        0
-#endif
-
-/**
- * LWIP_IPV6_DHCP6_STATELESS==1: enable DHCPv6 stateless address autoconfiguration.
- */
-#if !defined LWIP_IPV6_DHCP6_STATELESS || defined __DOXYGEN__
-#define LWIP_IPV6_DHCP6_STATELESS       LWIP_IPV6_DHCP6
-#endif
-
-/**
- * LWIP_DHCP6_GETS_NTP==1: Request NTP servers via DHCPv6. For each
- * response packet, a callback is called, which has to be provided by the port:
- * void dhcp6_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs);
-*/
-#if !defined LWIP_DHCP6_GET_NTP_SRV || defined __DOXYGEN__
-#define LWIP_DHCP6_GET_NTP_SRV          0
-#endif
-
-/**
- * The maximum of NTP servers requested
- */
-#if !defined LWIP_DHCP6_MAX_NTP_SERVERS || defined __DOXYGEN__
-#define LWIP_DHCP6_MAX_NTP_SERVERS      1
-#endif
-
-/**
- * LWIP_DHCP6_MAX_DNS_SERVERS > 0: Request DNS servers via DHCPv6.
- * DNS servers received in the response are passed to DNS via @ref dns_setserver()
- * (up to the maximum limit defined here).
- */
-#if !defined LWIP_DHCP6_MAX_DNS_SERVERS || defined __DOXYGEN__
-#define LWIP_DHCP6_MAX_DNS_SERVERS      DNS_MAX_SERVERS
-#endif
-/**
- * @}
- */
-
-/*
-   ---------------------------------------
-   ---------- Hook options ---------------
-   ---------------------------------------
-*/
-
-/**
- * @defgroup lwip_opts_hooks Hooks
- * @ingroup lwip_opts_infrastructure
- * Hooks are undefined by default, define them to a function if you need them.
- * @{
- */
-
-/**
- * LWIP_HOOK_FILENAME: Custom filename to \#include in files that provide hooks.
- * Declare your hook function prototypes in there, you may also \#include all headers
- * providing data types that are need in this file.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_FILENAME "path/to/my/lwip_hooks.h"
-#endif
-
-/**
- * LWIP_HOOK_TCP_ISN:
- * Hook for generation of the Initial Sequence Number (ISN) for a new TCP
- * connection. The default lwIP ISN generation algorithm is very basic and may
- * allow for TCP spoofing attacks. This hook provides the means to implement
- * the standardized ISN generation algorithm from RFC 6528 (see contrib/adons/tcp_isn),
- * or any other desired algorithm as a replacement.
- * Called from tcp_connect() and tcp_listen_input() when an ISN is needed for
- * a new TCP connection, if TCP support (@ref LWIP_TCP) is enabled.\n
- * Signature:\code{.c}
- * u32_t my_hook_tcp_isn(const ip_addr_t* local_ip, u16_t local_port, const ip_addr_t* remote_ip, u16_t remote_port);
- * \endcode
- * - it may be necessary to use "struct ip_addr" (ip4_addr, ip6_addr) instead of "ip_addr_t" in function declarations\n
- * Arguments:
- * - local_ip: pointer to the local IP address of the connection
- * - local_port: local port number of the connection (host-byte order)
- * - remote_ip: pointer to the remote IP address of the connection
- * - remote_port: remote port number of the connection (host-byte order)\n
- * Return value:
- * - the 32-bit Initial Sequence Number to use for the new TCP connection.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_TCP_ISN(local_ip, local_port, remote_ip, remote_port)
-#endif
-
-/**
- * LWIP_HOOK_TCP_INPACKET_PCB:
- * Hook for intercepting incoming packets before they are passed to a pcb. This
- * allows updating some state or even dropping a packet.
- * Signature:\code{.c}
- * err_t my_hook_tcp_inpkt(struct tcp_pcb *pcb, struct tcp_hdr *hdr, u16_t optlen, u16_t opt1len, u8_t *opt2, struct pbuf *p);
- * \endcode
- * Arguments:
- * - pcb: tcp_pcb selected for input of this packet (ATTENTION: this may be
- *        struct tcp_pcb_listen if pcb->state == LISTEN)
- * - hdr: pointer to tcp header (ATTENTION: tcp options may not be in one piece!)
- * - optlen: tcp option length
- * - opt1len: tcp option length 1st part
- * - opt2: if this is != NULL, tcp options are split among 2 pbufs. In that case,
- *         options start at right after the tcp header ('(u8_t*)(hdr + 1)') for
- *         the first 'opt1len' bytes and the rest starts at 'opt2'. opt2len can
- *         be simply calculated: 'opt2len = optlen - opt1len;'
- * - p: input packet, p->payload points to application data (that's why tcp hdr
- *      and options are passed in seperately)
- * Return value:
- * - ERR_OK: continue input of this packet as normal
- * - != ERR_OK: drop this packet for input (don't continue input processing)
- *
- * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
- * state or any pcb lists) from this callback!
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_TCP_INPACKET_PCB(pcb, hdr, optlen, opt1len, opt2, p)
-#endif
-
-/**
- * LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH:
- * Hook for increasing the size of the options allocated with a tcp header.
- * Together with LWIP_HOOK_TCP_OUT_ADD_TCPOPTS, this can be used to add custom
- * options to outgoing tcp segments.
- * Signature:\code{.c}
- * u8_t my_hook_tcp_out_tcpopt_length(const struct tcp_pcb *pcb, u8_t internal_option_length);
- * \endcode
- * Arguments:
- * - pcb: tcp_pcb that transmits (ATTENTION: this may be NULL or
- *        struct tcp_pcb_listen if pcb->state == LISTEN)
- * - internal_option_length: tcp option length used by the stack internally
- * Return value:
- * - a number of bytes to allocate for tcp options (internal_option_length <= ret <= 40)
- *
- * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
- * state or any pcb lists) from this callback!
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH(pcb, internal_len)
-#endif
-
-/**
- * LWIP_HOOK_TCP_OUT_ADD_TCPOPTS:
- * Hook for adding custom options to outgoing tcp segments.
- * Space for these custom options has to be reserved via LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH.
- * Signature:\code{.c}
- * u32_t *my_hook_tcp_out_add_tcpopts(struct pbuf *p, struct tcp_hdr *hdr, const struct tcp_pcb *pcb, u32_t *opts);
- * \endcode
- * Arguments:
- * - p: output packet, p->payload pointing to tcp header, data follows
- * - hdr: tcp header
- * - pcb: tcp_pcb that transmits (ATTENTION: this may be NULL or
- *        struct tcp_pcb_listen if pcb->state == LISTEN)
- * - opts: pointer where to add the custom options (there may already be options
- *         between the header and these)
- * Return value:
- * - pointer pointing directly after the inserted options
- *
- * ATTENTION: don't call any tcp api functions that might change tcp state (pcb
- * state or any pcb lists) from this callback!
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_TCP_OUT_ADD_TCPOPTS(p, hdr, pcb, opts)
-#endif
-
-/**
- * LWIP_HOOK_IP4_INPUT(pbuf, input_netif):
- * Called from ip_input() (IPv4)
- * Signature:\code{.c}
- *   int my_hook(struct pbuf *pbuf, struct netif *input_netif);
- * \endcode
- * Arguments:
- * - pbuf: received struct pbuf passed to ip_input()
- * - input_netif: struct netif on which the packet has been received
- * Return values:
- * - 0: Hook has not consumed the packet, packet is processed as normal
- * - != 0: Hook has consumed the packet.
- * If the hook consumed the packet, 'pbuf' is in the responsibility of the hook
- * (i.e. free it when done).
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_IP4_INPUT(pbuf, input_netif)
-#endif
-
-/**
- * LWIP_HOOK_IP4_ROUTE(dest):
- * Called from ip_route() (IPv4)
- * Signature:\code{.c}
- *   struct netif *my_hook(const ip4_addr_t *dest);
- * \endcode
- * Arguments:
- * - dest: destination IPv4 address
- * Returns values:
- * - the destination netif
- * - NULL if no destination netif is found. In that case, ip_route() continues as normal.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_IP4_ROUTE()
-#endif
-
-/**
- * LWIP_HOOK_IP4_ROUTE_SRC(src, dest):
- * Source-based routing for IPv4 - called from ip_route() (IPv4)
- * Signature:\code{.c}
- *   struct netif *my_hook(const ip4_addr_t *src, const ip4_addr_t *dest);
- * \endcode
- * Arguments:
- * - src: local/source IPv4 address
- * - dest: destination IPv4 address
- * Returns values:
- * - the destination netif
- * - NULL if no destination netif is found. In that case, ip_route() continues as normal.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_IP4_ROUTE_SRC(src, dest)
-#endif
-
-/**
- * LWIP_HOOK_IP4_CANFORWARD(src, dest):
- * Check if an IPv4 can be forwarded - called from:
- * ip4_input() -> ip4_forward() -> ip4_canforward() (IPv4)
- * - source address is available via ip4_current_src_addr()
- * - calling an output function in this context (e.g. multicast router) is allowed
- * Signature:\code{.c}
- *   int my_hook(struct pbuf *p, u32_t dest_addr_hostorder);
- * \endcode
- * Arguments:
- * - p: packet to forward
- * - dest: destination IPv4 address
- * Returns values:
- * - 1: forward
- * - 0: don't forward
- * - -1: no decision. In that case, ip4_canforward() continues as normal.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_IP4_CANFORWARD(src, dest)
-#endif
-
-/**
- * LWIP_HOOK_ETHARP_GET_GW(netif, dest):
- * Called from etharp_output() (IPv4)
- * Signature:\code{.c}
- *   const ip4_addr_t *my_hook(struct netif *netif, const ip4_addr_t *dest);
- * \endcode
- * Arguments:
- * - netif: the netif used for sending
- * - dest: the destination IPv4 address
- * Return values:
- * - the IPv4 address of the gateway to handle the specified destination IPv4 address
- * - NULL, in which case the netif's default gateway is used
- *
- * The returned address MUST be directly reachable on the specified netif!
- * This function is meant to implement advanced IPv4 routing together with
- * LWIP_HOOK_IP4_ROUTE(). The actual routing/gateway table implementation is
- * not part of lwIP but can e.g. be hidden in the netif's state argument.
-*/
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_ETHARP_GET_GW(netif, dest)
-#endif
-
-/**
- * LWIP_HOOK_IP6_INPUT(pbuf, input_netif):
- * Called from ip6_input() (IPv6)
- * Signature:\code{.c}
- *   int my_hook(struct pbuf *pbuf, struct netif *input_netif);
- * \endcode
- * Arguments:
- * - pbuf: received struct pbuf passed to ip6_input()
- * - input_netif: struct netif on which the packet has been received
- * Return values:
- * - 0: Hook has not consumed the packet, packet is processed as normal
- * - != 0: Hook has consumed the packet.
- * If the hook consumed the packet, 'pbuf' is in the responsibility of the hook
- * (i.e. free it when done).
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_IP6_INPUT(pbuf, input_netif)
-#endif
-
-/**
- * LWIP_HOOK_IP6_ROUTE(src, dest):
- * Called from ip_route() (IPv6)
- * Signature:\code{.c}
- *   struct netif *my_hook(const ip6_addr_t *dest, const ip6_addr_t *src);
- * \endcode
- * Arguments:
- * - src: source IPv6 address
- * - dest: destination IPv6 address
- * Return values:
- * - the destination netif
- * - NULL if no destination netif is found. In that case, ip6_route() continues as normal.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_IP6_ROUTE(src, dest)
-#endif
-
-/**
- * LWIP_HOOK_ND6_GET_GW(netif, dest):
- * Called from nd6_get_next_hop_entry() (IPv6)
- * Signature:\code{.c}
- *   const ip6_addr_t *my_hook(struct netif *netif, const ip6_addr_t *dest);
- * \endcode
- * Arguments:
- * - netif: the netif used for sending
- * - dest: the destination IPv6 address
- * Return values:
- * - the IPv6 address of the next hop to handle the specified destination IPv6 address
- * - NULL, in which case a NDP-discovered router is used instead
- *
- * The returned address MUST be directly reachable on the specified netif!
- * This function is meant to implement advanced IPv6 routing together with
- * LWIP_HOOK_IP6_ROUTE(). The actual routing/gateway table implementation is
- * not part of lwIP but can e.g. be hidden in the netif's state argument.
-*/
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_ND6_GET_GW(netif, dest)
-#endif
-
-/**
- * LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr):
- * Called from ethernet_input() if VLAN support is enabled
- * Signature:\code{.c}
- *   int my_hook(struct netif *netif, struct eth_hdr *eth_hdr, struct eth_vlan_hdr *vlan_hdr);
- * \endcode
- * Arguments:
- * - netif: struct netif on which the packet has been received
- * - eth_hdr: struct eth_hdr of the packet
- * - vlan_hdr: struct eth_vlan_hdr of the packet
- * Return values:
- * - 0: Packet must be dropped.
- * - != 0: Packet must be accepted.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr)
-#endif
-
-/**
- * LWIP_HOOK_VLAN_SET:
- * Hook can be used to set prio_vid field of vlan_hdr. If you need to store data
- * on per-netif basis to implement this callback, see @ref netif_cd.
- * Called from ethernet_output() if VLAN support (@ref ETHARP_SUPPORT_VLAN) is enabled.\n
- * Signature:\code{.c}
- *   s32_t my_hook_vlan_set(struct netif* netif, struct pbuf* pbuf, const struct eth_addr* src, const struct eth_addr* dst, u16_t eth_type);\n
- * \endcode
- * Arguments:
- * - netif: struct netif that the packet will be sent through
- * - p: struct pbuf packet to be sent
- * - src: source eth address
- * - dst: destination eth address
- * - eth_type: ethernet type to packet to be sent\n
- * 
- * 
- * Return values:
- * - &lt;0: Packet shall not contain VLAN header.
- * - 0 &lt;= return value &lt;= 0xFFFF: Packet shall contain VLAN header. Return value is prio_vid in host byte order.
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_VLAN_SET(netif, p, src, dst, eth_type)
-#endif
-
-/**
- * LWIP_HOOK_MEMP_AVAILABLE(memp_t_type):
- * Called from memp_free() when a memp pool was empty and an item is now available
- * Signature:\code{.c}
- *   void my_hook(memp_t type);
- * \endcode
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_MEMP_AVAILABLE(memp_t_type)
-#endif
-
-/**
- * LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(pbuf, netif):
- * Called from ethernet_input() when an unknown eth type is encountered.
- * Signature:\code{.c}
- *   err_t my_hook(struct pbuf* pbuf, struct netif* netif);
- * \endcode
- * Arguments:
- * - p: rx packet with unknown eth type
- * - netif: netif on which the packet has been received
- * Return values:
- * - ERR_OK if packet is accepted (hook function now owns the pbuf)
- * - any error code otherwise (pbuf is freed)
- *
- * Payload points to ethernet header!
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(pbuf, netif)
-#endif
-
-/**
- * LWIP_HOOK_DHCP_APPEND_OPTIONS(netif, dhcp, state, msg, msg_type, options_len_ptr):
- * Called from various dhcp functions when sending a DHCP message.
- * This hook is called just before the DHCP message trailer is added, so the
- * options are at the end of a DHCP message.
- * Signature:\code{.c}
- *   void my_hook(struct netif *netif, struct dhcp *dhcp, u8_t state, struct dhcp_msg *msg,
- *                u8_t msg_type, u16_t *options_len_ptr);
- * \endcode
- * Arguments:
- * - netif: struct netif that the packet will be sent through
- * - dhcp: struct dhcp on that netif
- * - state: current dhcp state (dhcp_state_enum_t as an u8_t)
- * - msg: struct dhcp_msg that will be sent
- * - msg_type: dhcp message type to be sent (u8_t)
- * - options_len_ptr: pointer to the current length of options in the dhcp_msg "msg"
- *                    (must be increased when options are added!)
- *
- * Options need to appended like this:
- *   LWIP_ASSERT("dhcp option overflow", *options_len_ptr + option_len + 2 <= DHCP_OPTIONS_LEN);
- *   msg->options[(*options_len_ptr)++] = &lt;option_number&gt;;
- *   msg->options[(*options_len_ptr)++] = &lt;option_len&gt;;
- *   msg->options[(*options_len_ptr)++] = &lt;option_bytes&gt;;
- *   [...]
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_DHCP_APPEND_OPTIONS(netif, dhcp, state, msg, msg_type, options_len_ptr)
-#endif
-
-/**
- * LWIP_HOOK_DHCP_PARSE_OPTION(netif, dhcp, state, msg, msg_type, option, len, pbuf, option_value_offset):
- * Called from dhcp_parse_reply when receiving a DHCP message.
- * This hook is called for every option in the received message that is not handled internally.
- * Signature:\code{.c}
- *   void my_hook(struct netif *netif, struct dhcp *dhcp, u8_t state, struct dhcp_msg *msg,
- *                u8_t msg_type, u8_t option, u8_t option_len, struct pbuf *pbuf, u16_t option_value_offset);
- * \endcode
- * Arguments:
- * - netif: struct netif that the packet will be sent through
- * - dhcp: struct dhcp on that netif
- * - state: current dhcp state (dhcp_state_enum_t as an u8_t)
- * - msg: struct dhcp_msg that was received
- * - msg_type: dhcp message type received (u8_t, ATTENTION: only valid after
- *             the message type option has been parsed!)
- * - option: option value (u8_t)
- * - len: option data length (u8_t)
- * - pbuf: pbuf where option data is contained
- * - option_value_offset: offset in pbuf where option data begins
- *
- * A nice way to get the option contents is pbuf_get_contiguous():
- *  u8_t buf[32];
- *  u8_t *ptr = (u8_t*)pbuf_get_contiguous(p, buf, sizeof(buf), LWIP_MIN(option_len, sizeof(buf)), offset);
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_DHCP_PARSE_OPTION(netif, dhcp, state, msg, msg_type, option, len, pbuf, offset)
-#endif
-
-/**
- * LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, state, msg, msg_type, options_len_ptr, max_len):
- * Called from various dhcp6 functions when sending a DHCP6 message.
- * This hook is called just before the DHCP6 message is sent, so the
- * options are at the end of a DHCP6 message.
- * Signature:\code{.c}
- *   void my_hook(struct netif *netif, struct dhcp6 *dhcp, u8_t state, struct dhcp6_msg *msg,
- *                u8_t msg_type, u16_t *options_len_ptr);
- * \endcode
- * Arguments:
- * - netif: struct netif that the packet will be sent through
- * - dhcp6: struct dhcp6 on that netif
- * - state: current dhcp6 state (dhcp6_state_enum_t as an u8_t)
- * - msg: struct dhcp6_msg that will be sent
- * - msg_type: dhcp6 message type to be sent (u8_t)
- * - options_len_ptr: pointer to the current length of options in the dhcp6_msg "msg"
- *                    (must be increased when options are added!)
- *
- * Options need to appended like this:
- *   u8_t *options = (u8_t *)(msg + 1);
- *   LWIP_ASSERT("dhcp option overflow", sizeof(struct dhcp6_msg) + *options_len_ptr + newoptlen <= max_len);
- *   options[(*options_len_ptr)++] = &lt;option_data&gt;;
- *   [...]
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_DHCP6_APPEND_OPTIONS(netif, dhcp6, state, msg, msg_type, options_len_ptr, max_len)
-#endif
-
-/**
- * LWIP_HOOK_SOCKETS_SETSOCKOPT(s, sock, level, optname, optval, optlen, err)
- * Called from socket API to implement setsockopt() for options not provided by lwIP.
- * Core lock is held when this hook is called.
- * Signature:\code{.c}
- *   int my_hook(int s, struct lwip_sock *sock, int level, int optname, const void *optval, socklen_t optlen, int *err)
- * \endcode
- * Arguments:
- * - s: socket file descriptor
- * - sock: internal socket descriptor (see lwip/priv/sockets_priv.h)
- * - level: protocol level at which the option resides
- * - optname: option to set
- * - optval: value to set
- * - optlen: size of optval
- * - err: output error
- * Return values:
- * - 0: Hook has not consumed the option, code continues as normal (to internal options)
- * - != 0: Hook has consumed the option, 'err' is returned
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_SOCKETS_SETSOCKOPT(s, sock, level, optname, optval, optlen, err)
-#endif
-
-/**
- * LWIP_HOOK_SOCKETS_GETSOCKOPT(s, sock, level, optname, optval, optlen, err)
- * Called from socket API to implement getsockopt() for options not provided by lwIP.
- * Core lock is held when this hook is called.
- * Signature:\code{.c}
- *   int my_hook(int s, struct lwip_sock *sock, int level, int optname, void *optval, socklen_t *optlen, int *err)
- * \endcode
- * Arguments:
- * - s: socket file descriptor
- * - sock: internal socket descriptor (see lwip/priv/sockets_priv.h)
- * - level: protocol level at which the option resides
- * - optname: option to get
- * - optval: value to get
- * - optlen: size of optval
- * - err: output error
- * Return values:
- * - 0: Hook has not consumed the option, code continues as normal (to internal options)
- * - != 0: Hook has consumed the option, 'err' is returned
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_SOCKETS_GETSOCKOPT(s, sock, level, optname, optval, optlen, err)
-#endif
-
-/**
- * LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE(name, addr, addrtype, err)
- * Called from netconn APIs (not usable with callback apps) allowing an
- * external DNS resolver (which uses sequential API) to handle the query.
- * Signature:\code{.c}
- *   int my_hook(const char *name, ip_addr_t *addr, u8_t addrtype, err_t *err)
- * \endcode
- * Arguments:
- * - name: hostname to resolve
- * - addr: output host address
- * - addrtype: type of address to query
- * - err: output error
- * Return values:
- * - 0: Hook has not consumed hostname query, query continues into DNS module
- * - != 0: Hook has consumed the query
- *
- * err must also be checked to determine if the hook consumed the query, but
- * the query failed
- */
-#ifdef __DOXYGEN__
-#define LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE(name, addr, addrtype, err)
-#endif
-/**
- * @}
- */
-
-/*
-   ---------------------------------------
-   ---------- Debugging options ----------
-   ---------------------------------------
-*/
-/**
- * @defgroup lwip_opts_debugmsg Debug messages
- * @ingroup lwip_opts_debug
- * @{
- */
-/**
- * LWIP_DBG_MIN_LEVEL: After masking, the value of the debug is
- * compared against this value. If it is smaller, then debugging
- * messages are written.
- * @see debugging_levels
- */
-#if !defined LWIP_DBG_MIN_LEVEL || defined __DOXYGEN__
-#define LWIP_DBG_MIN_LEVEL              LWIP_DBG_LEVEL_ALL
-#endif
-
-/**
- * LWIP_DBG_TYPES_ON: A mask that can be used to globally enable/disable
- * debug messages of certain types.
- * @see debugging_levels
- */
-#if !defined LWIP_DBG_TYPES_ON || defined __DOXYGEN__
-#define LWIP_DBG_TYPES_ON               LWIP_DBG_ON
-#endif
-
-/**
- * ETHARP_DEBUG: Enable debugging in etharp.c.
- */
-#if !defined ETHARP_DEBUG || defined __DOXYGEN__
-#define ETHARP_DEBUG                    LWIP_DBG_OFF
-#endif
-
-/**
- * NETIF_DEBUG: Enable debugging in netif.c.
- */
-#if !defined NETIF_DEBUG || defined __DOXYGEN__
-#define NETIF_DEBUG                     LWIP_DBG_OFF
-#endif
-
-/**
- * PBUF_DEBUG: Enable debugging in pbuf.c.
- */
-#if !defined PBUF_DEBUG || defined __DOXYGEN__
-#define PBUF_DEBUG                      LWIP_DBG_OFF
-#endif
-
-/**
- * API_LIB_DEBUG: Enable debugging in api_lib.c.
- */
-#if !defined API_LIB_DEBUG || defined __DOXYGEN__
-#define API_LIB_DEBUG                   LWIP_DBG_OFF
-#endif
-
-/**
- * API_MSG_DEBUG: Enable debugging in api_msg.c.
- */
-#if !defined API_MSG_DEBUG || defined __DOXYGEN__
-#define API_MSG_DEBUG                   LWIP_DBG_OFF
-#endif
-
-/**
- * SOCKETS_DEBUG: Enable debugging in sockets.c.
- */
-#if !defined SOCKETS_DEBUG || defined __DOXYGEN__
-#define SOCKETS_DEBUG                   LWIP_DBG_OFF
-#endif
-
-/**
- * ICMP_DEBUG: Enable debugging in icmp.c.
- */
-#if !defined ICMP_DEBUG || defined __DOXYGEN__
-#define ICMP_DEBUG                      LWIP_DBG_OFF
-#endif
-
-/**
- * IGMP_DEBUG: Enable debugging in igmp.c.
- */
-#if !defined IGMP_DEBUG || defined __DOXYGEN__
-#define IGMP_DEBUG                      LWIP_DBG_OFF
-#endif
-
-/**
- * INET_DEBUG: Enable debugging in inet.c.
- */
-#if !defined INET_DEBUG || defined __DOXYGEN__
-#define INET_DEBUG                      LWIP_DBG_OFF
-#endif
-
-/**
- * IP_DEBUG: Enable debugging for IP.
- */
-#if !defined IP_DEBUG || defined __DOXYGEN__
-#define IP_DEBUG                        LWIP_DBG_OFF
-#endif
-
-/**
- * IP_REASS_DEBUG: Enable debugging in ip_frag.c for both frag & reass.
- */
-#if !defined IP_REASS_DEBUG || defined __DOXYGEN__
-#define IP_REASS_DEBUG                  LWIP_DBG_OFF
-#endif
-
-/**
- * RAW_DEBUG: Enable debugging in raw.c.
- */
-#if !defined RAW_DEBUG || defined __DOXYGEN__
-#define RAW_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/**
- * MEM_DEBUG: Enable debugging in mem.c.
- */
-#if !defined MEM_DEBUG || defined __DOXYGEN__
-#define MEM_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/**
- * MEMP_DEBUG: Enable debugging in memp.c.
- */
-#if !defined MEMP_DEBUG || defined __DOXYGEN__
-#define MEMP_DEBUG                      LWIP_DBG_OFF
-#endif
-
-/**
- * SYS_DEBUG: Enable debugging in sys.c.
- */
-#if !defined SYS_DEBUG || defined __DOXYGEN__
-#define SYS_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/**
- * TIMERS_DEBUG: Enable debugging in timers.c.
- */
-#if !defined TIMERS_DEBUG || defined __DOXYGEN__
-#define TIMERS_DEBUG                    LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_DEBUG: Enable debugging for TCP.
- */
-#if !defined TCP_DEBUG || defined __DOXYGEN__
-#define TCP_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_INPUT_DEBUG: Enable debugging in tcp_in.c for incoming debug.
- */
-#if !defined TCP_INPUT_DEBUG || defined __DOXYGEN__
-#define TCP_INPUT_DEBUG                 LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_FR_DEBUG: Enable debugging in tcp_in.c for fast retransmit.
- */
-#if !defined TCP_FR_DEBUG || defined __DOXYGEN__
-#define TCP_FR_DEBUG                    LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_RTO_DEBUG: Enable debugging in TCP for retransmit
- * timeout.
- */
-#if !defined TCP_RTO_DEBUG || defined __DOXYGEN__
-#define TCP_RTO_DEBUG                   LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_CWND_DEBUG: Enable debugging for TCP congestion window.
- */
-#if !defined TCP_CWND_DEBUG || defined __DOXYGEN__
-#define TCP_CWND_DEBUG                  LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_WND_DEBUG: Enable debugging in tcp_in.c for window updating.
- */
-#if !defined TCP_WND_DEBUG || defined __DOXYGEN__
-#define TCP_WND_DEBUG                   LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_OUTPUT_DEBUG: Enable debugging in tcp_out.c output functions.
- */
-#if !defined TCP_OUTPUT_DEBUG || defined __DOXYGEN__
-#define TCP_OUTPUT_DEBUG                LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_RST_DEBUG: Enable debugging for TCP with the RST message.
- */
-#if !defined TCP_RST_DEBUG || defined __DOXYGEN__
-#define TCP_RST_DEBUG                   LWIP_DBG_OFF
-#endif
-
-/**
- * TCP_QLEN_DEBUG: Enable debugging for TCP queue lengths.
- */
-#if !defined TCP_QLEN_DEBUG || defined __DOXYGEN__
-#define TCP_QLEN_DEBUG                  LWIP_DBG_OFF
-#endif
-
-/**
- * UDP_DEBUG: Enable debugging in UDP.
- */
-#if !defined UDP_DEBUG || defined __DOXYGEN__
-#define UDP_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/**
- * TCPIP_DEBUG: Enable debugging in tcpip.c.
- */
-#if !defined TCPIP_DEBUG || defined __DOXYGEN__
-#define TCPIP_DEBUG                     LWIP_DBG_OFF
-#endif
-
-/**
- * SLIP_DEBUG: Enable debugging in slipif.c.
- */
-#if !defined SLIP_DEBUG || defined __DOXYGEN__
-#define SLIP_DEBUG                      LWIP_DBG_OFF
-#endif
-
-/**
- * DHCP_DEBUG: Enable debugging in dhcp.c.
- */
-#if !defined DHCP_DEBUG || defined __DOXYGEN__
-#define DHCP_DEBUG                      LWIP_DBG_OFF
-#endif
-
-/**
- * AUTOIP_DEBUG: Enable debugging in autoip.c.
- */
-#if !defined AUTOIP_DEBUG || defined __DOXYGEN__
-#define AUTOIP_DEBUG                    LWIP_DBG_OFF
-#endif
-
-/**
- * DNS_DEBUG: Enable debugging for DNS.
- */
-#if !defined DNS_DEBUG || defined __DOXYGEN__
-#define DNS_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/**
- * IP6_DEBUG: Enable debugging for IPv6.
- */
-#if !defined IP6_DEBUG || defined __DOXYGEN__
-#define IP6_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/**
- * DHCP6_DEBUG: Enable debugging in dhcp6.c.
- */
-#if !defined DHCP6_DEBUG || defined __DOXYGEN__
-#define DHCP6_DEBUG                     LWIP_DBG_OFF
-#endif
-/**
- * @}
- */
-
-/**
- * LWIP_TESTMODE: Changes to make unit test possible
- */
-#if !defined LWIP_TESTMODE
-#define LWIP_TESTMODE                   0
-#endif
-
-/**
- * NAPT_DEBUG: Enable debugging for NAPT.
- */
-#ifndef NAPT_DEBUG
-#define NAPT_DEBUG                       LWIP_DBG_OFF
-#endif
-
-/*
-   --------------------------------------------------
-   ---------- Performance tracking options ----------
-   --------------------------------------------------
-*/
-/**
- * @defgroup lwip_opts_perf Performance
- * @ingroup lwip_opts_debug
- * @{
- */
-/**
- * LWIP_PERF: Enable performance testing for lwIP
- * (if enabled, arch/perf.h is included)
- */
-#if !defined LWIP_PERF || defined __DOXYGEN__
-#define LWIP_PERF                       0
-#endif
-/**
- * @}
- */
-
-#endif /* LWIP_HDR_OPT_H */
diff --git a/arduinoIDE_esp32_boards/opt.h b/arduinoIDE_esp32_boards/opt.h
new file mode 120000
index 0000000000000000000000000000000000000000..cbdfa918048b99c13f5b81bb4e79c94a95aedf54
--- /dev/null
+++ b/arduinoIDE_esp32_boards/opt.h
@@ -0,0 +1 @@
+2.0.17/opt.h
\ No newline at end of file
diff --git a/deploy.sh b/deploy.sh
index 4c1779bf44ddf1dbb499ec6017be797eb666c477..4e3bbcc6515b7a6757f3c13f5822384b4a1954fd 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -2,6 +2,7 @@
 #
 # Deployment script for neOCampus / neOSensor boards
 #
+# F.Thiebolt    jul.24  adaptation to esp32 arduino core 3.X
 # F.Thiebolt    jul.23  upgrades esp* SDKs
 # F.Thiebolt    jan.23  upgrades esp* SDKs
 # F.Thiebolt    aug.22  upgrades esp* SDKs
@@ -13,9 +14,12 @@
 
 #
 # Global defs
-ESP32_REV=${ESP32_REV:-"2.0.15"}
+ESP32_REV=${ESP32_REV:-"2.0.17"}
+# [jun.24] preparing migration to 3.X serie
+#ESP32_REV=${ESP32_REV:-"3.0.2"}
+#ESP32_IDF_RELEASE=${ESP32_IDF_RELEASE:-"5.1"}
+
 ESP8266_REV=${ESP8266_REV:-"3.1.2"}
-CUBECELL_REV=${CUBECELL_REV:-"1.3.0"}
 
 
 #
@@ -23,18 +27,33 @@ CUBECELL_REV=${CUBECELL_REV:-"1.3.0"}
 # $1: ARCH (e.g ESP8266, ESP32 or CubeCell)
 # $2: SDK_REV (e.g "2.7.4" for esp8266 "1.0.4" for esp32)
 function boards_install() {
-    [ $# -ne 2 ] && { return 1; }
+    #[ $# -ne 2 ] && { return 1; }
     ARCH=$1
     SDK_REV=$2
+    IDF_REV=$3
 
     SDK_DIR="~/.arduino15/packages/${ARCH}/hardware/${ARCH}/${SDK_REV}"
     eval SDK_DIR=${SDK_DIR}
     [ -d ${SDK_DIR} ] || { echo -e "unable to find ${ARCH} SDK dir '${SDK_DIR}' ... wrong revision ??" >&2; return 1; }
 
+    IDF_DIR=""
+    if [ "X${IDF_REV}" != "X" ]; then
+        IDF_DIR="~/.arduino15/packages/${ARCH}/tools/esp32-arduino-libs/idf-release_v${IDF_REV}*/${ARCH}"
+        eval IDF_DIR=${IDF_DIR}
+        for _dir in $(ls -d ${IDF_DIR}); do
+            [ -d ${_dir} ] && { IDF_DIR=${_dir}; break; }
+        done
+        [ -d ${IDF_DIR} ] || { IDF_DIR=""; }
+    fi
+
     echo -e "\n# -------------------------------------------------------- #"
     echo -e "#\tTARGET:\t${ARCH^^}"
     echo -e "#\tDetected ${ARCH} SDK dir:"
     printf  "#\t%-80s\n" "${SDK_DIR}"
+    if [ "X${IDF_DIR}" != "X" ]; then
+        echo -e "#\tDetected ${ARCH} IDF dir:"
+        printf  "#\t%-80s\n" "${IDF_DIR}";
+    fi
     sleep 1
 
     SRC_DIR="arduinoIDE_${ARCH,,}_boards"
@@ -88,7 +107,13 @@ function boards_install() {
     if [ -x ${SRC_DIR}/local_deploy.sh ]; then
         echo -e "#         execute local_deploy script                      #"
         echo -e "#                                                          #"
-        ( cd ${SRC_DIR}; source local_deploy.sh ${SDK_DIR}/tools/sdk; )
+        if [ "X${IDF_DIR}" != "X" ]; then
+            ( cd ${SRC_DIR}; source local_deploy.sh ${IDF_DIR}; )
+        elif [ -d ${SDK_DIR}/tools/sdk/${ARCH} ]; then
+            ( cd ${SRC_DIR}; source local_deploy.sh ${SDK_DIR}/tools/sdk/${ARCH}; )
+        else
+            ( cd ${SRC_DIR}; source local_deploy.sh ${SDK_DIR}/tools/sdk; )
+        fi
     fi
 
     echo -e "#                                                          #"
@@ -106,13 +131,13 @@ echo -e "#                                                          #"
 echo -e "# -------------------------------------------------------- #"
 
 # ESP32 boards
-boards_install "esp32" ${ESP32_REV}
+if [ "X${ESP32_IDF_RELEASE}" != "X" ]; then
+    boards_install "esp32" ${ESP32_REV} ${ESP32_IDF_RELEASE}
+else
+    boards_install "esp32" ${ESP32_REV}
+fi
 [ $? -ne 0 ] && { echo -e "\n\tfailed to install additional ESP32 boards ?!?!" >&2; sleep 2; }
 
-# CubeCell boards
-boards_install "CubeCell" ${CUBECELL_REV}
-[ $? -ne 0 ] && { echo -e "\n\tfailed to install additional CubeCell boards ?!?!" >&2; sleep 2; }
-
 # ESP8266 boards
 boards_install "esp8266" ${ESP8266_REV}
 [ $? -ne 0 ] && { echo -e "\n\tfailed to install additional ESP8266 boards ?!?!" >&2; sleep 2; }
diff --git a/neosensor/libraries/ArduinoJson/ArduinoJson.h b/neosensor/libraries/ArduinoJson/ArduinoJson.h
index 15c218f480f0be6a6e21f68eb3078d689babb515..993959949b72337803e8adb157332f04bf916672 100644
--- a/neosensor/libraries/ArduinoJson/ArduinoJson.h
+++ b/neosensor/libraries/ArduinoJson/ArduinoJson.h
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include "src/ArduinoJson.h"
diff --git a/neosensor/libraries/ArduinoJson/CHANGELOG.md b/neosensor/libraries/ArduinoJson/CHANGELOG.md
index 2f47083a77859398012363a396b12a5e90e90786..b348d564be77fac7078474f26e853ae425abb96a 100644
--- a/neosensor/libraries/ArduinoJson/CHANGELOG.md
+++ b/neosensor/libraries/ArduinoJson/CHANGELOG.md
@@ -1,868 +1,150 @@
 ArduinoJson: change log
 =======================
 
-v6.21.5 (2024-01-10)
--------
-
-* Fix warning `function returns incomplete class type` on IAR (issue #2001)
-* Fix `volatile bool` serialized as `1` or `0` instead of `true` or `false` (issue #2029)
-* Remove unused files in the PlatformIO package
-
-v6.21.4 (2023-12-07)
--------
-
-* Fix error `'std::string' has not been declared` (issue #1967)
-* Fix error `'std::string_view' has not been declared` (issue #1967)
-* Fix error `no instance of overloaded function...` on recent IAR compilers (issue #2001)
-
-v6.21.3 (2023-07-23)
--------
-
-* Fix compatibility with the Blynk libary (issue #1914)
-* Fix double lookup in `to<JsonVariant>()`
-* Fix double call to `size()` in `serializeMsgPack()`
-* Include `ARDUINOJSON_SLOT_OFFSET_SIZE` in the namespace name
-* Show a link to the documentation when user passes an unsupported input type
-
-v6.21.2 (2023-04-12)
--------
-
-* Fix compatibility with the Zephyr Project (issue #1905)
-* Allow using PROGMEM outside of Arduino (issue #1903)
-* Set default for `ARDUINOJSON_ENABLE_PROGMEM` to `1` on AVR
-
-v6.21.1 (2023-03-27)
--------
-
-* Double speed of `DynamicJsonDocument::garbageCollect()`
-* Fix compatibility with GCC 5.2 (issue #1897)
-
-v6.21.0 (2023-03-14)
--------
-
-* Drop support for C++98/C++03. Minimum required is C++11.
-* Remove `ARDUINOJSON_NAMESPACE`; use `ArduinoJson` instead.
-* Make string support generic (issue #1807)
-
-v6.20.1 (2023-02-08)
--------
-
-* Remove explicit exclusion of `as<char*>()` and `as<char>()` (issue #1860)
-  If you try to call them, you'll now get the same error message as any unsupported type.
-  You could also add a custom converter for `char*` and `char`.
-
-v6.20.0 (2022-12-26)
--------
-
-* Add `JsonVariant::shallowCopy()` (issue #1343)
-* Fix `9.22337e+18 is outside the range of representable values of type 'long'`
-* Fix comparison operators for `JsonArray`, `JsonArrayConst`, `JsonObject`, and `JsonObjectConst`
-* Fix lax parsing of `true`, `false`, and `null` (issue #1781)
-* Remove undocumented `accept()` functions
-* Rename `addElement()` to `add()`
-* Remove `getElement()`, `getOrAddElement()`, `getMember()`, and `getOrAddMember()`
-* Remove undocumented `JsonDocument::data()` and `JsonDocument::memoryPool()`
-* Remove undocumented `JsonArrayIterator::internal()` and `JsonObjectIterator::internal()`
-* Rename things in `ARDUINOJSON_NAMESPACE` to match the public names
-* Add documentation to most public symbols
-* Remove support for naked `char` (was deprecated since 6.18.0)
-
-> ### BREAKING CHANGES
->
-> This release hides `JsonVariant`'s functions that were only intended for internal use.
-> If you were using them in your programs, you must replace with `operator[]` and `to<JsonVariant>()`, like so:
->
-> ```c++
-> // before
-> JsonVariant a = variant.getElement(idx);
-> JsonVariant b = variant.getOrAddElement(idx);
-> JsonVariant c = variant.getMember(key);
-> JsonVariant d = variant.getOrAddMember(key);
->
-> // after
-> JsonVariant a = variant[idx];
-> JsonVariant b = idx < variant.size() ? variant[idx] : variant[idx].to<JsonVariant>();
-> JsonVariant c = variant[key];
-> JsonVariant d = variant.containsKey(key) ? variant[key] : variant[key].to<JsonVariant>();
-> ```
-
-v6.19.4 (2022-04-05)
--------
-
-* Add `ElementProxy::memoryUsage()`
-* Add `MemberProxy::memoryUsage()` (issue #1730)
-* Add implicit conversion from `JsonDocument` to `JsonVariant`
-* Fix comparisons operators with `const JsonDocument&`
-
-v6.19.3 (2022-03-08)
--------
-
-* Fix `call of overloaded 'String(const char*, int)' is ambiguous`
-* Fix `JsonString` operator `==` and `!=` for non-zero-terminated string
-* Fix `-Wsign-conversion` on GCC 8 (issue #1715)
-* MessagePack: serialize round floats as integers (issue #1718)
-
-v6.19.2 (2022-02-14)
--------
-
-* Fix `cannot convert 'pgm_p' to 'const void*'` (issue #1707)
-
-v6.19.1 (2022-01-14)
--------
-
-* Fix crash when adding an object member in a too small `JsonDocument`
-* Fix filter not working in zero-copy mode (issue #1697)
-
-v6.19.0 (2022-01-08)
--------
-
-* Remove `ARDUINOJSON_EMBEDDED_MODE` and assume we run on an embedded platform.  
-  Dependent settings (like `ARDUINOJSON_DEFAULT_NESTING_LIMIT`) must be set individually.
-* Change the default of `ARDUINOJSON_USE_DOUBLE` to `1`
-* Change the default of `ARDUINOJSON_USE_LONG_LONG` to `1` on 32-bit platforms
-* Add `as<JsonString>()` and `is<JsonString>()`
-* Add safe bool idiom in `JsonString`
-* Add support for NUL in string values (issue #1646)
-* Add support for arbitrary array rank in `copyArray()`
-* Add support for `char[][]` in `copyArray()`
-* Remove `DeserializationError == bool` and `DeserializationError != bool`
-* Renamed undocumented function `isUndefined()` to `isUnbound()`
-* Fix `JsonVariant::memoryUsage()` for raw strings
-* Fix `call of overloaded 'swap(BasicJsonDocument&, BasicJsonDocument&)' is ambiguous` (issue #1678)
-* Fix inconsistent pool capacity between `BasicJsonDocument`'s copy and move constructors
-* Fix inconsistent pool capacity between `BasicJsonDocument`'s copy and move assignments
-* Fix return type of `StaticJsonDocument::operator=`
-* Avoid pool reallocation in `BasicJsonDocument`'s copy assignment if capacity is the same
-* Avoid including `Arduino.h` when all its features are disabled (issue #1692, PR #1693 by @paulocsanz)
-* Assume `PROGMEM` is available as soon as `ARDUINO` is defined (consequence of #1693)
-
-v6.18.5 (2021-09-28)
--------
-
-* Set `ARDUINOJSON_EMBEDDED_MODE` to `1` on Nios II (issue #1657)
+v7.1.0 (2024-06-27)
+------
 
-v6.18.4 (2021-09-06)
--------
+* Add `ARDUINOJSON_STRING_LENGTH_SIZE` to the namespace name
+* Add support for MsgPack binary (PR #2078 by @Sanae6)
+* Add support for MsgPack extension
+* Make string support even more generic (PR #2084 by @d-a-v)
+* Optimize `deserializeMsgPack()`
+* Allow using a `JsonVariant` as a key or index (issue #2080)
+  Note: works only for reading, not for writing
+* Support `ElementProxy` and `MemberProxy` in `JsonDocument`'s constructor
+* Don't add partial objects when allocation fails (issue #2081)
+* Read MsgPack's 64-bit integers even if `ARDUINOJSON_USE_LONG_LONG` is `0`
+  (they are set to `null` if they don't fit in a `long`)
+
+v7.0.4 (2024-03-12)
+------
 
-* Fixed error `'dummy' may be used uninitialized` on GCC 11
-* Fixed error `expected unqualified-id before 'const'` on GCC 11 (issue #1622)
-* Filter: exact match takes precedence over wildcard (issue #1628)
-* Fixed deserialization of `\u0000` (issue #1646)
+* Make `JSON_STRING_SIZE(N)` return `N+1` to fix third-party code (issue #2054)
 
-v6.18.3 (2021-07-27)
--------
+v7.0.3 (2024-02-05)
+------
 
-* Changed return type of `convertToJson()` and `Converter<T>::toJson()` to `void`
-* Added `as<std::string_view>()` and `is<std::string_view>()`
+* Improve error messages when using `char` or `char*` (issue #2043)
+* Reduce stack consumption (issue #2046)
+* Fix compatibility with GCC 4.8 (issue #2045)
 
-v6.18.2 (2021-07-19)
--------
+v7.0.2 (2024-01-19)
+------
 
-* Removed a symlink because the Arduino Library Specification forbids it
+* Fix assertion `poolIndex < count_` after `JsonDocument::clear()` (issue #2034)
 
-v6.18.1 (2021-07-03)
--------
+v7.0.1 (2024-01-10)
+------
 
-* Fixed support for `volatile float` and `volatile double` (issue #1557)
-* Fixed error `[Pe070]: incomplete type is not allowed` on IAR (issue #1560)
-* Fixed `serializeJson(doc, String)` when allocation fails (issue #1572)
-* Fixed clang-tidy warnings (issue #1574, PR #1577 by @armandas)
-* Added fake class `InvalidConversion<T1,T2>` to easily identify invalid conversions (issue #1585)
-* Added support for `std::string_view` (issue #1578, PR #1554 by @0xFEEDC0DE64)
-* Fixed warning `definition of implicit copy constructor for 'MsgPackDeserializer' is deprecated because it has a user-declared copy assignment operator`
-* Added `JsonArray::clear()` (issue #1597)
-* Fixed `JsonVariant::as<unsigned>()` (issue #1601)
-* Added support for ESP-IDF component build (PR #1562 by @qt1, PR #1599 by @andreaskuster)
+* Fix "no matching function" with `JsonObjectConst::operator[]` (issue #2019)
+* Remove unused files in the PlatformIO package
+* Fix `volatile bool` serialized as `1` or `0` instead of `true` or `false` (issue #2029)
 
-v6.18.0 (2021-05-05)
--------
+v7.0.0 (2024-01-03)
+------
 
-* Added support for custom converters (issue #687)
-* Added support for `Printable` (issue #1444)
-* Removed support for `char` values, see below (issue #1498)
-* `deserializeJson()` leaves `\uXXXX` unchanged instead of returning `NotSupported`
-* `deserializeMsgPack()` inserts `null` instead of returning `NotSupported`
-* Removed `DeserializationError::NotSupported`
-* Added `JsonVariant::is<JsonArrayConst/JsonObjectConst>()` (issue #1412)
-* Added `JsonVariant::is<JsonVariant/JsonVariantConst>()` (issue #1412)
-* Changed `JsonVariantConst::is<JsonArray/JsonObject>()` to return `false` (issue #1412)
-* Simplified `JsonVariant::as<T>()` to always return `T` (see below)
-* Updated folders list in `.mbedignore` (PR #1515 by @AGlass0fMilk)
-* Fixed member-call-on-null-pointer in `getMember()` when array is empty
-* `serializeMsgPack(doc, buffer, size)` doesn't add null-terminator anymore (issue #1545)
-* `serializeJson(doc, buffer, size)` adds null-terminator only if there is enough room
-* PlatformIO: set `build.libArchive` to `false` (PR #1550 by @askreet)
+* Remove `BasicJsonDocument`
+* Remove `StaticJsonDocument`
+* Add abstract `Allocator` class
+* Merge `DynamicJsonDocument` with `JsonDocument`
+* Remove `JSON_ARRAY_SIZE()`, `JSON_OBJECT_SIZE()`, and `JSON_STRING_SIZE()`
+* Remove `ARDUINOJSON_ENABLE_STRING_DEDUPLICATION` (string deduplication cannot be disabled anymore)
+* Remove `JsonDocument::capacity()`
+* Store the strings in the heap
+* Reference-count shared strings
+* Always store `serialized("string")` by copy (#1915)
+* Remove the zero-copy mode of `deserializeJson()` and `deserializeMsgPack()`
+* Fix double lookup in `to<JsonVariant>()`
+* Fix double call to `size()` in `serializeMsgPack()`
+* Include `ARDUINOJSON_SLOT_OFFSET_SIZE` in the namespace name
+* Remove `JsonVariant::shallowCopy()`
+* `JsonDocument`'s capacity grows as needed, no need to pass it to the constructor anymore
+* `JsonDocument`'s allocator is not monotonic anymore, removed values get recycled
+* Show a link to the documentation when user passes an unsupported input type
+* Remove `JsonDocument::memoryUsage()`
+* Remove `JsonDocument::garbageCollect()`
+* Add `deserializeJson(JsonVariant, ...)` and `deserializeMsgPack(JsonVariant, ...)` (#1226)
+* Call `shrinkToFit()` in `deserializeJson()` and `deserializeMsgPack()`
+* `serializeJson()` and `serializeMsgPack()` replace the content of `std::string` and `String` instead of appending to it
+* Replace `add()` with `add<T>()` (`add(T)` is still supported)
+* Remove `createNestedArray()` and `createNestedObject()` (use `to<JsonArray>()` and `to<JsonObject>()` instead)
 
 > ### BREAKING CHANGES
 >
-> #### Support for `char` removed
->
-> We cannot cast a `JsonVariant` to a `char` anymore, so the following will break:
-> ```c++
-> char age = doc["age"];  //  error: no matching function for call to 'variantAs(VariantData*&)'
-> ```
-> Instead, you must use another integral type, such as `int8_t`:
-> ```c++
-> int8_t age = doc["age"];  // OK
-> ```
->
-> Similarly, we cannot assign from a `char` anymore, so the following will break:
-> ```c++
-> char age;
-> doc["age"] = age;  // error: no matching function for call to 'VariantRef::set(const char&)'
-> ```
-> Instead, you must use another integral type, such as `int8_t`:
-> ```c++
-> int8_t age;
-> doc["age"] = age;  // OK
-> ```
-> A deprecation warning with the message "Support for `char` is deprecated, use `int8_t` or `uint8_t` instead" was added to allow a smooth transition.
+> As every major release, ArduinoJson 7 introduces several breaking changes.
+> I added some stubs so that most existing programs should compile, but I highty recommend you upgrade your code.
 >
-> #### `as<T>()` always returns `T`
->
-> Previously, `JsonVariant::as<T>()` could return a type different from `T`.
-> The most common example is `as<char*>()` that returned a `const char*`.
-> While this feature simplified a few use cases, it was confusing and complicated the
-> implementation of custom converters.
->
-> Starting from this version, `as<T>` doesn't try to auto-correct the return type and always return `T`,
-> which means that you cannot write this anymore:
->
-> ```c++
-> Serial.println(doc["sensor"].as<char*>());  // error: invalid conversion from 'const char*' to 'char*' [-fpermissive]
-> ```
+> #### `JsonDocument`
 > 
-> Instead, you must write:
->
-> ```c++
-> Serial.println(doc["sensor"].as<const char*>());  // OK
-> ```
->
-> A deprecation warning with the message "Replace `as<char*>()` with `as<const char*>()`" was added to allow a smooth transition.
+> In ArduinoJson 6, you could allocate the memory pool on the stack (with `StaticJsonDocument`) or in the heap (with `DynamicJsonDocument`).  
+> In ArduinoJson 7, the memory pool is always allocated in the heap, so `StaticJsonDocument` and `DynamicJsonDocument` have been merged into `JsonDocument`.
 >
-> #### `DeserializationError::NotSupported` removed
->
-> On a different topic, `DeserializationError::NotSupported` has been removed.
-> Instead of returning this error:
->
-> * `deserializeJson()` leaves `\uXXXX` unchanged (only when `ARDUINOJSON_DECODE_UNICODE` is `0`)
-> * `deserializeMsgPack()` replaces unsupported values with `null`s
->
-> #### Const-aware `is<T>()`
->
-> Lastly, a very minor change concerns `JsonVariantConst::is<T>()`.
-> It used to return `true` for `JsonArray` and `JsonOject`, but now it returns `false`.
-> Instead, you must use `JsonArrayConst` and `JsonObjectConst`.
-
-v6.17.3 (2021-02-15)
--------
-
-* Made `JsonDocument`'s destructor protected (issue #1480)
-* Added missing calls to `client.stop()` in `JsonHttpClient.ino` (issue #1485)
-* Fixed error `expected ')' before 'char'` when `isdigit()` is a macro (issue #1487)
-* Fixed error `definition of implicit copy constructor is deprecated` on Clang 10
-* PlatformIO: set framework compatibility to `*` (PR #1490 by @maxgerhardt)
-
-v6.17.2 (2020-11-14)
--------
-
-* Fixed invalid conversion error in `operator|(JsonVariant, char*)` (issue #1432)
-* Changed the default value of `ARDUINOJSON_ENABLE_PROGMEM` (issue #1433).
-  It now checks that the `pgm_read_XXX` macros are defined before enabling `PROGMEM`.
-
-v6.17.1 (2020-11-07)
--------
-
-* Fixed error `ambiguous overload for 'operator|'` (issue #1411)
-* Fixed `operator|(MemberProxy, JsonObject)` (issue #1415)
-* Allowed more than 32767 values in non-embedded mode (issue #1414)
-
-v6.17.0 (2020-10-19)
--------
-
-* Added a build failure when nullptr is defined as a macro (issue #1355)
-* Added `JsonDocument::overflowed()` which tells if the memory pool was too small (issue #1358)
-* Added `DeserializationError::EmptyInput` which tells if the input was empty
-* Added `DeserializationError::f_str()` which returns a `const __FlashStringHelper*` (issue #846)
-* Added `operator|(JsonVariantConst, JsonVariantConst)`
-* Added filtering for MessagePack (issue #1298, PR #1394 by Luca Passarella)
-* Moved float convertion tables to PROGMEM
-* Fixed `JsonVariant::set((char*)0)` which returned false instead of true (issue #1368)
-* Fixed error `No such file or directory #include <WString.h>` (issue #1381)
-
-v6.16.1 (2020-08-04)
--------
-
-* Fixed `deserializeJson()` that stopped reading after `{}` (issue #1335)
-
-v6.16.0 (2020-08-01)
--------
-
-* Added comparisons (`>`, `>=`, `==`, `!=`, `<`, and `<=`) between `JsonVariant`s
-* Added string deduplication (issue #1303)
-* Added `JsonString::operator!=`
-* Added wildcard key (`*`) for filters (issue #1309)
-* Set `ARDUINOJSON_DECODE_UNICODE` to `1` by default
-* Fixed `copyArray()` not working with `String`, `ElementProxy`, and `MemberProxy`
-* Fixed error `getOrAddElement is not a member of ElementProxy` (issue #1311)
-* Fixed excessive stack usage when compiled with `-Og` (issues #1210 and #1314)
-* Fixed `Warning[Pa093]: implicit conversion from floating point to integer` on IAR compiler (PR #1328 by @stawiski)
-
-v6.15.2 (2020-05-15)
--------
-
-* CMake: don't build tests when imported in another project
-* CMake: made project arch-independent
-* Visual Studio: fixed error C2766 with flag `/Zc:__cplusplus` (issue #1250)
-* Added support for `JsonDocument` to `copyArray()` (issue #1255)
-* Added support for `enum`s in `as<T>()` and `is<T>()`  (issue #1256)
-* Added `JsonVariant` as an input type for `deserializeXxx()`  
-  For example, you can do: `deserializeJson(doc2, doc1["payload"])`
-* Break the build if using 64-bit integers with ARDUINOJSON_USE_LONG_LONG==0
-
-v6.15.1 (2020-04-08)
--------
-
-* Fixed "maybe-uninitialized" warning (issue #1217)
-* Fixed "statement is unreachable" warning on IAR (issue #1233)
-* Fixed "pointless integer comparison" warning on IAR (issue #1233)
-* Added CMake "install" target (issue #1209)
-* Disabled alignment on AVR (issue #1231)
-
-v6.15.0 (2020-03-22)
--------
-
-* Added `DeserializationOption::Filter` (issue #959)
-* Added example `JsonFilterExample.ino`
-* Changed the array subscript operator to automatically add missing elements
-* Fixed "deprecated-copy" warning on GCC 9 (fixes #1184)
-* Fixed `MemberProxy::set(char[])` not duplicating the string (issue #1191)
-* Fixed enums serialized as booleans (issue #1197)
-* Fixed incorrect string comparison on some platforms (issue #1198)
-* Added move-constructor and move-assignment to `BasicJsonDocument`
-* Added `BasicJsonDocument::garbageCollect()` (issue #1195)
-* Added `StaticJsonDocument::garbageCollect()`
-* Changed copy-constructor of `BasicJsonDocument` to preserve the capacity of the source.
-* Removed copy-constructor of `JsonDocument` (issue #1189)
-
-> ### BREAKING CHANGES
-> 
-> #### Copy-constructor of `BasicJsonDocument`
->
-> In previous versions, the copy constructor of `BasicJsonDocument` looked at the source's `memoryUsage()` to choose its capacity.
-> Now, the copy constructor of `BasicJsonDocument` uses the same capacity as the source.
->
-> Example:
+> In ArduinoJson 6, `JsonDocument` had a fixed capacity; in ArduinoJson 7, it has an elastic capacity that grows as needed.
+> Therefore, you don't need to specify the capacity anymore, so the macros `JSON_ARRAY_SIZE()`, `JSON_OBJECT_SIZE()`, and `JSON_STRING_SIZE()` have been removed.
 >
 > ```c++
-> DynamicJsonDocument doc1(64);
-> doc1.set(String("example"));
->
-> DynamicJsonDocument doc2 = doc1;
-> Serial.print(doc2.capacity());  // 8 with ArduinoJson 6.14
->                                 // 64 with ArduinoJson 6.15
-> ```
->
-> I made this change to get consistent results between copy-constructor and move-constructor, and whether RVO applies or not.
->
-> If you use the copy-constructor to optimize your documents, you can use `garbageCollect()` or `shrinkToFit()` instead.
->
-> #### Copy-constructor of `JsonDocument`
->
-> In previous versions, it was possible to create a function that take a `JsonDocument` by value.
->
-> ```c++
-> void myFunction(JsonDocument doc) {}
-> ```
->
-> This function gives the wrong clues because it doesn't receive a copy of the `JsonDocument`, only a sliced version.
-> It worked because the copy constructor copied the internal pointers, but it was an accident.
->
-> From now, if you need to pass a `JsonDocument` to a function, you must use a reference:
->
-> ```c++
-> void myFunction(JsonDocument& doc) {}
-> ```
-
-v6.14.1 (2020-01-27)
--------
-
-* Fixed regression in UTF16 decoding (issue #1173)
-* Fixed `containsKey()` on `JsonVariantConst`
-* Added `getElement()` and `getMember()` to `JsonVariantConst`
-
-v6.14.0 (2020-01-16)
--------
-
-* Added `BasicJsonDocument::shrinkToFit()`
-* Added support of `uint8_t` for `serializeJson()`, `serializeJsonPretty()`, and `serializeMsgPack()` (issue #1142)
-* Added `ARDUINOJSON_ENABLE_COMMENTS` to enable support for comments (defaults to 0)
-* Auto enable support for `std::string` and `std::stream` on modern compilers (issue #1156)
-  (No need to define `ARDUINOJSON_ENABLE_STD_STRING` and `ARDUINOJSON_ENABLE_STD_STREAM` anymore)
-* Improved decoding of UTF-16 surrogate pairs (PR #1157 by @kaysievers)
-  (ArduinoJson now produces standard UTF-8 instead of CESU-8)
-* Added `measureJson`, `measureJsonPretty`, and `measureMsgPack` to `keywords.txt`
-  (This file is used for syntax highlighting in the Arduino IDE) 
-* Fixed `variant.is<nullptr_t>()`
-* Fixed value returned by `serializeJson()`, `serializeJsonPretty()`, and `serializeMsgPack()` when writing to a `String`
-* Improved speed of `serializeJson()`, `serializeJsonPretty()`, and `serializeMsgPack()` when writing to a `String`
-
-> ### BREAKING CHANGES
-> 
-> #### Comments
+> // ArduinoJson 6
+> StaticJsonDocument<256> doc;
+> // or
+> DynamicJsonDocument doc(256);
 > 
-> Support for comments in input is now optional and disabled by default.
->
-> If you need support for comments, you must defined `ARDUINOJSON_ENABLE_COMMENTS` to `1`; otherwise, you'll receive `InvalidInput` errors.
->
-> ```c++
-> #define ARDUINOJSON_ENABLE_COMMENTS 1
-> #include <ArduinoJson.h>
+> // ArduinoJson 7
+> JsonDocument doc;
 > ```
-
-v6.13.0 (2019-11-01)
--------
-
-* Added support for custom writer/reader classes (issue #1088)
-* Added conversion from `JsonArray` and `JsonObject` to `bool`, to be consistent with `JsonVariant`
-* Fixed `deserializeJson()` when input contains duplicate keys (issue #1095)
-* Improved `deserializeMsgPack()` speed by reading several bytes at once
-* Added detection of Atmel AVR8/GNU C Compiler (issue #1112)
-* Fixed deserializer that stopped reading at the first `0xFF` (PR #1118 by @mikee47)
-* Fixed dangling reference in copies of `MemberProxy` and `ElementProxy` (issue #1120)
-
-v6.12.0 (2019-09-05)
--------
-
-* Use absolute instead of relative includes (issue #1072)
-* Changed `JsonVariant::as<bool>()` to return `true` for any non-null value (issue #1005)
-* Moved ancillary files to `extras/` (issue #1011)
-
-v6.11.5 (2019-08-23)
--------
-
-* Added fallback implementations of `strlen_P()`, `strncmp_P()`, `strcmp_P()`, and `memcpy_P()` (issue #1073)
-
-v6.11.4 (2019-08-12)
--------
-
-* Added `measureJson()` to the `ArduinoJson` namespace (PR #1069 by @nomis)
-* Added support for `basic_string<char, traits, allocator>` (issue #1045)
-* Fixed example `JsonConfigFile.ino` for ESP8266
-* Include `Arduino.h` if `ARDUINO` is defined (PR #1071 by @nomis)
-
-v6.11.3 (2019-07-22)
--------
-
-* Added operators `==` and `!=` for `JsonDocument`, `ElementProxy`, and `MemberProxy`
-* Fixed comparison of `JsonVariant` when one contains a linked string and the other contains an owned string (issue #1051)
-
-v6.11.2 (2019-07-08)
--------
-
-* Fixed assignment of `JsonDocument` to `JsonVariant` (issue #1023)
-* Fix invalid conversion error on Particle Argon (issue #1035)
-
-v6.11.1 (2019-06-21)
--------
-
-* Fixed `serialized()` not working with Flash strings (issue #1030)
-
-v6.11.0 (2019-05-26)
--------
-
-* Fixed `deserializeJson()` silently accepting a `Stream*` (issue #978)
-* Fixed invalid result from `operator|` (issue #981)
-* Made `deserializeJson()` more picky about trailing characters (issue #980)
-* Added `ARDUINOJSON_ENABLE_NAN` (default=0) to enable NaN in JSON (issue #973)
-* Added `ARDUINOJSON_ENABLE_INFINITY` (default=0) to enable Infinity in JSON
-* Removed implicit conversion in comparison operators (issue #998)
-* Added lexicographical comparison for `JsonVariant`
-* Added support for `nullptr` (issue #998)
-
-> ### BREAKING CHANGES
-> 
-> #### NaN and Infinity
-> 
-> The JSON specification allows neither NaN not Infinity, but previous
-> versions of ArduinoJson supported it. Now, ArduinoJson behaves like most
-> other libraries: a NaN or and Infinity in the `JsonDocument`, becomes
-> a `null` in the output JSON. Also, `deserializeJson()` returns
-> `InvalidInput` if the JSON document contains NaN or Infinity.
-> 
-> This version still supports NaN and Infinity in JSON documents, but
-> it's disabled by default to be compatible with other JSON parsers.
-> If you need the old behavior back, define `ARDUINOJSON_ENABLE_NAN` and
-> `ARDUINOJSON_ENABLE_INFINITY` to `1`;:
-> 
-> ```c++
-> #define ARDUINOJSON_ENABLE_NAN 1
-> #define ARDUINOJSON_ENABLE_INFINITY 1
-> #include <ArduinoJson.h>
-> ```
-> 
-> #### The "or" operator
-> 
-> This version slightly changes the behavior of the | operator when the 
-> variant contains a float and the user requests an integer.
 >
-> Older versions returned the floating point value truncated.
-> Now, it returns the default value.
-> 
-> ```c++
-> // suppose variant contains 1.2
-> int value = variant | 3;
-> 
-> // old behavior:
-> value == 1
-> 
-> // new behavior
-> value == 3
-> ```
-> 
-> If you need the old behavior, you must add `if (variant.is<float>())`.
-
-v6.10.1 (2019-04-23)
--------
-
-* Fixed error "attributes are not allowed on a function-definition"
-* Fixed `deserializeJson()` not being picky enough (issue #969)
-* Fixed error "no matching function for call to write(uint8_t)" (issue #972)
-
-v6.10.0 (2019-03-22)
--------
-
-* Fixed an integer overflow in the JSON deserializer
-* Added overflow handling in `JsonVariant::as<T>()` and `JsonVariant::is<T>()`.
-   - `as<T>()` returns `0` if the integer `T` overflows
-   - `is<T>()` returns `false` if the integer `T` overflows
-* Added `BasicJsonDocument` to support custom allocator (issue #876)
-* Added `JsonDocument::containsKey()` (issue #938)
-* Added `JsonVariant::containsKey()`
-
-v6.9.1 (2019-03-01)
-------
-
-* Fixed warning "unused variable" with GCC 4.4 (issue #912)
-* Fixed warning "cast  increases required alignment" (issue #914)
-* Fixed warning "conversion may alter value" (issue #914)
-* Fixed naming conflict with "CAPACITY" (issue #839)
-* Muted warning "will change in GCC 7.1" (issue #914)
-* Added a clear error message for `StaticJsonBuffer` and `DynamicJsonBuffer`
-* Marked ArduinoJson.h  as a "system header"
-
-v6.9.0 (2019-02-26)
-------
-
-* Decode escaped Unicode characters like \u00DE (issue #304, PR #791)
-  Many thanks to Daniel Schulte (aka @trilader) who implemented this feature.
-* Added option ARDUINOJSON_DECODE_UNICODE to enable it
-* Converted `JsonArray::copyFrom()/copyTo()` to free functions `copyArray()`
-* Renamed `JsonArray::copyFrom()` and `JsonObject::copyFrom()` to `set()`
-* Renamed `JsonArray::get()` to `getElement()`
-* Renamed `JsonArray::add()` (without arg) to `addElement()`
-* Renamed `JsonObject::get()` to `getMember()`
-* Renamed `JsonObject::getOrCreate()` to `getOrAddMember()`
-* Fixed `JsonVariant::isNull()` not returning `true` after `set((char*)0)`
-* Fixed segfault after `variant.set(serialized((char*)0))`
-* Detect `IncompleteInput` in `false`, `true`, and `null`
-* Added `JsonDocument::size()`
-* Added `JsonDocument::remove()`
-* Added `JsonVariant::clear()`
-* Added `JsonVariant::remove()`
-
-v6.8.0-beta (2019-01-30)
------------
-
-* Import functions in the ArduinoJson namespace to get clearer errors
-* Improved syntax highlighting in Arduino IDE
-* Removed default capacity of `DynamicJsonDocument`
-* `JsonArray::copyFrom()` accepts `JsonArrayConst`
-* `JsonVariant::set()` accepts `JsonArrayConst` and `JsonObjectConst`
-* `JsonDocument` was missing in the ArduinoJson namespace
-* Added `memoryUsage()` to `JsonArray`, `JsonObject`, and `JsonVariant`
-* Added `nesting()` to `JsonArray`, `JsonDocument`, `JsonObject`, and `JsonVariant`
-* Replaced `JsonDocument::nestingLimit` with an additional parameter
-  to `deserializeJson()` and `deserializeMsgPack()`
-* Fixed uninitialized variant in `JsonDocument`
-* Fixed `StaticJsonDocument` copy constructor and copy assignment
-* The copy constructor of `DynamicJsonDocument` chooses the capacity according to the memory usage of the source, not from the capacity of the source.
-* Added the ability to create/assign a `StaticJsonDocument`/`DynamicJsonDocument` from a `JsonArray`/`JsonObject`/`JsonVariant`
-* Added `JsonDocument::isNull()`
-* Added `JsonDocument::operator[]`
-* Added `ARDUINOJSON_TAB` to configure the indentation character
-* Reduced the size of the pretty JSON serializer
-* Added `add()`, `createNestedArray()` and `createNestedObject()` to `JsonVariant`
-* `JsonVariant` automatically promotes to `JsonObject` or `JsonArray` on write.
-  Calling `JsonVariant::to<T>()` is not required anymore.
-* `JsonDocument` now support the same operations as `JsonVariant`.
-  Calling `JsonDocument::as<T>()` is not required anymore.
-* Fixed example `JsonHttpClient.ino`
-* User can now use a `JsonString` as a key or a value
-
-> ### BREAKING CHANGES
-> 
-> #### `DynamicJsonDocument`'s constructor
-> 
-> The parameter to the constructor of `DynamicJsonDocument` is now mandatory
->
-> Old code:
->
-> ```c++
-> DynamicJsonDocument doc;
-> ```
+> In ArduinoJson 7, `JsonDocument` reuses released memory, so `garbageCollect()` has been removed.  
+> `shrinkToFit()` is still available and releases the over-allocated memory.
 >
-> New code:
->
-> ```c++
-> DynamicJsonDocument doc(1024);
-> ```
-> 
-> #### Nesting limit
-> 
-> `JsonDocument::nestingLimit` was replaced with a new parameter to `deserializeJson()` and `deserializeMsgPack()`.
-> 
-> Old code:
+> Due to a change in the implementation, it's not possible to store a pointer to a variant from another `JsonDocument`, so `shallowCopy()` has been removed.
 > 
-> ```c++
-> doc.nestingLimit = 15;
-> deserializeJson(doc, input);
-> ```
-> 
-> New code: 
-> 
-> ```c++
-> deserializeJson(doc, input, DeserializationOption::NestingLimit(15));
-> ```
-
-v6.7.0-beta (2018-12-07)
------------
-
-* Removed the automatic expansion of `DynamicJsonDocument`, it now has a fixed capacity.
-* Restored the monotonic allocator because the code was getting too big
-* Reduced the memory usage
-* Reduced the code size
-* Renamed `JsonKey` to `JsonString`
-* Removed spurious files in the Particle library
-
-v6.6.0-beta (2018-11-13)
------------
-
-* Removed `JsonArray::is<T>(i)` and `JsonArray::set(i,v)`
-* Removed `JsonObject::is<T>(k)` and `JsonObject::set(k,v)`
-* Replaced `T JsonArray::get<T>(i)` with `JsonVariant JsonArray::get(i)`
-* Replaced `T JsonObject::get<T>(k)` with `JsonVariant JsonObject::get(k)`
-* Added `JSON_STRING_SIZE()`
-* ~~Replacing or removing a value now releases the memory~~
-* Added `DeserializationError::code()` to be used in switch statements (issue #846)
-
-v6.5.0-beta (2018-10-13)
------------
-
-* Added implicit conversion from `JsonArray` and `JsonObject` to `JsonVariant`
-* Allow mixed configuration in compilation units (issue #809)
-* Fixed object keys not being duplicated
-* `JsonPair::key()` now returns a `JsonKey`
-* Increased the default capacity of `DynamicJsonDocument`
-* Fixed `JsonVariant::is<String>()` (closes #763)
-* Added `JsonArrayConst`, `JsonObjectConst`, and `JsonVariantConst`
-* Added copy-constructor and copy-assignment-operator for `JsonDocument` (issue #827)
-
-v6.4.0-beta (2018-09-11)
------------
-
-* Copy `JsonArray` and `JsonObject`, instead of storing pointers (issue #780)
-* Added `JsonVariant::to<JsonArray>()` and `JsonVariant::to<JsonObject>()`
-
-v6.3.0-beta (2018-08-31)
------------
-
-* Implemented reference semantics for `JsonVariant`
-* Replaced `JsonPair`'s `key` and `value` with `key()` and `value()`
-* Fixed `serializeJson(obj[key], dst)` (issue #794)
-
-> ### BREAKING CHANGES
+> In ArduinoJson 6, the meaning of `memoryUsage()` was clear: it returned the number of bytes used in the memory pool.  
+> In ArduinoJson 7, the meaning of `memoryUsage()` would be ambiguous, so it has been removed.
 >
-> #### JsonVariant
-> 
-> `JsonVariant` now has a semantic similar to `JsonObject` and `JsonArray`.
-> It's a reference to a value stored in the `JsonDocument`.
-> As a consequence, a `JsonVariant` cannot be used as a standalone variable anymore.
+> #### Custom allocators
 >
-> Old code:
+> In ArduinoJson 6, you could specify a custom allocator class as a template parameter of `BasicJsonDocument`.  
+> In ArduinoJson 7, you must inherit from `ArduinoJson::Allocator` and pass a pointer to an instance of your class to the constructor of `JsonDocument`.
 >
 > ```c++
-> JsonVariant myValue = 42;
-> ```
->
-> New code:
+> // ArduinoJson 6
+> class MyAllocator {
+>   // ...
+> };
+> BasicJsonDocument<MyAllocator> doc(256);
 >
-> ```c++
-> DynamicJsonDocument doc;
-> JsonVariant myValue = doc.to<JsonVariant>();
-> myValue.set(42);
+> // ArduinoJson 7
+> class MyAllocator : public ArduinoJson::Allocator {
+>   // ...
+> };
+> MyAllocator myAllocator;
+> JsonDocument doc(&myAllocator);
 > ```
 >
-> #### JsonPair
->
-> Old code:
+> #### `createNestedArray()` and `createNestedObject()`
 >
-> ```c++
-> for(JsonPair p : myObject) {
->   Serial.println(p.key);
->   Serial.println(p.value.as<int>());
-> }
-> ```
+> In ArduinoJson 6, you could create a nested array or object with `createNestedArray()` and `createNestedObject()`.  
+> In ArduinoJson 7, you must use `add<T>()` or `to<T>()` instead.
 >
-> New code:
+> For example, to create `[[],{}]`, you would write:
 >
 > ```c++
-> for(JsonPair p : myObject) {
->   Serial.println(p.key());
->   Serial.println(p.value().as<int>());
-> }
-> ```
+> // ArduinoJson 6
+> arr.createNestedArray();
+> arr.createNestedObject();
 >
-> CAUTION: the key is now read only!
-
-v6.2.3-beta (2018-07-19)
------------
-
-* Fixed exception when using Flash strings as object keys (issue #784)
-
-v6.2.2-beta (2018-07-18)
------------
-
-* Fixed `invalid application of 'sizeof' to incomplete type '__FlashStringHelper'` (issue #783)
-* Fixed `char[]` not duplicated when passed to `JsonVariant::operator[]`
-
-v6.2.1-beta (2018-07-17)
------------
-
-* Fixed `JsonObject` not inserting keys of type `String` (issue #782)
-
-v6.2.0-beta (2018-07-12)
------------
-
-* Disabled lazy number deserialization (issue #772)
-* Fixed `JsonVariant::is<int>()` that returned true for empty strings
-* Improved float serialization when `-fsingle-precision-constant` is used
-* Renamed function `RawJson()` to `serialized()`
-* `serializeMsgPack()` now supports values marked with `serialized()`
-
-> ### BREAKING CHANGES
->
-> #### Non quoted strings
->
-> Non quoted strings are now forbidden in values, but they are still allowed in keys.
-> For example, `{key:"value"}` is accepted, but `{key:value}` is not.
->
-> #### Preformatted values
+> // ArduinoJson 7
+> arr.add<JsonArray>();
+> arr.add<JsonObject>();
+> ```
 >
-> Old code:
+> And to create `{"array":[],"object":{}}`, you would write:
 >
 > ```c++
-> object["values"] = RawJson("[1,2,3,4]");
-> ```
-> 
-> New code:
-> 
-> ```c++
-> object["values"] = serialized("[1,2,3,4]");
-> ```
-
-v6.1.0-beta (2018-07-02)
------------
-
-* Return `JsonArray` and `JsonObject` by value instead of reference (issue #309)
-* Replaced `success()` with `isNull()`
-
-> ### BREAKING CHANGES
-> 
-> Old code:
+> // ArduinoJson 6
+> obj.createNestedArray("array");
+> obj.createNestedObject("object");
 >
-> ```c++
-> JsonObject& obj = doc.to<JsonObject>();
-> JsonArray& arr = obj.createNestedArray("key");
-> if (!arr.success()) {
->   Serial.println("Not enough memory");
->   return;
-> }
-> ```
-> 
-> New code:
-> 
-> ```c++
-> JsonObject obj = doc.to<JsonObject>();
-> JsonArray arr = obj.createNestedArray("key");
-> if (arr.isNull()) {
->   Serial.println("Not enough memory");
->   return;
-> }
-> ```
-
-v6.0.1-beta (2018-06-11)
------------
-
-* Fixed conflicts with `isnan()` and `isinf()` macros (issue #752)
-
-v6.0.0-beta (2018-06-07)
------------
-
-* Added `DynamicJsonDocument` and `StaticJsonDocument`
-* Added `deserializeJson()`
-* Added `serializeJson()` and `serializeJsonPretty()`
-* Added `measureJson()` and `measureJsonPretty()`
-* Added `serializeMsgPack()`, `deserializeMsgPack()` and `measureMsgPack()` (issue #358)
-* Added example `MsgPackParser.ino` (issue #358)
-* Added support for non zero-terminated strings (issue #704)
-* Removed `JsonBuffer::parseArray()`, `parseObject()` and `parse()`
-* Removed `JsonBuffer::createArray()` and `createObject()`
-* Removed `printTo()` and `prettyPrintTo()`
-* Removed `measureLength()` and `measurePrettyLength()`
-* Removed all deprecated features
-
-> ### BREAKING CHANGES
-> 
-> #### Deserialization
-> 
-> Old code:
-> 
-> ```c++
-> DynamicJsonBuffer jb;
-> JsonObject& obj = jb.parseObject(json);
-> if (obj.success()) {
-> 
-> }
-> ```
-> 
-> New code:
-> 
-> ```c++
-> DynamicJsonDocument doc;
-> DeserializationError error = deserializeJson(doc, json);
-> if (error) {
-> 
-> }
-> JsonObject& obj = doc.as<JsonObject>();
-> ```
-> 
-> #### Serialization
-> 
-> Old code:
-> 
-> ```c++
-> DynamicJsonBuffer jb;
-> JsonObject& obj = jb.createObject();
-> obj["key"] = "value";
-> obj.printTo(Serial);
-> ```
-> 
-> New code:
-> 
-> ```c++
-> DynamicJsonDocument obj;
-> JsonObject& obj = doc.to<JsonObject>();
-> obj["key"] = "value";
-> serializeJson(doc, Serial);
+> // ArduinoJson 7
+> obj["array"].to<JsonArray>();
+> obj["object"].to<JsonObject>();
 > ```
diff --git a/neosensor/libraries/ArduinoJson/CMakeLists.txt b/neosensor/libraries/ArduinoJson/CMakeLists.txt
index 71b3d535826b9dbdd5c64f213f858b8d64af46e0..d76b8379205ae61472ee4c29d7e0c134ec381b4b 100644
--- a/neosensor/libraries/ArduinoJson/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 cmake_minimum_required(VERSION 3.15)
@@ -10,7 +10,7 @@ if(ESP_PLATFORM)
 	return()
 endif()
 
-project(ArduinoJson VERSION 6.21.5)
+project(ArduinoJson VERSION 7.1.0)
 
 if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
 	include(CTest)
diff --git a/neosensor/libraries/ArduinoJson/LICENSE.txt b/neosensor/libraries/ArduinoJson/LICENSE.txt
index 15f1b9fbb5a786a89289aa2f60bbdaf7be7d5326..56bb92b7f92509347edd79bdb6c122d2c2f6afe0 100644
--- a/neosensor/libraries/ArduinoJson/LICENSE.txt
+++ b/neosensor/libraries/ArduinoJson/LICENSE.txt
@@ -1,7 +1,7 @@
 The MIT License (MIT)
 ---------------------
 
-Copyright © 2014-2023, Benoit BLANCHON
+Copyright © 2014-2024, Benoit BLANCHON
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
diff --git a/neosensor/libraries/ArduinoJson/README.md b/neosensor/libraries/ArduinoJson/README.md
index 96879e01e83c929d1fee436e55eedc4e7ae5020b..2900240e53daee0321fef6509d58a45158ed8c25 100644
--- a/neosensor/libraries/ArduinoJson/README.md
+++ b/neosensor/libraries/ArduinoJson/README.md
@@ -4,13 +4,10 @@
 
 ---
 
-[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bblanchon/ArduinoJson/ci.yml?branch=6.x&logo=github)](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A6.x)
-[![Continuous Integration](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/6.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x)
+[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bblanchon/ArduinoJson/ci.yml?branch=7.x&logo=github)](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A7.x)
+[![Continuous Integration](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/7.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
 [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/arduinojson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
-[![Coveralls branch](https://img.shields.io/coveralls/github/bblanchon/ArduinoJson/6.x?logo=coveralls)](https://coveralls.io/github/bblanchon/ArduinoJson?branch=6.x)  
-[![Arduino Library Manager](https://img.shields.io/static/v1?label=Arduino&message=v6.21.5&logo=arduino&logoColor=white&color=blue)](https://www.ardu-badge.com/ArduinoJson/6.21.5)
-[![PlatformIO Registry](https://badges.registry.platformio.org/packages/bblanchon/library/ArduinoJson.svg?version=6.21.5)](https://registry.platformio.org/packages/libraries/bblanchon/ArduinoJson?version=6.21.5) 
-[![ESP IDF](https://img.shields.io/static/v1?label=ESP+IDF&message=v6.21.5&logo=cpu&logoColor=white&color=blue)](https://components.espressif.com/components/bblanchon/arduinojson)  
+[![Coveralls branch](https://img.shields.io/coveralls/github/bblanchon/ArduinoJson/7.x?logo=coveralls)](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)  
 [![GitHub stars](https://img.shields.io/github/stars/bblanchon/ArduinoJson?style=flat&logo=github&color=orange)](https://github.com/bblanchon/ArduinoJson/stargazers)
 [![GitHub Sponsors](https://img.shields.io/github/sponsors/bblanchon?logo=github&color=orange)](https://github.com/sponsors/bblanchon)
 
@@ -18,31 +15,28 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
 
 ## Features
 
-* [JSON deserialization](https://arduinojson.org/v6/api/json/deserializejson/)
-    * [Optionally decodes UTF-16 escape sequences to UTF-8](https://arduinojson.org/v6/api/config/decode_unicode/)
-    * [Optionally stores links to the input buffer (zero-copy)](https://arduinojson.org/v6/api/json/deserializejson/)
-    * [Optionally supports comments in the input](https://arduinojson.org/v6/api/config/enable_comments/)
-    * [Optionally filters the input to keep only desired values](https://arduinojson.org/v6/api/json/deserializejson/#filtering)
+* [JSON deserialization](https://arduinojson.org/v7/api/json/deserializejson/)
+    * [Optionally decodes UTF-16 escape sequences to UTF-8](https://arduinojson.org/v7/api/config/decode_unicode/)
+    * [Optionally supports comments in the input](https://arduinojson.org/v7/api/config/enable_comments/)
+    * [Optionally filters the input to keep only desired values](https://arduinojson.org/v7/api/json/deserializejson/#filtering)
     * Supports single quotes as a string delimiter
     * Compatible with [NDJSON](http://ndjson.org/) and [JSON Lines](https://jsonlines.org/)
-* [JSON serialization](https://arduinojson.org/v6/api/json/serializejson/)
-    * [Can write to a buffer or a stream](https://arduinojson.org/v6/api/json/serializejson/)
-    * [Optionally indents the document (prettified JSON)](https://arduinojson.org/v6/api/json/serializejsonpretty/)
-* [MessagePack serialization](https://arduinojson.org/v6/api/msgpack/serializemsgpack/)
-* [MessagePack deserialization](https://arduinojson.org/v6/api/msgpack/deserializemsgpack/)
+* [JSON serialization](https://arduinojson.org/v7/api/json/serializejson/)
+    * [Can write to a buffer or a stream](https://arduinojson.org/v7/api/json/serializejson/)
+    * [Optionally indents the document (prettified JSON)](https://arduinojson.org/v7/api/json/serializejsonpretty/)
+* [MessagePack serialization](https://arduinojson.org/v7/api/msgpack/serializemsgpack/)
+* [MessagePack deserialization](https://arduinojson.org/v7/api/msgpack/deserializemsgpack/)
 * Efficient
     * [Twice smaller than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
     * [Almost 10% faster than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
     * [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
-    * [Fixed memory allocation, no heap fragmentation](https://arduinojson.org/v6/api/jsondocument/)
-    * [Optionally works without heap memory (zero malloc)](https://arduinojson.org/v6/api/staticjsondocument/)
     * [Deduplicates strings](https://arduinojson.org/news/2020/08/01/version-6-16-0/)
 * Versatile
-    * Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/)
-    * Supports [`String`](https://arduinojson.org/v6/api/config/enable_arduino_string/), [`std::string`](https://arduinojson.org/v6/api/config/enable_std_string/), and [`std::string_view`](https://arduinojson.org/v6/api/config/enable_string_view/)
-    * Supports [`Stream`](https://arduinojson.org/v6/api/config/enable_arduino_stream/) and [`std::istream`/`std::ostream`](https://arduinojson.org/v6/api/config/enable_std_stream/)
-    * Supports [Flash strings](https://arduinojson.org/v6/api/config/enable_progmem/)
-    * Supports [custom readers](https://arduinojson.org/v6/api/json/deserializejson/#custom-reader) and [custom writers](https://arduinojson.org/v6/api/json/serializejson/#custom-writer)
+    * Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v7/how-to/use-external-ram-on-esp32/)
+    * Supports [`String`](https://arduinojson.org/v7/api/config/enable_arduino_string/), [`std::string`](https://arduinojson.org/v7/api/config/enable_std_string/), and [`std::string_view`](https://arduinojson.org/v7/api/config/enable_string_view/)
+    * Supports [`Stream`](https://arduinojson.org/v7/api/config/enable_arduino_stream/) and [`std::istream`/`std::ostream`](https://arduinojson.org/v7/api/config/enable_std_stream/)
+    * Supports [Flash strings](https://arduinojson.org/v7/api/config/enable_progmem/)
+    * Supports [custom readers](https://arduinojson.org/v7/api/json/deserializejson/#custom-reader) and [custom writers](https://arduinojson.org/v7/api/json/serializejson/#custom-writer)
     * Supports [custom converters](https://arduinojson.org/news/2021/05/04/version-6-18-0/)
 * Portable
     * Usable on any C++ project (not limited to Arduino)
@@ -72,29 +66,29 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
         * [Visual Micro](http://www.visualmicro.com/)
         * [Visual Studio](https://www.visualstudio.com/)
     * [Even works with online compilers like wandbox.org](https://wandbox.org/permlink/RlZSKy17DjJ6HcdN)
-    * [CMake friendly](https://arduinojson.org/v6/how-to/use-arduinojson-with-cmake/)
+    * [CMake friendly](https://arduinojson.org/v7/how-to/use-arduinojson-with-cmake/)
 * Well designed
-    * [Elegant API](http://arduinojson.org/v6/example/)
+    * [Elegant API](http://arduinojson.org/v7/example/)
     * [Thread-safe](https://en.wikipedia.org/wiki/Thread_safety)
     * Self-contained (no external dependency)
     * `const` friendly
-    * [`for` friendly](https://arduinojson.org/v6/api/jsonobject/begin_end/)
+    * [`for` friendly](https://arduinojson.org/v7/api/jsonobject/begin_end/)
     * [TMP friendly](https://en.wikipedia.org/wiki/Template_metaprogramming)
-    * Handles [integer overflows](https://arduinojson.org/v6/api/jsonvariant/as/#integer-overflows)
+    * Handles [integer overflows](https://arduinojson.org/v7/api/jsonvariant/as/#integer-overflows)
 * Well tested
-    * [Unit test coverage close to 100%](https://coveralls.io/github/bblanchon/ArduinoJson?branch=6.x)
+    * [Unit test coverage close to 100%](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)
     * Continuously tested on
-        * [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x)
-        * [GCC 5, 6, 7, 8, 9, 10, 11](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
-        * [Clang 3.8, 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
+        * [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
+        * [GCC 4.8, 5, 6, 7, 8, 9, 10, 11, 12](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
+        * [Clang 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10, 11, 12, 13, 14, 15](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
     * [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
     * Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/)
 * Well documented
-    * [Tutorials](https://arduinojson.org/v6/doc/deserialization/)
-    * [Examples](https://arduinojson.org/v6/example/)
-    * [How-tos](https://arduinojson.org/v6/example/)
-    * [FAQ](https://arduinojson.org/v6/faq/)
-    * [Troubleshooter](https://arduinojson.org/v6/troubleshooter/)
+    * [Tutorials](https://arduinojson.org/v7/doc/deserialization/)
+    * [Examples](https://arduinojson.org/v7/example/)
+    * [How-tos](https://arduinojson.org/v7/example/)
+    * [FAQ](https://arduinojson.org/v7/faq/)
+    * [Troubleshooter](https://arduinojson.org/v7/troubleshooter/)
     * [Book](https://arduinojson.org/book/)
     * [Changelog](CHANGELOG.md)
 * Vibrant user community
@@ -109,9 +103,9 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
 Here is a program that parses a JSON document with ArduinoJson.
 
 ```c++
-char json[] = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
+const char* json = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
 
-DynamicJsonDocument doc(1024);
+JsonDocument doc;
 deserializeJson(doc, json);
 
 const char* sensor = doc["sensor"];
@@ -120,14 +114,14 @@ double latitude    = doc["data"][0];
 double longitude   = doc["data"][1];
 ```
 
-See the [tutorial on arduinojson.org](https://arduinojson.org/v6/doc/deserialization/)
+See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/deserialization/)
 
 ### Serialization
 
 Here is a program that generates a JSON document with ArduinoJson:
 
 ```c++
-DynamicJsonDocument doc(1024);
+JsonDocument doc;
 
 doc["sensor"] = "gps";
 doc["time"]   = 1351824120;
@@ -139,7 +133,7 @@ serializeJson(doc, Serial);
 // {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
 ```
 
-See the [tutorial on arduinojson.org](https://arduinojson.org/v6/doc/serialization/)
+See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/serialization/)
 
 ## Sponsors
 
diff --git a/neosensor/libraries/ArduinoJson/appveyor.yml b/neosensor/libraries/ArduinoJson/appveyor.yml
index 8a6a332b5c593f74e2d3eb01d90b0dfb5d7d73fb..edcdb4bbdad06bc383a65fe0db4940ed9425d14c 100644
--- a/neosensor/libraries/ArduinoJson/appveyor.yml
+++ b/neosensor/libraries/ArduinoJson/appveyor.yml
@@ -1,4 +1,4 @@
-version: 6.21.5.{build}
+version: 7.1.0.{build}
 environment:
   matrix:
     - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
diff --git a/neosensor/libraries/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino b/neosensor/libraries/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino
index b2819d553621d25a85d5573fe6257d6c1946d952..cd94064bc48f556578aa3bd61ef4bd7463d5c630 100644
--- a/neosensor/libraries/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino
+++ b/neosensor/libraries/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to store your project configuration in a file.
@@ -17,35 +17,28 @@
 // * CLK  <-> pin 13
 // * CS   <-> pin 4
 //
-// https://arduinojson.org/v6/example/config/
+// https://arduinojson.org/v7/example/config/
 
 #include <ArduinoJson.h>
 #include <SD.h>
 #include <SPI.h>
 
 // Our configuration structure.
-//
-// Never use a JsonDocument to store the configuration!
-// A JsonDocument is *not* a permanent storage; it's only a temporary storage
-// used during the serialization phase. See:
-// https://arduinojson.org/v6/faq/why-must-i-create-a-separate-config-object/
 struct Config {
   char hostname[64];
   int port;
 };
 
-const char *filename = "/config.txt";  // <- SD library uses 8.3 filenames
+const char* filename = "/config.txt";  // <- SD library uses 8.3 filenames
 Config config;                         // <- global configuration object
 
 // Loads the configuration from a file
-void loadConfiguration(const char *filename, Config &config) {
+void loadConfiguration(const char* filename, Config& config) {
   // Open file for reading
   File file = SD.open(filename);
 
   // Allocate a temporary JsonDocument
-  // Don't forget to change the capacity to match your requirements.
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<512> doc;
+  JsonDocument doc;
 
   // Deserialize the JSON document
   DeserializationError error = deserializeJson(doc, file);
@@ -63,7 +56,7 @@ void loadConfiguration(const char *filename, Config &config) {
 }
 
 // Saves the configuration to a file
-void saveConfiguration(const char *filename, const Config &config) {
+void saveConfiguration(const char* filename, const Config& config) {
   // Delete existing file, otherwise the configuration is appended to the file
   SD.remove(filename);
 
@@ -75,9 +68,7 @@ void saveConfiguration(const char *filename, const Config &config) {
   }
 
   // Allocate a temporary JsonDocument
-  // Don't forget to change the capacity to match your requirements.
-  // Use https://arduinojson.org/assistant to compute the capacity.
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
 
   // Set the values in the document
   doc["hostname"] = config.hostname;
@@ -93,7 +84,7 @@ void saveConfiguration(const char *filename, const Config &config) {
 }
 
 // Prints the content of a file to the Serial
-void printFile(const char *filename) {
+void printFile(const char* filename) {
   // Open file for reading
   File file = SD.open(filename);
   if (!file) {
@@ -114,7 +105,8 @@ void printFile(const char *filename) {
 void setup() {
   // Initialize serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // Initialize SD library
   const int chipSelect = 4;
@@ -144,7 +136,7 @@ void loop() {
 // ------------------
 //
 // File is an unbuffered stream, which is not optimal for ArduinoJson.
-// See: https://arduinojson.org/v6/how-to/improve-speed/
+// See: https://arduinojson.org/v7/how-to/improve-speed/
 
 // See also
 // --------
diff --git a/neosensor/libraries/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino b/neosensor/libraries/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino
index 6937a00df328ed3aaff65b8db6e73dec70de082f..1c20fe99b5f37d42863f1b40177ea7c5a48ff24c 100644
--- a/neosensor/libraries/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino
+++ b/neosensor/libraries/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino
@@ -1,17 +1,18 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to use DeserializationOption::Filter
 //
-// https://arduinojson.org/v6/example/filter/
+// https://arduinojson.org/v7/example/filter/
 
 #include <ArduinoJson.h>
 
 void setup() {
   // Initialize serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // The huge input: an extract from OpenWeatherMap response
   auto input_json = F(
@@ -33,12 +34,12 @@ void setup() {
       "1000000,\"timezone\":0,\"sunrise\":1581492085,\"sunset\":1581527294}}");
 
   // The filter: it contains "true" for each value we want to keep
-  StaticJsonDocument<200> filter;
+  JsonDocument filter;
   filter["list"][0]["dt"] = true;
   filter["list"][0]["main"]["temp"] = true;
 
   // Deserialize the document
-  StaticJsonDocument<400> doc;
+  JsonDocument doc;
   deserializeJson(doc, input_json, DeserializationOption::Filter(filter));
 
   // Print the result
diff --git a/neosensor/libraries/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino b/neosensor/libraries/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino
index cf4ab0d948122606cfdb1c09e814551ebaa6905d..d67b8859d6c3356464e388c2ed7bde5b46be321a 100644
--- a/neosensor/libraries/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino
+++ b/neosensor/libraries/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino
@@ -1,43 +1,32 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to generate a JSON document with ArduinoJson.
 //
-// https://arduinojson.org/v6/example/generator/
+// https://arduinojson.org/v7/example/generator/
 
 #include <ArduinoJson.h>
 
 void setup() {
   // Initialize Serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // Allocate the JSON document
-  //
-  // Inside the brackets, 200 is the RAM allocated to this document.
-  // Don't forget to change this value to match your requirement.
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<200> doc;
-
-  // StaticJsonObject allocates memory on the stack, it can be
-  // replaced by DynamicJsonDocument which allocates in the heap.
-  //
-  // DynamicJsonDocument  doc(200);
+  JsonDocument doc;
 
   // Add values in the document
-  //
   doc["sensor"] = "gps";
   doc["time"] = 1351824120;
 
-  // Add an array.
-  //
-  JsonArray data = doc.createNestedArray("data");
+  // Add an array
+  JsonArray data = doc["data"].to<JsonArray>();
   data.add(48.756080);
   data.add(2.302038);
 
-  // Generate the minified JSON and send it to the Serial port.
-  //
+  // Generate the minified JSON and send it to the Serial port
   serializeJson(doc, Serial);
   // The above line prints:
   // {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
@@ -45,8 +34,7 @@ void setup() {
   // Start a new line
   Serial.println();
 
-  // Generate the prettified JSON and send it to the Serial port.
-  //
+  // Generate the prettified JSON and send it to the Serial port
   serializeJsonPretty(doc, Serial);
   // The above line prints:
   // {
diff --git a/neosensor/libraries/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino b/neosensor/libraries/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino
index 72f311fea543d2ffe1a5b6328221cccf1ae1bd28..93151187e9a0212e74068dccf61d0a0131d59f25 100644
--- a/neosensor/libraries/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino
+++ b/neosensor/libraries/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to parse a JSON document in an HTTP response.
@@ -16,7 +16,7 @@
 //   ]
 // }
 //
-// https://arduinojson.org/v6/example/http-client/
+// https://arduinojson.org/v7/example/http-client/
 
 #include <ArduinoJson.h>
 #include <Ethernet.h>
@@ -25,7 +25,8 @@
 void setup() {
   // Initialize Serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // Initialize Ethernet library
   byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
@@ -77,9 +78,7 @@ void setup() {
   }
 
   // Allocate the JSON document
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
-  DynamicJsonDocument doc(capacity);
+  JsonDocument doc;
 
   // Parse JSON object
   DeserializationError error = deserializeJson(doc, client);
@@ -109,7 +108,7 @@ void loop() {
 // ------------------
 //
 // EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson.
-// See: https://arduinojson.org/v6/how-to/improve-speed/
+// See: https://arduinojson.org/v7/how-to/improve-speed/
 
 // See also
 // --------
diff --git a/neosensor/libraries/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino b/neosensor/libraries/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino
index a9d8b6d7b17c20f81fd9c66941b1fbc962223e7d..7220dcf6c07ef65f78534e7436bed2eba587de88 100644
--- a/neosensor/libraries/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino
+++ b/neosensor/libraries/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino
@@ -1,52 +1,37 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to deserialize a JSON document with ArduinoJson.
 //
-// https://arduinojson.org/v6/example/parser/
+// https://arduinojson.org/v7/example/parser/
 
 #include <ArduinoJson.h>
 
 void setup() {
   // Initialize serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // Allocate the JSON document
-  //
-  // Inside the brackets, 200 is the capacity of the memory pool in bytes.
-  // Don't forget to change this value to match your JSON document.
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<200> doc;
-
-  // StaticJsonDocument<N> allocates memory on the stack, it can be
-  // replaced by DynamicJsonDocument which allocates in the heap.
-  //
-  // DynamicJsonDocument doc(200);
+  JsonDocument doc;
 
   // JSON input string.
-  //
-  // Using a char[], as shown here, enables the "zero-copy" mode. This mode uses
-  // the minimal amount of memory because the JsonDocument stores pointers to
-  // the input buffer.
-  // If you use another type of input, ArduinoJson must copy the strings from
-  // the input to the JsonDocument, so you need to increase the capacity of the
-  // JsonDocument.
-  char json[] =
+  const char* json =
       "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
 
   // Deserialize the JSON document
   DeserializationError error = deserializeJson(doc, json);
 
-  // Test if parsing succeeds.
+  // Test if parsing succeeds
   if (error) {
     Serial.print(F("deserializeJson() failed: "));
     Serial.println(error.f_str());
     return;
   }
 
-  // Fetch values.
+  // Fetch the values
   //
   // Most of the time, you can rely on the implicit casts.
   // In other case, you can do doc["time"].as<long>();
@@ -55,7 +40,7 @@ void setup() {
   double latitude = doc["data"][0];
   double longitude = doc["data"][1];
 
-  // Print values.
+  // Print the values
   Serial.println(sensor);
   Serial.println(time);
   Serial.println(latitude, 6);
diff --git a/neosensor/libraries/ArduinoJson/examples/JsonServer/JsonServer.ino b/neosensor/libraries/ArduinoJson/examples/JsonServer/JsonServer.ino
index a897fce6da5f381175569592e5aee0af7f2e4dcb..d8b65c81a111487ce4e1d236c73bc7054d43c753 100644
--- a/neosensor/libraries/ArduinoJson/examples/JsonServer/JsonServer.ino
+++ b/neosensor/libraries/ArduinoJson/examples/JsonServer/JsonServer.ino
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to implement an HTTP server that sends a JSON document
@@ -13,7 +13,7 @@
 //   "digital": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]
 // }
 //
-// https://arduinojson.org/v6/example/http-server/
+// https://arduinojson.org/v7/example/http-server/
 
 #include <ArduinoJson.h>
 #include <Ethernet.h>
@@ -25,7 +25,8 @@ EthernetServer server(80);
 void setup() {
   // Initialize serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // Initialize Ethernet libary
   if (!Ethernet.begin(mac)) {
@@ -52,14 +53,14 @@ void loop() {
   Serial.println(F("New client"));
 
   // Read the request (we ignore the content in this example)
-  while (client.available()) client.read();
+  while (client.available())
+    client.read();
 
   // Allocate a temporary JsonDocument
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<500> doc;
+  JsonDocument doc;
 
   // Create the "analog" array
-  JsonArray analogValues = doc.createNestedArray("analog");
+  JsonArray analogValues = doc["analog"].to<JsonArray>();
   for (int pin = 0; pin < 6; pin++) {
     // Read the analog input
     int value = analogRead(pin);
@@ -69,7 +70,7 @@ void loop() {
   }
 
   // Create the "digital" array
-  JsonArray digitalValues = doc.createNestedArray("digital");
+  JsonArray digitalValues = doc["digital"].to<JsonArray>();
   for (int pin = 0; pin < 14; pin++) {
     // Read the digital input
     int value = digitalRead(pin);
@@ -101,7 +102,7 @@ void loop() {
 // ------------------
 //
 // EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson.
-// See: https://arduinojson.org/v6/how-to/improve-speed/
+// See: https://arduinojson.org/v7/how-to/improve-speed/
 
 // See also
 // --------
diff --git a/neosensor/libraries/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino b/neosensor/libraries/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino
index 6c369faa489e9f4f15d31a01972fc775ec3174d0..6a4e08f684b4f42410871e02e5fadad1859f8f17 100644
--- a/neosensor/libraries/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino
+++ b/neosensor/libraries/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to send a JSON document to a UDP socket.
@@ -17,7 +17,7 @@
 // $ ncat -ulp 8888
 // See https://nmap.org/ncat/
 //
-// https://arduinojson.org/v6/example/udp-beacon/
+// https://arduinojson.org/v7/example/udp-beacon/
 
 #include <ArduinoJson.h>
 #include <Ethernet.h>
@@ -32,7 +32,8 @@ EthernetUDP udp;
 void setup() {
   // Initialize serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // Initialize Ethernet libary
   if (!Ethernet.begin(mac)) {
@@ -46,11 +47,10 @@ void setup() {
 
 void loop() {
   // Allocate a temporary JsonDocument
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<500> doc;
+  JsonDocument doc;
 
   // Create the "analog" array
-  JsonArray analogValues = doc.createNestedArray("analog");
+  JsonArray analogValues = doc["analog"].to<JsonArray>();
   for (int pin = 0; pin < 6; pin++) {
     // Read the analog input
     int value = analogRead(pin);
@@ -60,7 +60,7 @@ void loop() {
   }
 
   // Create the "digital" array
-  JsonArray digitalValues = doc.createNestedArray("digital");
+  JsonArray digitalValues = doc["digital"].to<JsonArray>();
   for (int pin = 0; pin < 14; pin++) {
     // Read the digital input
     int value = digitalRead(pin);
@@ -90,7 +90,7 @@ void loop() {
 // ------------------
 //
 // EthernetUDP is an unbuffered stream, which is not optimal for ArduinoJson.
-// See: https://arduinojson.org/v6/how-to/improve-speed/
+// See: https://arduinojson.org/v7/how-to/improve-speed/
 
 // See also
 // --------
diff --git a/neosensor/libraries/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino b/neosensor/libraries/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino
index 1a54c3b3a1c4df19bc9592940a4b267202b1224a..dd7d7b6a418c0d0fe55c3e2916bf26881e6e8648 100644
--- a/neosensor/libraries/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino
+++ b/neosensor/libraries/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino
@@ -1,39 +1,24 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to deserialize a MessagePack document with
 // ArduinoJson.
 //
-// https://arduinojson.org/v6/example/msgpack-parser/
+// https://arduinojson.org/v7/example/msgpack-parser/
 
 #include <ArduinoJson.h>
 
 void setup() {
   // Initialize serial port
   Serial.begin(9600);
-  while (!Serial) continue;
+  while (!Serial)
+    continue;
 
   // Allocate the JSON document
-  //
-  // Inside the brackets, 200 is the capacity of the memory pool in bytes.
-  // Don't forget to change this value to match your JSON document.
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<200> doc;
-
-  // StaticJsonObject allocates memory on the stack, it can be
-  // replaced by DynamicJsonObject which allocates in the heap.
-  //
-  // DynamicJsonObject doc(200);
+  JsonDocument doc;
 
-  // MessagePack input string.
-  //
-  // Using a char[], as shown here, enables the "zero-copy" mode. This mode uses
-  // the minimal amount of memory because the JsonDocument stores pointers to
-  // the input buffer.
-  // If you use another type of input, ArduinoJson must copy the strings from
-  // the input to the JsonDocument, so you need to increase the capacity of the
-  // JsonDocument.
+  // The MessagePack input string
   uint8_t input[] = {131, 166, 115, 101, 110, 115, 111, 114, 163, 103, 112, 115,
                      164, 116, 105, 109, 101, 206, 80,  147, 50,  248, 164, 100,
                      97,  116, 97,  146, 203, 64,  72,  96,  199, 58,  188, 148,
@@ -45,16 +30,17 @@ void setup() {
   //   "data": [48.75608, 2.302038]
   // }
 
+  // Parse the input
   DeserializationError error = deserializeMsgPack(doc, input);
 
-  // Test if parsing succeeded.
+  // Test if parsing succeeded
   if (error) {
     Serial.print("deserializeMsgPack() failed: ");
     Serial.println(error.f_str());
     return;
   }
 
-  // Fetch values.
+  // Fetch the values
   //
   // Most of the time, you can rely on the implicit casts.
   // In other case, you can do doc["time"].as<long>();
@@ -63,7 +49,7 @@ void setup() {
   double latitude = doc["data"][0];
   double longitude = doc["data"][1];
 
-  // Print values.
+  // Print the values
   Serial.println(sensor);
   Serial.println(time);
   Serial.println(latitude, 6);
diff --git a/neosensor/libraries/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino b/neosensor/libraries/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino
index 68b8ec562b72d645ea1da59c071537ae8bceab92..ffeb79e5e71af7ac1085b9ac66a4aadca66f949e 100644
--- a/neosensor/libraries/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino
+++ b/neosensor/libraries/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows the different ways you can use Flash strings with
@@ -9,12 +9,12 @@
 // JsonDocument. Prefer plain old char*, as they are more efficient in term of
 // code size, speed, and memory usage.
 //
-// https://arduinojson.org/v6/example/progmem/
+// https://arduinojson.org/v7/example/progmem/
 
 #include <ArduinoJson.h>
 
 void setup() {
-  DynamicJsonDocument doc(1024);
+  JsonDocument doc;
 
   // You can use a Flash String as your JSON input.
   // WARNING: the strings in the input will be duplicated in the JsonDocument.
diff --git a/neosensor/libraries/ArduinoJson/examples/StringExample/StringExample.ino b/neosensor/libraries/ArduinoJson/examples/StringExample/StringExample.ino
index 3e51b915b82d601058fcefb5aebfcf6c3e376dcd..fb5f2433a388315b428da3da49cb9389be4ac166 100644
--- a/neosensor/libraries/ArduinoJson/examples/StringExample/StringExample.ino
+++ b/neosensor/libraries/ArduinoJson/examples/StringExample/StringExample.ino
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows the different ways you can use String with ArduinoJson.
@@ -8,12 +8,12 @@
 // JsonDocument. Prefer plain old char[], as they are more efficient in term of
 // code size, speed, and memory usage.
 //
-// https://arduinojson.org/v6/example/string/
+// https://arduinojson.org/v7/example/string/
 
 #include <ArduinoJson.h>
 
 void setup() {
-  DynamicJsonDocument doc(1024);
+  JsonDocument doc;
 
   // You can use a String as your JSON input.
   // WARNING: the string in the input  will be duplicated in the JsonDocument.
@@ -55,7 +55,6 @@ void setup() {
   }
 
   // Lastly, you can print the resulting JSON to a String
-  // WARNING: it doesn't replace the content but appends to it
   String output;
   serializeJson(doc, output);
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/CompileOptions.cmake b/neosensor/libraries/ArduinoJson/extras/CompileOptions.cmake
index 0cbd576c2a5cb9e0ecae35e422b99909b74b1926..5d9f8046a9123c8ceacb7b63deef8f4046984e47 100644
--- a/neosensor/libraries/ArduinoJson/extras/CompileOptions.cmake
+++ b/neosensor/libraries/ArduinoJson/extras/CompileOptions.cmake
@@ -1,3 +1,7 @@
+if(NOT DEFINED COVERAGE)
+	set(COVERAGE OFF)
+endif()
+
 if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
 	add_compile_options(
 		-pedantic
@@ -30,10 +34,20 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
 endif()
 
 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-	if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.8) AND(NOT ${COVERAGE}))
+	if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9) AND(NOT ${COVERAGE}))
 		add_compile_options(-g -Og)
-	else()
-		add_compile_options(-g -O0)
+	else() # GCC 4.8
+		add_compile_options(
+			-g
+			-O0 # GCC 4.8 doesn't support -Og
+			-Wno-shadow  # allow the same name for a function parameter and a member functions
+			-Wp,-w  # Disable preprocessing warnings (see below)
+		)
+		# GCC 4.8 doesn't support __has_include, so we need to help him
+		add_definitions(
+			-DARDUINOJSON_ENABLE_STD_STRING=1
+			-DARDUINOJSON_ENABLE_STD_STREAM=1
+		)
 	endif()
 
 	add_compile_options(
@@ -64,6 +78,9 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
 endif()
 
 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+	add_compile_options(-stdlib=libc++)
+	link_libraries(c++ m)
+
 	if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0) AND(NOT ${COVERAGE}))
 		add_compile_options(-g -Og)
 	else()
diff --git a/neosensor/libraries/ArduinoJson/extras/ci/espidf/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/ci/espidf/CMakeLists.txt
index 3b878969ac6c62c66a99146215dbfce7516e9b7c..38d393de24bdd943c745c211e070209ff7e49597 100644
--- a/neosensor/libraries/ArduinoJson/extras/ci/espidf/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/ci/espidf/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 cmake_minimum_required(VERSION 3.5)
diff --git a/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt
index 3e10ef8f8ff5866dd76d739b0e533dc7e669ce15..e32eb7c8f9bd53bff9e2e5f871b09474973d913d 100644
--- a/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 idf_component_register(
diff --git a/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/main.cpp b/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/main.cpp
index 4818a6792c3e7ddaf15362c35c3c01cb6aed1dec..f65aa39848a9a6a7801ceba2922c0b7e1aa1418f 100644
--- a/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/main.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/ci/espidf/main/main.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 
 extern "C" void app_main() {
   char buffer[256];
-  StaticJsonDocument<200> doc;
+  JsonDocument doc;
 
   doc["hello"] = "world";
   serializeJson(doc, buffer);
diff --git a/neosensor/libraries/ArduinoJson/extras/conf_test/avr.cpp b/neosensor/libraries/ArduinoJson/extras/conf_test/avr.cpp
index b2b105e394bf6049edd1fba9965a03402d6bcad1..e0061eaf9966957413c5a44474a843fa6b09e588 100644
--- a/neosensor/libraries/ArduinoJson/extras/conf_test/avr.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/conf_test/avr.cpp
@@ -4,8 +4,7 @@ static_assert(ARDUINOJSON_ENABLE_PROGMEM == 1, "ARDUINOJSON_ENABLE_PROGMEM");
 
 static_assert(ARDUINOJSON_USE_LONG_LONG == 0, "ARDUINOJSON_USE_LONG_LONG");
 
-static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 1,
-              "ARDUINOJSON_SLOT_OFFSET_SIZE");
+static_assert(ARDUINOJSON_SLOT_ID_SIZE == 1, "ARDUINOJSON_SLOT_ID_SIZE");
 
 static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
 
diff --git a/neosensor/libraries/ArduinoJson/extras/conf_test/esp8266.cpp b/neosensor/libraries/ArduinoJson/extras/conf_test/esp8266.cpp
index caf70b2a3f2ebb79431df0dfa8f8e99509929da5..b2e6d06fe478cfff0b4abb4e08570690f6362bb6 100644
--- a/neosensor/libraries/ArduinoJson/extras/conf_test/esp8266.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/conf_test/esp8266.cpp
@@ -2,8 +2,7 @@
 
 static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG");
 
-static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 2,
-              "ARDUINOJSON_SLOT_OFFSET_SIZE");
+static_assert(ARDUINOJSON_SLOT_ID_SIZE == 2, "ARDUINOJSON_SLOT_ID_SIZE");
 
 static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
 
diff --git a/neosensor/libraries/ArduinoJson/extras/conf_test/x64.cpp b/neosensor/libraries/ArduinoJson/extras/conf_test/x64.cpp
index 97838e34e92837e85e500e61838ecd093a1ee64a..2b533bf7030dc6555305651fec03e104a2df685e 100644
--- a/neosensor/libraries/ArduinoJson/extras/conf_test/x64.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/conf_test/x64.cpp
@@ -2,14 +2,13 @@
 
 static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG");
 
-static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 4,
-              "ARDUINOJSON_SLOT_OFFSET_SIZE");
+static_assert(ARDUINOJSON_SLOT_ID_SIZE == 4, "ARDUINOJSON_SLOT_ID_SIZE");
 
 static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
 
 static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
 
-static_assert(sizeof(ArduinoJson::detail::VariantSlot) == 32,
+static_assert(sizeof(ArduinoJson::detail::VariantSlot) == 24,
               "sizeof(VariantSlot)");
 
 int main() {}
diff --git a/neosensor/libraries/ArduinoJson/extras/conf_test/x86.cpp b/neosensor/libraries/ArduinoJson/extras/conf_test/x86.cpp
index f3dab30ffaa0388fd4cb743803e164739e3a6f44..a9874efd45a3bade89ba1c258fa92b585ae32155 100644
--- a/neosensor/libraries/ArduinoJson/extras/conf_test/x86.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/conf_test/x86.cpp
@@ -2,8 +2,7 @@
 
 static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG");
 
-static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 2,
-              "ARDUINOJSON_SLOT_OFFSET_SIZE");
+static_assert(ARDUINOJSON_SLOT_ID_SIZE == 2, "ARDUINOJSON_SLOT_ID_SIZE");
 
 static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
 
diff --git a/neosensor/libraries/ArduinoJson/extras/fuzzing/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/fuzzing/CMakeLists.txt
index 1d439f46b24498b96e5352c2f25db01d2358770f..cc6e05d68cb8c32bd39a2dfd956b07784b6c3c2d 100644
--- a/neosensor/libraries/ArduinoJson/extras/fuzzing/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/fuzzing/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 set(CMAKE_CXX_STANDARD 11)
@@ -52,7 +52,16 @@ macro(add_fuzzer name)
 	)
 endmacro()
 
-if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6)
+# Needs Clang 6+ to compile
+if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6)
+	if(DEFINED ENV{GITHUB_ACTIONS} AND CMAKE_CXX_COMPILER_VERSION MATCHES "^11\\.")
+		# Clang 11 fails on GitHub Actions with the following error:
+		# > ERROR: UndefinedBehaviorSanitizer failed to allocate 0x0 (0) bytes of SetAlternateSignalStack (error code: 22)
+		# > Sanitizer CHECK failed: /build/llvm-toolchain-11-mnvtwk/llvm-toolchain-11-11.1.0/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp:54 ((0 && "unable to mmap")) != (0) (0, 0)
+		message(WARNING "Fuzzing is disabled on GitHub Actions to workaround a bug in Clang 11")
+		return()
+	endif()
+
 	add_fuzzer(json)
 	add_fuzzer(msgpack)
 endif()
diff --git a/neosensor/libraries/ArduinoJson/extras/fuzzing/json_fuzzer.cpp b/neosensor/libraries/ArduinoJson/extras/fuzzing/json_fuzzer.cpp
index 043cd426e553a9fc38fb6e01a174601e7b9bb5f7..8d78aa345b4252bec4a7ad3e38cd5708be52f64f 100644
--- a/neosensor/libraries/ArduinoJson/extras/fuzzing/json_fuzzer.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/fuzzing/json_fuzzer.cpp
@@ -1,7 +1,7 @@
 #include <ArduinoJson.h>
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   DeserializationError error = deserializeJson(doc, data, size);
   if (!error) {
     std::string json;
diff --git a/neosensor/libraries/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp b/neosensor/libraries/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp
index b6ab14147cd3802592dd1767ac32c68efbccffe4..3011501b7e2b71fcd4204efd99984f75055b3849 100644
--- a/neosensor/libraries/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp
@@ -1,7 +1,7 @@
 #include <ArduinoJson.h>
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   DeserializationError error = deserializeMsgPack(doc, data, size);
   if (!error) {
     std::string json;
diff --git a/neosensor/libraries/ArduinoJson/extras/fuzzing/reproducer.cpp b/neosensor/libraries/ArduinoJson/extras/fuzzing/reproducer.cpp
index a004d01eee112a9b499bcf85d8faaa2b705cb144..1595a9af637d19a8b3806ddbcb34a6351e9ac047 100644
--- a/neosensor/libraries/ArduinoJson/extras/fuzzing/reproducer.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/fuzzing/reproducer.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 // This file is NOT use by Google's OSS fuzz
diff --git a/neosensor/libraries/ArduinoJson/extras/scripts/build-arduino-package.sh b/neosensor/libraries/ArduinoJson/extras/scripts/build-arduino-package.sh
deleted file mode 100755
index 09f6efb3077dc646522cb9e83b592e699db5c62c..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/scripts/build-arduino-package.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-INPUT=$1
-OUTPUT=$2
-
-cd "$INPUT"
-
-# remove existing file
-rm -f "$OUTPUT"
-
-# create zip
-7z a "$OUTPUT" \
-	-xr!.vs \
-	CHANGELOG.md \
-	examples \
-	src \
-	keywords.txt \
-	library.properties \
-	LICENSE.txt \
-	README.md \
-	ArduinoJson.h
diff --git a/neosensor/libraries/ArduinoJson/extras/scripts/get-release-page.sh b/neosensor/libraries/ArduinoJson/extras/scripts/get-release-page.sh
index 9bf943ef0d7f1fce2bf304e3a5e4a2ca09fcbe49..9eb53e1f09c8ab9bba63c07164568d0b7940067f 100755
--- a/neosensor/libraries/ArduinoJson/extras/scripts/get-release-page.sh
+++ b/neosensor/libraries/ArduinoJson/extras/scripts/get-release-page.sh
@@ -8,7 +8,7 @@ ARDUINOJSON_H="$3"
 
 cat << END
 ---
-branch: v6
+branch: v7
 version: $VERSION
 date: '$(date +'%Y-%m-%d')'
 $(extras/scripts/wandbox/publish.sh "$ARDUINOJSON_H")
diff --git a/neosensor/libraries/ArduinoJson/extras/scripts/publish-particle-library.sh b/neosensor/libraries/ArduinoJson/extras/scripts/publish-particle-library.sh
index 3ee7f91226bdbeb08d6f1380bdda1e87b180e611..d410c470a54a31b74d2cd0a98ba03b4e23184d8c 100755
--- a/neosensor/libraries/ArduinoJson/extras/scripts/publish-particle-library.sh
+++ b/neosensor/libraries/ArduinoJson/extras/scripts/publish-particle-library.sh
@@ -14,5 +14,5 @@ cp -r "$SOURCE_DIR/src" "$WORK_DIR/"
 cp -r "$SOURCE_DIR/examples" "$WORK_DIR/"
 
 cd "$WORK_DIR"
-particle library upload -v
-particle library publish -v
+particle library upload
+particle library publish
diff --git a/neosensor/libraries/ArduinoJson/extras/scripts/publish.sh b/neosensor/libraries/ArduinoJson/extras/scripts/publish.sh
index af9546a6a55e3cdf57c3d1f3230b1260a488511b..60af5fc463c9bea7f614e396b861f6c5f3634c6f 100755
--- a/neosensor/libraries/ArduinoJson/extras/scripts/publish.sh
+++ b/neosensor/libraries/ArduinoJson/extras/scripts/publish.sh
@@ -2,7 +2,7 @@
 
 set -eu
 
-which awk sed jq 7z curl perl >/dev/null
+which awk sed jq curl perl >/dev/null
 
 cd "$(dirname "$0")/../.."
 
@@ -15,6 +15,7 @@ VERSION="$1"
 DATE=$(date +%F)
 TAG="v$VERSION"
 VERSION_REGEX='[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?'
+STARS=$(curl -s https://api.github.com/repos/bblanchon/ArduinoJson | jq '.stargazers_count')
 
 update_version_in_source () {
 	IFS=".-" read MAJOR MINOR REVISION EXTRA < <(echo "$VERSION")
@@ -29,16 +30,25 @@ update_version_in_source () {
 	sed -i~ -bE "s/(project\\s*\\(ArduinoJson\\s+VERSION\\s+).*?\\)/\\1$MAJOR.$MINOR.$REVISION)/" CMakeLists.txt
 	rm CMakeLists.txt~
 
-	sed -i~ -bE "s/\"version\":.*$/\"version\": \"$VERSION\",/" library.json
+	sed -i~ -bE \
+		-e "s/\"version\":.*$/\"version\": \"$VERSION\",/" \
+		-e "s/[0-9]+ stars/$STARS stars/" \
+		library.json
 	rm library.json~
 
-	sed -i~ -bE "s/version=.*$/version=$VERSION/" library.properties
+	sed -i~ -bE \
+		-e "s/version=.*$/version=$VERSION/" \
+		-e "s/[0-9]+ stars/$STARS stars/" \
+		library.properties
 	rm library.properties~
 
 	sed -i~ -bE "s/version: .*$/version: $VERSION.{build}/" appveyor.yml
 	rm appveyor.yml~
 
-	sed -i~ -bE "s/^version: .*$/version: \"$VERSION\"/" idf_component.yml
+	sed -i~ -bE \
+		-e "s/^version: .*$/version: \"$VERSION\"/" \
+		-e "s/[0-9]+ stars/$STARS stars/" \
+		idf_component.yml
 	rm idf_component.yml~
 
 	sed -i~ -bE \
@@ -70,7 +80,6 @@ commit_new_version
 add_tag
 push
 
-extras/scripts/build-arduino-package.sh . "../ArduinoJson-$TAG.zip"
 extras/scripts/build-single-header.sh "src/ArduinoJson.h" "../ArduinoJson-$TAG.h"
 extras/scripts/build-single-header.sh "src/ArduinoJson.hpp" "../ArduinoJson-$TAG.hpp"
 extras/scripts/get-release-page.sh "$VERSION" "CHANGELOG.md" "../ArduinoJson-$TAG.h" > "../ArduinoJson-$TAG.md"
diff --git a/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp b/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp
index 474a0c3092dbc80dfca1372e9f2edf7a88764668..313f93b027069e0b4bdaa09a6dfd1675fb4609c7 100644
--- a/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to generate a JSON document with ArduinoJson.
@@ -9,30 +9,18 @@
 
 int main() {
   // Allocate the JSON document
-  //
-  // Inside the brackets, 200 is the RAM allocated to this document.
-  // Don't forget to change this value to match your requirement.
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<200> doc;
+  JsonDocument doc;
 
-  // StaticJsonObject allocates memory on the stack, it can be
-  // replaced by DynamicJsonDocument which allocates in the heap.
-  //
-  // DynamicJsonDocument  doc(200);
-
-  // Add values in the document
-  //
+  // Add values in the document.
   doc["sensor"] = "gps";
   doc["time"] = 1351824120;
 
-  // Add an array.
-  //
-  JsonArray data = doc.createNestedArray("data");
+  // Add an array
+  JsonArray data = doc["data"].to<JsonArray>();
   data.add(48.756080);
   data.add(2.302038);
 
   // Generate the minified JSON and send it to STDOUT
-  //
   serializeJson(doc, std::cout);
   // The above line prints:
   // {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
@@ -41,7 +29,6 @@ int main() {
   std::cout << std::endl;
 
   // Generate the prettified JSON and send it to STDOUT
-  //
   serializeJsonPretty(doc, std::cout);
   // The above line prints:
   // {
diff --git a/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp b/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp
index 396f98cb0178c2473b76ad426574c9cdbb5b9740..cf653a4f98e375fb5edbdc94a7eeda944b766912 100644
--- a/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to deserialize a JSON document with ArduinoJson.
@@ -9,38 +9,22 @@
 
 int main() {
   // Allocate the JSON document
-  //
-  // Inside the brackets, 200 is the capacity of the memory pool in bytes.
-  // Don't forget to change this value to match your JSON document.
-  // Use https://arduinojson.org/v6/assistant to compute the capacity.
-  StaticJsonDocument<300> doc;
-
-  // StaticJsonDocument<N> allocates memory on the stack, it can be
-  // replaced by DynamicJsonDocument which allocates in the heap.
-  //
-  // DynamicJsonDocument doc(200);
+  JsonDocument doc;
 
-  // JSON input string.
-  //
-  // Using a char[], as shown here, enables the "zero-copy" mode. This mode uses
-  // the minimal amount of memory because the JsonDocument stores pointers to
-  // the input buffer.
-  // If you use another type of input, ArduinoJson must copy the strings from
-  // the input to the JsonDocument, so you need to increase the capacity of the
-  // JsonDocument.
-  char json[] =
+  // JSON input string
+  const char* json =
       "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
 
   // Deserialize the JSON document
   DeserializationError error = deserializeJson(doc, json);
 
-  // Test if parsing succeeds.
+  // Test if parsing succeeds
   if (error) {
     std::cerr << "deserializeJson() failed: " << error.c_str() << std::endl;
     return 1;
   }
 
-  // Fetch values.
+  // Fetch the values
   //
   // Most of the time, you can rely on the implicit casts.
   // In other case, you can do doc["time"].as<long>();
@@ -49,7 +33,7 @@ int main() {
   double latitude = doc["data"][0];
   double longitude = doc["data"][1];
 
-  // Print values.
+  // Print the values
   std::cout << sensor << std::endl;
   std::cout << time << std::endl;
   std::cout << latitude << std::endl;
diff --git a/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp b/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp
index 763de84e6a5a6ffd87a6c39c31dbfa74d861f7d7..cb797aafa44d5db1e59573e1428f0f681d6154a7 100644
--- a/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // This example shows how to generate a JSON document with ArduinoJson.
@@ -9,22 +9,9 @@
 
 int main() {
   // Allocate the JSON document
-  //
-  // Inside the brackets, 300 is the size of the memory pool in bytes.
-  // Don't forget to change this value to match your JSON document.
-  // Use https://arduinojson.org/assistant to compute the capacity.
-  StaticJsonDocument<300> doc;
-
-  // StaticJsonObject allocates memory on the stack, it can be
-  // replaced by DynamicJsonObject which allocates in the heap.
-  //
-  // DynamicJsonObject doc(200);
+  JsonDocument doc;
 
-  // MessagePack input string.
-  //
-  // It's better to use a char[] as shown here.
-  // If you use a const char* or a String, ArduinoJson will
-  // have to make a copy of the input in the JsonBuffer.
+  // The MessagePack input string
   uint8_t input[] = {131, 166, 115, 101, 110, 115, 111, 114, 163, 103, 112, 115,
                      164, 116, 105, 109, 101, 206, 80,  147, 50,  248, 164, 100,
                      97,  116, 97,  146, 203, 64,  72,  96,  199, 58,  188, 148,
@@ -36,20 +23,16 @@ int main() {
   //   "data": [48.75608, 2.302038]
   // }
 
-  // doc of the object tree.
-  //
-  // It's a reference to the JsonObject, the actual bytes are inside the
-  // JsonBuffer with all the other nodes of the object tree.
-  // Memory is freed when jsonBuffer goes out of scope.
+  // Parse the input
   DeserializationError error = deserializeMsgPack(doc, input);
 
-  // Test if parsing succeeds.
+  // Test if parsing succeeds
   if (error) {
     std::cerr << "deserializeMsgPack() failed: " << error.c_str() << std::endl;
     return 1;
   }
 
-  // Fetch values.
+  // Fetch the values
   //
   // Most of the time, you can rely on the implicit casts.
   // In other case, you can do doc["time"].as<long>();
@@ -58,7 +41,7 @@ int main() {
   double latitude = doc["data"][0];
   double longitude = doc["data"][1];
 
-  // Print values.
+  // Print the values
   std::cout << sensor << std::endl;
   std::cout << time << std::endl;
   std::cout << latitude << std::endl;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/CMakeLists.txt
index c62401f97094598393a6e72edc8a599c36c29b51..f79a3c927a39548d721aad7286fe84259cb24583 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 set(CMAKE_CXX_STANDARD 11)
@@ -12,15 +12,19 @@ link_libraries(ArduinoJson catch)
 include_directories(Helpers)
 add_subdirectory(Cpp17)
 add_subdirectory(Cpp20)
+add_subdirectory(Deprecated)
 add_subdirectory(FailingBuilds)
 add_subdirectory(IntegrationTests)
 add_subdirectory(JsonArray)
+add_subdirectory(JsonArrayConst)
 add_subdirectory(JsonDeserializer)
 add_subdirectory(JsonDocument)
 add_subdirectory(JsonObject)
+add_subdirectory(JsonObjectConst)
 add_subdirectory(JsonSerializer)
 add_subdirectory(JsonVariant)
-add_subdirectory(MemoryPool)
+add_subdirectory(JsonVariantConst)
+add_subdirectory(ResourceManager)
 add_subdirectory(Misc)
 add_subdirectory(MixedConfiguration)
 add_subdirectory(MsgPackDeserializer)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt
index 62f2784ff96c994f36d11d36842051cc746f13cc..ff5cc0e052b2b5a29539caca2bb03cb2c3e7a2e7 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 if(MSVC_VERSION LESS 1910)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/string_view.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/string_view.cpp
index 7ef656269714dfe1fb9fdd52fd3b79f67dd3e91d..92e0a62a5dfd2fbc5ebe199d133a29247f0d5185 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/string_view.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Cpp17/string_view.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 // we expect ArduinoJson.h to include <string_view>
@@ -11,12 +11,18 @@
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
 #if !ARDUINOJSON_ENABLE_STRING_VIEW
 #  error ARDUINOJSON_ENABLE_STRING_VIEW must be set to 1
 #endif
 
+using ArduinoJson::detail::sizeofArray;
+
 TEST_CASE("string_view") {
-  StaticJsonDocument<256> doc;
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("deserializeJson()") {
@@ -61,16 +67,15 @@ TEST_CASE("string_view") {
 
   SECTION("String deduplication") {
     doc.add(std::string_view("example one", 7));
-    REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1) + 8);
-
     doc.add(std::string_view("example two", 7));
-    REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
-
     doc.add(std::string_view("example\0tree", 12));
-    REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(3) + 21);
-
     doc.add(std::string_view("example\0tree and a half", 12));
-    REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(4) + 21);
+
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                             Allocate(sizeofString("example tree")),
+                         });
   }
 
   SECTION("as<std::string_view>()") {
@@ -88,7 +93,7 @@ TEST_CASE("string_view") {
   }
 
   SECTION("String containing NUL") {
-    doc.set(std::string("hello\0world", 11));
+    doc.set("hello\0world"_s);
     REQUIRE(doc.as<std::string_view>().size() == 11);
     REQUIRE(doc.as<std::string_view>() == std::string_view("hello\0world", 11));
   }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt
index e993e974b6008418d07dfb6ae5f0542724f36c60..637af5c477affef3ee6d4f627f1d81d3e5f06bc8 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 if(MSVC_VERSION LESS 1910)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp
index dc41e87570c2d0fb5d7c0c4918395f366c4ab9fe..72dd50f32f2f1356dccb372e94cd36d39ed33720 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp
@@ -4,7 +4,7 @@
 #include <string>
 
 TEST_CASE("C++20 smoke test") {
-  StaticJsonDocument<128> doc;
+  JsonDocument doc;
 
   deserializeJson(doc, "{\"hello\":\"world\"}");
   REQUIRE(doc["hello"] == "world");
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/BasicJsonDocument.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/BasicJsonDocument.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0b69aee41f9e022b0cd5e52e8edf6a1166877884
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/BasicJsonDocument.cpp
@@ -0,0 +1,69 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include <string>
+
+using ArduinoJson::detail::is_base_of;
+
+static std::string allocatorLog;
+
+struct CustomAllocator {
+  CustomAllocator() {
+    allocatorLog = "";
+  }
+
+  void* allocate(size_t n) {
+    allocatorLog += "A";
+    return malloc(n);
+  }
+
+  void deallocate(void* p) {
+    free(p);
+    allocatorLog += "D";
+  }
+
+  void* reallocate(void* p, size_t n) {
+    allocatorLog += "R";
+    return realloc(p, n);
+  }
+};
+
+TEST_CASE("BasicJsonDocument") {
+  allocatorLog.clear();
+
+  SECTION("is a JsonDocument") {
+    REQUIRE(
+        is_base_of<JsonDocument, BasicJsonDocument<CustomAllocator>>::value ==
+        true);
+  }
+
+  SECTION("deserialize / serialize") {
+    BasicJsonDocument<CustomAllocator> doc(256);
+    deserializeJson(doc, "{\"hello\":\"world\"}");
+    REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\"}");
+    doc.clear();
+    REQUIRE(allocatorLog == "ARAARDDD");
+  }
+
+  SECTION("copy") {
+    BasicJsonDocument<CustomAllocator> doc(256);
+    doc["hello"] = "world";
+    auto copy = doc;
+    REQUIRE(copy.as<std::string>() == "{\"hello\":\"world\"}");
+    REQUIRE(allocatorLog == "AA");
+  }
+
+  SECTION("capacity") {
+    BasicJsonDocument<CustomAllocator> doc(256);
+    REQUIRE(doc.capacity() == 256);
+  }
+
+  SECTION("garbageCollect()") {
+    BasicJsonDocument<CustomAllocator> doc(256);
+    doc.garbageCollect();
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9cefe82c310c75be8659f8b96a6a96107bffe432
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/CMakeLists.txt
@@ -0,0 +1,34 @@
+# ArduinoJson - https://arduinojson.org
+# Copyright © 2014-2024, Benoit BLANCHON
+# MIT License
+
+if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
+	add_compile_options(
+		-w
+	)
+endif()
+
+if(MSVC)
+	add_compile_options(
+		/wd4996
+	)
+endif()
+
+add_executable(DeprecatedTests
+	add.cpp
+	createNestedArray.cpp
+	createNestedObject.cpp
+	BasicJsonDocument.cpp
+	DynamicJsonDocument.cpp
+	macros.cpp
+	memoryUsage.cpp
+	shallowCopy.cpp
+	StaticJsonDocument.cpp
+)
+
+add_test(Deprecated DeprecatedTests)
+
+set_tests_properties(Deprecated
+	PROPERTIES
+	LABELS "Catch"
+)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/DynamicJsonDocument.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/DynamicJsonDocument.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..df9634b9cb58e87932d2e284a210cfecef77a92e
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/DynamicJsonDocument.cpp
@@ -0,0 +1,37 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+using ArduinoJson::detail::is_base_of;
+
+TEST_CASE("DynamicJsonDocument") {
+  SECTION("is a JsonDocument") {
+    REQUIRE(is_base_of<JsonDocument, DynamicJsonDocument>::value == true);
+  }
+
+  SECTION("deserialize / serialize") {
+    DynamicJsonDocument doc(256);
+    deserializeJson(doc, "{\"hello\":\"world\"}");
+    REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\"}");
+  }
+
+  SECTION("copy") {
+    DynamicJsonDocument doc(256);
+    doc["hello"] = "world";
+    auto copy = doc;
+    REQUIRE(copy.as<std::string>() == "{\"hello\":\"world\"}");
+  }
+
+  SECTION("capacity") {
+    DynamicJsonDocument doc(256);
+    REQUIRE(doc.capacity() == 256);
+  }
+
+  SECTION("garbageCollect()") {
+    DynamicJsonDocument doc(256);
+    doc.garbageCollect();
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/StaticJsonDocument.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/StaticJsonDocument.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..60ca6f226514d3f1c8204260fe8d54074b2959b9
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/StaticJsonDocument.cpp
@@ -0,0 +1,32 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+using ArduinoJson::detail::is_base_of;
+
+TEST_CASE("StaticJsonDocument") {
+  SECTION("is a JsonDocument") {
+    REQUIRE(is_base_of<JsonDocument, StaticJsonDocument<256>>::value == true);
+  }
+
+  SECTION("deserialize / serialize") {
+    StaticJsonDocument<256> doc;
+    deserializeJson(doc, "{\"hello\":\"world\"}");
+    REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\"}");
+  }
+
+  SECTION("copy") {
+    StaticJsonDocument<256> doc;
+    doc["hello"] = "world";
+    auto copy = doc;
+    REQUIRE(copy.as<std::string>() == "{\"hello\":\"world\"}");
+  }
+
+  SECTION("capacity") {
+    StaticJsonDocument<256> doc;
+    REQUIRE(doc.capacity() == 256);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/add.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/add.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..de961d13bbf329aff33938fe187b256853d4e2d6
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/add.cpp
@@ -0,0 +1,38 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArray::add()") {
+  JsonDocument doc;
+  JsonArray array = doc.to<JsonArray>();
+  array.add().set(42);
+  REQUIRE(doc.as<std::string>() == "[42]");
+}
+
+TEST_CASE("JsonDocument::add()") {
+  JsonDocument doc;
+  doc.add().set(42);
+  REQUIRE(doc.as<std::string>() == "[42]");
+}
+
+TEST_CASE("ElementProxy::add()") {
+  JsonDocument doc;
+  doc[0].add().set(42);
+  REQUIRE(doc.as<std::string>() == "[[42]]");
+}
+
+TEST_CASE("MemberProxy::add()") {
+  JsonDocument doc;
+  doc["x"].add().set(42);
+  REQUIRE(doc.as<std::string>() == "{\"x\":[42]}");
+}
+
+TEST_CASE("JsonVariant::add()") {
+  JsonDocument doc;
+  JsonVariant v = doc.add();
+  v.add().set(42);
+  REQUIRE(doc.as<std::string>() == "[[42]]");
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/createNestedArray.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/createNestedArray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6efbd8800a7020b43a425d648b95a7ac310f591a
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/createNestedArray.cpp
@@ -0,0 +1,113 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include <string>
+
+#include "Literals.hpp"
+
+TEST_CASE("JsonDocument::createNestedArray()") {
+  JsonDocument doc;
+
+  SECTION("createNestedArray()") {
+    JsonArray array = doc.createNestedArray();
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "[[42]]");
+  }
+
+  SECTION("createNestedArray(const char*)") {
+    JsonArray array = doc.createNestedArray("key");
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+
+  SECTION("createNestedArray(std::string)") {
+    JsonArray array = doc.createNestedArray("key"_s);
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("createNestedArray(VLA)") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "key");
+    JsonArray array = doc.createNestedArray(vla);
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+#endif
+}
+
+TEST_CASE("JsonArray::createNestedArray()") {
+  JsonDocument doc;
+  JsonArray array = doc.to<JsonArray>();
+  JsonArray nestedArray = array.createNestedArray();
+  nestedArray.add(42);
+  REQUIRE(doc.as<std::string>() == "[[42]]");
+}
+
+TEST_CASE("JsonObject::createNestedArray()") {
+  JsonDocument doc;
+  JsonObject object = doc.to<JsonObject>();
+
+  SECTION("createNestedArray(const char*)") {
+    JsonArray array = object.createNestedArray("key");
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+
+  SECTION("createNestedArray(std::string)") {
+    JsonArray array = object.createNestedArray("key"_s);
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("createNestedArray(VLA)") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "key");
+    JsonArray array = object.createNestedArray(vla);
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+#endif
+}
+
+TEST_CASE("JsonVariant::createNestedArray()") {
+  JsonDocument doc;
+  JsonVariant variant = doc.to<JsonVariant>();
+
+  SECTION("createNestedArray()") {
+    JsonArray array = variant.createNestedArray();
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "[[42]]");
+  }
+
+  SECTION("createNestedArray(const char*)") {
+    JsonArray array = variant.createNestedArray("key");
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+
+  SECTION("createNestedArray(std::string)") {
+    JsonArray array = variant.createNestedArray("key"_s);
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("createNestedArray(VLA)") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "key");
+    JsonArray array = variant.createNestedArray(vla);
+    array.add(42);
+    REQUIRE(doc.as<std::string>() == "{\"key\":[42]}");
+  }
+#endif
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/createNestedObject.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/createNestedObject.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b73c8b3439da73fbd28b72fa51bb4d605f42f8c
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/createNestedObject.cpp
@@ -0,0 +1,113 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include <string>
+
+#include "Literals.hpp"
+
+TEST_CASE("JsonDocument::createNestedObject()") {
+  JsonDocument doc;
+
+  SECTION("createNestedObject()") {
+    JsonObject object = doc.createNestedObject();
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "[{\"hello\":\"world\"}]");
+  }
+
+  SECTION("createNestedObject(const char*)") {
+    JsonObject object = doc.createNestedObject("key");
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+
+  SECTION("createNestedObject(std::string)") {
+    JsonObject object = doc.createNestedObject("key"_s);
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("createNestedObject(VLA)") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "key");
+    JsonObject object = doc.createNestedObject(vla);
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+#endif
+}
+
+TEST_CASE("JsonArray::createNestedObject()") {
+  JsonDocument doc;
+  JsonArray array = doc.to<JsonArray>();
+  JsonObject object = array.createNestedObject();
+  object["hello"] = "world";
+  REQUIRE(doc.as<std::string>() == "[{\"hello\":\"world\"}]");
+}
+
+TEST_CASE("JsonObject::createNestedObject()") {
+  JsonDocument doc;
+  JsonObject object = doc.to<JsonObject>();
+
+  SECTION("createNestedObject(const char*)") {
+    JsonObject nestedObject = object.createNestedObject("key");
+    nestedObject["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+
+  SECTION("createNestedObject(std::string)") {
+    JsonObject nestedObject = object.createNestedObject("key"_s);
+    nestedObject["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("createNestedObject(VLA)") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "key");
+    JsonObject nestedObject = object.createNestedObject(vla);
+    nestedObject["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+#endif
+}
+
+TEST_CASE("JsonVariant::createNestedObject()") {
+  JsonDocument doc;
+  JsonVariant variant = doc.to<JsonVariant>();
+
+  SECTION("createNestedObject()") {
+    JsonObject object = variant.createNestedObject();
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "[{\"hello\":\"world\"}]");
+  }
+
+  SECTION("createNestedObject(const char*)") {
+    JsonObject object = variant.createNestedObject("key");
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+
+  SECTION("createNestedObject(std::string)") {
+    JsonObject object = variant.createNestedObject("key"_s);
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("createNestedObject(VLA)") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "key");
+    JsonObject object = variant.createNestedObject(vla);
+    object["hello"] = "world";
+    REQUIRE(doc.as<std::string>() == "{\"key\":{\"hello\":\"world\"}}");
+  }
+#endif
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/macros.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/macros.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..36610c7dde945a6bc53b4679c082568d144f4ff4
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/macros.cpp
@@ -0,0 +1,18 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JSON_ARRAY_SIZE") {
+  REQUIRE(JSON_ARRAY_SIZE(10) == ArduinoJson::detail::sizeofArray(10));
+}
+
+TEST_CASE("JSON_OBJECT_SIZE") {
+  REQUIRE(JSON_OBJECT_SIZE(10) == ArduinoJson::detail::sizeofObject(10));
+}
+
+TEST_CASE("JSON_STRING_SIZE") {
+  REQUIRE(JSON_STRING_SIZE(10) == 11);  // issue #2054
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/memoryUsage.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/memoryUsage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..00b5bbbd86fb8bb114a2c039d8ecaaa17cea0150
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/memoryUsage.cpp
@@ -0,0 +1,51 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArray::memoryUsage()") {
+  JsonArray array;
+  REQUIRE(array.memoryUsage() == 0);
+}
+
+TEST_CASE("JsonArrayConst::memoryUsage()") {
+  JsonArrayConst array;
+  REQUIRE(array.memoryUsage() == 0);
+}
+
+TEST_CASE("JsonDocument::memoryUsage()") {
+  JsonDocument doc;
+  REQUIRE(doc.memoryUsage() == 0);
+}
+
+TEST_CASE("JsonObject::memoryUsage()") {
+  JsonObject array;
+  REQUIRE(array.memoryUsage() == 0);
+}
+
+TEST_CASE("JsonObjectConst::memoryUsage()") {
+  JsonObjectConst array;
+  REQUIRE(array.memoryUsage() == 0);
+}
+
+TEST_CASE("JsonVariant::memoryUsage()") {
+  JsonVariant doc;
+  REQUIRE(doc.memoryUsage() == 0);
+}
+
+TEST_CASE("JsonVariantConst::memoryUsage()") {
+  JsonVariantConst doc;
+  REQUIRE(doc.memoryUsage() == 0);
+}
+
+TEST_CASE("ElementProxy::memoryUsage()") {
+  JsonDocument doc;
+  REQUIRE(doc[0].memoryUsage() == 0);
+}
+
+TEST_CASE("MemberProxy::memoryUsage()") {
+  JsonDocument doc;
+  REQUIRE(doc["hello"].memoryUsage() == 0);
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/shallowCopy.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/shallowCopy.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ab5d6c4f685f98ee337dba86553f467ba838f4a5
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Deprecated/shallowCopy.cpp
@@ -0,0 +1,14 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("shallowCopy()") {
+  JsonDocument doc1, doc2;
+  doc1["b"] = "c";
+  doc2["a"].shallowCopy(doc1);
+
+  REQUIRE(doc2.as<std::string>() == "{\"a\":{\"b\":\"c\"}}");
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt
index 0464908c92e9c98e48757bd6bb455a4484967980..7ee4a46acdb314d54752ca3786cfbfde5b17b615 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 macro(build_should_fail target)
@@ -23,18 +23,12 @@ endmacro()
 add_executable(Issue978 Issue978.cpp)
 build_should_fail(Issue978)
 
-add_executable(Issue1189 Issue1189.cpp)
-build_should_fail(Issue1189)
-
 add_executable(read_long_long read_long_long.cpp)
 build_should_fail(read_long_long)
 
 add_executable(write_long_long write_long_long.cpp)
 build_should_fail(write_long_long)
 
-add_executable(delete_jsondocument delete_jsondocument.cpp)
-build_should_fail(delete_jsondocument)
-
 add_executable(variant_as_char variant_as_char.cpp)
 build_should_fail(variant_as_char)
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/Issue1189.cpp b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/Issue1189.cpp
deleted file mode 100644
index 40cb59f8f8875a16ec039d918698aef97f21d9f7..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/Issue1189.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-
-// a function should not be able to get a JsonDocument by value
-void f(JsonDocument) {}
-
-int main() {
-  DynamicJsonDocument doc(1024);
-  f(doc);
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp
index d85aaa0c8848fb85d4c4e3ced9f8ccaf5735cff8..2ebbf94e02282aded504d16f8ad59190ef2b6965 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -8,6 +8,6 @@ struct Stream {};
 
 int main() {
   Stream* stream = 0;
-  DynamicJsonDocument doc(1024);
+  JsonDocument doc;
   deserializeJson(doc, stream);
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp
index b9d897f776c3f21592f87cd6a17a4dba3c66c5fd..b5653763183ae2e820b7a41d37778c88f2cd9e5e 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,6 +7,6 @@
 // See issue #1498
 
 int main() {
-  DynamicJsonDocument doc(1024);
+  JsonDocument doc;
   doc["dummy"] = 'A';
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/delete_jsondocument.cpp b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/delete_jsondocument.cpp
deleted file mode 100644
index 174b80a5d16f03310f6bed798f791c38e8941eff..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/delete_jsondocument.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-
-struct Stream {};
-
-int main() {
-  JsonDocument* doc = new DynamicJsonDocument(42);
-  delete doc;
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp
index 6fe5992a1db2ae4985068bc128ddd14319c2b351..7577698a0924617db4db20fe8ec7ff49daa31885 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_USE_LONG_LONG 0
@@ -11,6 +11,6 @@
 
 ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(long long)
 int main() {
-  DynamicJsonDocument doc(1024);
+  JsonDocument doc;
   doc["dummy"].as<long long>();
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp
index 1a9e8342ffc02a1475e7e43186893bab34a7e3c5..37685a657b4a104785181ddfabf3f5306ac49266 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,6 +7,6 @@
 // See issue #1498
 
 int main() {
-  DynamicJsonDocument doc(1024);
+  JsonDocument doc;
   doc["dummy"].as<char>();
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp
index 6ff835651d6431167eb33220652e4c2584fd4f65..a8d14609296b0f8d54050ac98af5f2edafddaeec 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_USE_LONG_LONG 0
@@ -10,6 +10,6 @@
 #endif
 
 int main() {
-  DynamicJsonDocument doc(1024);
+  JsonDocument doc;
   doc["dummy"] = static_cast<long long>(42);
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Allocators.hpp b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Allocators.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..095d726a6fbf31f0aa0c7aa99da6039096245b5a
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Allocators.hpp
@@ -0,0 +1,286 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Memory/Allocator.hpp>
+#include <ArduinoJson/Memory/StringBuilder.hpp>
+#include <ArduinoJson/Memory/VariantPool.hpp>
+
+#include <sstream>
+
+namespace {
+
+struct FailingAllocator : ArduinoJson::Allocator {
+  static FailingAllocator* instance() {
+    static FailingAllocator allocator;
+    return &allocator;
+  }
+
+ private:
+  FailingAllocator() = default;
+  ~FailingAllocator() = default;
+
+  void* allocate(size_t) override {
+    return nullptr;
+  }
+
+  void deallocate(void*) override {}
+
+  void* reallocate(void*, size_t) override {
+    return nullptr;
+  }
+};
+
+class AllocatorLogEntry {
+ public:
+  AllocatorLogEntry(std::string s, size_t n = 1) : str_(s), count_(n) {}
+
+  const std::string& str() const {
+    return str_;
+  }
+
+  size_t count() const {
+    return count_;
+  }
+
+  AllocatorLogEntry operator*(size_t n) const {
+    return AllocatorLogEntry(str_, n);
+  }
+
+ private:
+  std::string str_;
+  size_t count_;
+};
+
+inline AllocatorLogEntry Allocate(size_t s) {
+  char buffer[32];
+  snprintf(buffer, sizeof(buffer), "allocate(%zu)", s);
+  return AllocatorLogEntry(buffer);
+}
+
+inline AllocatorLogEntry AllocateFail(size_t s) {
+  char buffer[32];
+  snprintf(buffer, sizeof(buffer), "allocate(%zu) -> nullptr", s);
+  return AllocatorLogEntry(buffer);
+}
+
+inline AllocatorLogEntry Reallocate(size_t s1, size_t s2) {
+  char buffer[32];
+  snprintf(buffer, sizeof(buffer), "reallocate(%zu, %zu)", s1, s2);
+  return AllocatorLogEntry(buffer);
+}
+
+inline AllocatorLogEntry ReallocateFail(size_t s1, size_t s2) {
+  char buffer[32];
+  snprintf(buffer, sizeof(buffer), "reallocate(%zu, %zu) -> nullptr", s1, s2);
+  return AllocatorLogEntry(buffer);
+}
+
+inline AllocatorLogEntry Deallocate(size_t s) {
+  char buffer[32];
+  snprintf(buffer, sizeof(buffer), "deallocate(%zu)", s);
+  return AllocatorLogEntry(buffer);
+}
+
+class AllocatorLog {
+ public:
+  AllocatorLog() = default;
+  AllocatorLog(std::initializer_list<AllocatorLogEntry> list) {
+    for (auto& entry : list)
+      append(entry);
+  }
+
+  void clear() {
+    log_.str("");
+  }
+
+  void append(const AllocatorLogEntry& entry) {
+    for (size_t i = 0; i < entry.count(); i++)
+      log_ << entry.str() << "\n";
+  }
+
+  std::string str() const {
+    auto s = log_.str();
+    if (s.empty())
+      return "(empty)";
+    s.pop_back();  // remove the trailing '\n'
+    return s;
+  }
+
+  bool operator==(const AllocatorLog& other) const {
+    return str() == other.str();
+  }
+
+  friend std::ostream& operator<<(std::ostream& os, const AllocatorLog& log) {
+    os << log.str();
+    return os;
+  }
+
+ private:
+  std::ostringstream log_;
+};
+
+class SpyingAllocator : public ArduinoJson::Allocator {
+ public:
+  SpyingAllocator(
+      Allocator* upstream = ArduinoJson::detail::DefaultAllocator::instance())
+      : upstream_(upstream) {}
+  virtual ~SpyingAllocator() {}
+
+  size_t allocatedBytes() const {
+    return allocatedBytes_;
+  }
+
+  void* allocate(size_t n) override {
+    auto block = reinterpret_cast<AllocatedBlock*>(
+        upstream_->allocate(sizeof(AllocatedBlock) + n - 1));
+    if (block) {
+      log_.append(Allocate(n));
+      allocatedBytes_ += n;
+      block->size = n;
+      return block->payload;
+    } else {
+      log_.append(AllocateFail(n));
+      return nullptr;
+    }
+  }
+
+  void deallocate(void* p) override {
+    auto block = AllocatedBlock::fromPayload(p);
+    allocatedBytes_ -= block->size;
+    log_.append(Deallocate(block ? block->size : 0));
+    upstream_->deallocate(block);
+  }
+
+  void* reallocate(void* p, size_t n) override {
+    auto block = AllocatedBlock::fromPayload(p);
+    auto oldSize = block ? block->size : 0;
+    block = reinterpret_cast<AllocatedBlock*>(
+        upstream_->reallocate(block, sizeof(AllocatedBlock) + n - 1));
+    if (block) {
+      log_.append(Reallocate(oldSize, n));
+      block->size = n;
+      allocatedBytes_ += n - oldSize;
+      return block->payload;
+    } else {
+      log_.append(ReallocateFail(oldSize, n));
+      return nullptr;
+    }
+  }
+
+  void clearLog() {
+    log_.clear();
+  }
+
+  const AllocatorLog& log() const {
+    return log_;
+  }
+
+ private:
+  struct AllocatedBlock {
+    size_t size;
+    char payload[1];
+
+    static AllocatedBlock* fromPayload(void* p) {
+      if (!p)
+        return nullptr;
+      return reinterpret_cast<AllocatedBlock*>(
+          // Cast to void* to silence "cast increases required alignment of
+          // target type [-Werror=cast-align]"
+          reinterpret_cast<void*>(reinterpret_cast<char*>(p) -
+                                  offsetof(AllocatedBlock, payload)));
+    }
+  };
+
+  AllocatorLog log_;
+  Allocator* upstream_;
+  size_t allocatedBytes_ = 0;
+};
+
+class KillswitchAllocator : public ArduinoJson::Allocator {
+ public:
+  KillswitchAllocator(
+      Allocator* upstream = ArduinoJson::detail::DefaultAllocator::instance())
+      : working_(true), upstream_(upstream) {}
+  virtual ~KillswitchAllocator() {}
+
+  void* allocate(size_t n) override {
+    return working_ ? upstream_->allocate(n) : 0;
+  }
+
+  void deallocate(void* p) override {
+    upstream_->deallocate(p);
+  }
+
+  void* reallocate(void* ptr, size_t n) override {
+    return working_ ? upstream_->reallocate(ptr, n) : 0;
+  }
+
+  // Turn the killswitch on, so all allocation fail
+  void on() {
+    working_ = false;
+  }
+
+ private:
+  bool working_;
+  Allocator* upstream_;
+};
+
+class TimebombAllocator : public ArduinoJson::Allocator {
+ public:
+  TimebombAllocator(
+      size_t initialCountdown,
+      Allocator* upstream = ArduinoJson::detail::DefaultAllocator::instance())
+      : countdown_(initialCountdown), upstream_(upstream) {}
+  virtual ~TimebombAllocator() {}
+
+  void* allocate(size_t n) override {
+    if (!countdown_)
+      return nullptr;
+    countdown_--;
+    return upstream_->allocate(n);
+  }
+
+  void deallocate(void* p) override {
+    upstream_->deallocate(p);
+  }
+
+  void* reallocate(void* ptr, size_t n) override {
+    if (!countdown_)
+      return nullptr;
+    countdown_--;
+    return upstream_->reallocate(ptr, n);
+  }
+
+  void setCountdown(size_t value) {
+    countdown_ = value;
+  }
+
+ private:
+  size_t countdown_ = 0;
+  Allocator* upstream_;
+};
+}  // namespace
+
+inline size_t sizeofPoolList(size_t n = ARDUINOJSON_INITIAL_POOL_COUNT) {
+  return sizeof(ArduinoJson::detail::VariantPool) * n;
+}
+
+inline size_t sizeofPool(
+    ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
+  return ArduinoJson::detail::VariantPool::slotsToBytes(n);
+}
+
+inline size_t sizeofStringBuffer(size_t iteration = 1) {
+  // returns 31, 63, 127, 255, etc.
+  auto capacity = ArduinoJson::detail::StringBuilder::initialCapacity;
+  for (size_t i = 1; i < iteration; i++)
+    capacity = capacity * 2 + 1;
+  return ArduinoJson::detail::sizeofString(capacity);
+}
+
+inline size_t sizeofString(const char* s) {
+  return ArduinoJson::detail::sizeofString(strlen(s));
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Arduino.h b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Arduino.h
index ef3640d9c0de264c0d8d285dfbb84e8c4f5a9cf1..4b8c27c09ac7c8391e87f711291e2e6f2f4b39a0 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Arduino.h
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Arduino.h
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/CustomReader.hpp b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/CustomReader.hpp
index 66525a25bf9838d921e4528b64a395bf17f5c390..66350a7cff541c02923857850a3e64c6000fc12c 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/CustomReader.hpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/CustomReader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Literals.hpp b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Literals.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..59164cfa1edfb01bbe09c8341d501c043a8ae369
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/Literals.hpp
@@ -0,0 +1,12 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <string>
+
+// the space before _s is required by GCC 4.8
+inline std::string operator"" _s(const char* str, size_t len) {
+  return std::string(str, len);
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Print.h b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Print.h
index d3c27ccfe6e02596377804c500bcd3b15908da39..c9ec1916b92c2759af5170d00a62227fd6d9418b 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Print.h
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Print.h
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Stream.h b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Stream.h
index 387940bb83f31f012de91eab93d6db0bb707b769..2cd4651f0d50847f6271055e767372044599f416 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Stream.h
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/Stream.h
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/String.h b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/String.h
index 64c763f943e3ae0f199eb378bb7dcd3e13451fd7..2e8fdfe8a518603775d46e064a0e66cd3dbaf777 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/String.h
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/api/String.h
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,11 +9,14 @@
 // Reproduces Arduino's String class
 class String {
  public:
-  String() : _maxCapacity(1024) {}
-  explicit String(const char* s) : _str(s), _maxCapacity(1024) {}
+  String() = default;
+  String(const char* s) {
+    if (s)
+      str_.assign(s);
+  }
 
   void limitCapacityTo(size_t maxCapacity) {
-    _maxCapacity = maxCapacity;
+    maxCapacity_ = maxCapacity;
   }
 
   unsigned char concat(const char* s) {
@@ -21,45 +24,48 @@ class String {
   }
 
   size_t length() const {
-    return _str.size();
+    return str_.size();
   }
 
   const char* c_str() const {
-    return _str.c_str();
+    return str_.c_str();
   }
 
   bool operator==(const char* s) const {
-    return _str == s;
+    return str_ == s;
   }
 
   String& operator=(const char* s) {
-    _str.assign(s);
+    if (s)
+      str_.assign(s);
+    else
+      str_.clear();
     return *this;
   }
 
   char operator[](unsigned int index) const {
-    if (index >= _str.size())
+    if (index >= str_.size())
       return 0;
-    return _str[index];
+    return str_[index];
   }
 
   friend std::ostream& operator<<(std::ostream& lhs, const ::String& rhs) {
-    lhs << rhs._str;
+    lhs << rhs.str_;
     return lhs;
   }
 
  protected:
   // This function is protected in most Arduino cores
   unsigned char concat(const char* s, size_t n) {
-    if (_str.size() + n > _maxCapacity)
+    if (str_.size() + n > maxCapacity_)
       return 0;
-    _str.append(s, n);
+    str_.append(s, n);
     return 1;
   }
 
  private:
-  std::string _str;
-  size_t _maxCapacity;
+  std::string str_;
+  size_t maxCapacity_ = 1024;
 };
 
 class StringSumHelper : public ::String {};
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/avr/pgmspace.h b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/avr/pgmspace.h
index 378065da3c8b71fc3e811d443d0858c7b85e019e..2cdd182740653e82664913249cc2cbe40ede1936 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Helpers/avr/pgmspace.h
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Helpers/avr/pgmspace.h
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt
index 9ee858d88ab9ac16a8187484f73334048f50d55d..adc0ebfc0b13d5f4a6dfe7e90077bf3980c1a223 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(IntegrationTests
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp
index 34c4d0d84cbf7d4db0f57ec0d0c0a69638f9422b..db649aa14ee9bae160be40983b90f687d24ed905 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("Gbathree") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   DeserializationError error = deserializeJson(
       doc,
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp
index dd85a085acf38af51d16b8d00e2f8f33d80a75ce..57e0b2cddcd523ca303ac41831b551a5e75bb74a 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -8,8 +8,8 @@
 // https://github.com/bblanchon/ArduinoJson/issues/772
 
 TEST_CASE("Issue772") {
-  DynamicJsonDocument doc1(4096);
-  DynamicJsonDocument doc2(4096);
+  JsonDocument doc1;
+  JsonDocument doc2;
   DeserializationError err;
   std::string data =
       "{\"state\":{\"reported\":{\"timestamp\":\"2018-07-02T09:40:12Z\","
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp
index e91ac0a8a224ac7f994291944f5108b79b8426ea..2d55b5409db3a1a56bb616d2aa4a70c3bd276052 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -53,12 +53,12 @@ TEST_CASE("OpenWeatherMap") {
       "]}";
   // clang-format on
 
-  StaticJsonDocument<512> filter;
+  JsonDocument filter;
   filter["list"][0]["dt"] = true;
   filter["list"][0]["main"]["temp"] = true;
   filter["list"][0]["weather"][0]["description"] = true;
 
-  DynamicJsonDocument doc(16384);
+  JsonDocument doc;
 
   REQUIRE(
       deserializeJson(doc, input_json, DeserializationOption::Filter(filter)) ==
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp
index ede311d00faa065fcb2bb387ffe63ec42e29ccdb..4fd5d2d33abba56ec7981593a48efcada305eaf8 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 void check(std::string originalJson) {
-  DynamicJsonDocument doc(16384);
+  JsonDocument doc;
 
   std::string prettyJson;
   deserializeJson(doc, originalJson);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt
index d97a2b16f5a8d4098ccf6f2c020f4a56263b0e0a..bb9719a373a890cad562c8fddd7238e853550cc4 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(JsonArrayTests
@@ -7,11 +7,9 @@ add_executable(JsonArrayTests
 	clear.cpp
 	compare.cpp
 	copyArray.cpp
-	createNested.cpp
 	equals.cpp
 	isNull.cpp
 	iterator.cpp
-	memoryUsage.cpp
 	nesting.cpp
 	remove.cpp
 	size.cpp
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/add.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/add.cpp
index 2708c7d4458e7b04972be70d1097d3bf79b1b113..1527670acb13f1b5eab5f2a9663cf1afad9baed6 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/add.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/add.cpp
@@ -1,12 +1,18 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
-TEST_CASE("JsonArray::add()") {
-  DynamicJsonDocument doc(4096);
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+
+TEST_CASE("JsonArray::add(T)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
   JsonArray array = doc.to<JsonArray>();
 
   SECTION("int") {
@@ -46,12 +52,12 @@ TEST_CASE("JsonArray::add()") {
 
     array.add(vla);
 
-    REQUIRE(std::string("world") == array[0]);
+    REQUIRE("world"_s == array[0]);
   }
 #endif
 
   SECTION("nested array") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonArray arr = doc2.to<JsonArray>();
 
     array.add(arr);
@@ -62,7 +68,7 @@ TEST_CASE("JsonArray::add()") {
   }
 
   SECTION("nested object") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonObject obj = doc2.to<JsonObject>();
 
     array.add(obj);
@@ -74,7 +80,7 @@ TEST_CASE("JsonArray::add()") {
 
   SECTION("array subscript") {
     const char* str = "hello";
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonArray arr = doc2.to<JsonArray>();
     arr.add(str);
 
@@ -85,7 +91,7 @@ TEST_CASE("JsonArray::add()") {
 
   SECTION("object subscript") {
     const char* str = "hello";
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonObject obj = doc2.to<JsonObject>();
     obj["x"] = str;
 
@@ -96,43 +102,130 @@ TEST_CASE("JsonArray::add()") {
 
   SECTION("should not duplicate const char*") {
     array.add("world");
-    const size_t expectedSize = JSON_ARRAY_SIZE(1);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                         });
   }
 
   SECTION("should duplicate char*") {
     array.add(const_cast<char*>("world"));
-    const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
   SECTION("should duplicate std::string") {
-    array.add(std::string("world"));
-    const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    array.add("world"_s);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
-  SECTION("should not duplicate serialized(const char*)") {
+  SECTION("should duplicate serialized(const char*)") {
     array.add(serialized("{}"));
-    const size_t expectedSize = JSON_ARRAY_SIZE(1);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("{}")),
+                         });
   }
 
   SECTION("should duplicate serialized(char*)") {
     array.add(serialized(const_cast<char*>("{}")));
-    const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(2);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("{}")),
+                         });
   }
 
   SECTION("should duplicate serialized(std::string)") {
-    array.add(serialized(std::string("{}")));
-    const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(2);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    array.add(serialized("{}"_s));
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("{}")),
+                         });
   }
 
   SECTION("should duplicate serialized(std::string)") {
-    array.add(serialized(std::string("\0XX", 3)));
-    const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(3);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    array.add(serialized("\0XX"_s));
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString(" XX")),
+                         });
+  }
+}
+
+TEST_CASE("JsonArray::add<T>()") {
+  JsonDocument doc;
+  JsonArray array = doc.to<JsonArray>();
+
+  SECTION("add<JsonArray>()") {
+    JsonArray nestedArray = array.add<JsonArray>();
+    nestedArray.add(1);
+    nestedArray.add(2);
+    REQUIRE(doc.as<std::string>() == "[[1,2]]");
+  }
+
+  SECTION("add<JsonObject>()") {
+    JsonObject nestedObject = array.add<JsonObject>();
+    nestedObject["a"] = 1;
+    nestedObject["b"] = 2;
+    REQUIRE(doc.as<std::string>() == "[{\"a\":1,\"b\":2}]");
+  }
+
+  SECTION("add<JsonVariant>()") {
+    JsonVariant nestedVariant = array.add<JsonVariant>();
+    nestedVariant.set(42);
+    REQUIRE(doc.as<std::string>() == "[42]");
+  }
+}
+
+TEST_CASE("JsonObject::add(JsonObject) ") {
+  JsonDocument doc1;
+  doc1["key1"_s] = "value1"_s;
+
+  TimebombAllocator allocator(10);
+  SpyingAllocator spy(&allocator);
+  JsonDocument doc2(&spy);
+  JsonArray array = doc2.to<JsonArray>();
+
+  SECTION("success") {
+    bool result = array.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == true);
+    REQUIRE(doc2.as<std::string>() == "[{\"key1\":\"value1\"}]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("key1")),
+                             Allocate(sizeofString("value1")),
+                         });
+  }
+
+  SECTION("partial failure") {  // issue #2081
+    allocator.setCountdown(2);
+
+    bool result = array.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == false);
+    REQUIRE(doc2.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("key1")),
+                             AllocateFail(sizeofString("value1")),
+                             Deallocate(sizeofString("key1")),
+                         });
+  }
+
+  SECTION("complete failure") {
+    allocator.setCountdown(0);
+
+    bool result = array.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == false);
+    REQUIRE(doc2.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofPool()),
+                         });
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/clear.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/clear.cpp
index 3a593c5ad108a7c79c3ce5d03d5030b1e159d531..8ac4548e7423e5323cee1d18b60bbf0a047278fd 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/clear.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/clear.cpp
@@ -1,10 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+
 TEST_CASE("JsonArray::clear()") {
   SECTION("No-op on null JsonArray") {
     JsonArray array;
@@ -14,7 +16,7 @@ TEST_CASE("JsonArray::clear()") {
   }
 
   SECTION("Removes all elements") {
-    StaticJsonDocument<64> doc;
+    JsonDocument doc;
     JsonArray array = doc.to<JsonArray>();
     array.add(1);
     array.add(2);
@@ -22,4 +24,23 @@ TEST_CASE("JsonArray::clear()") {
     REQUIRE(array.size() == 0);
     REQUIRE(array.isNull() == false);
   }
+
+  SECTION("Removed elements are recycled") {
+    SpyingAllocator spy;
+    JsonDocument doc(&spy);
+    JsonArray array = doc.to<JsonArray>();
+
+    // fill the pool entirely
+    for (int i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
+      array.add(i);
+
+    // clear and fill again
+    array.clear();
+    for (int i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
+      array.add(i);
+
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                         });
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/compare.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/compare.cpp
index 1e64e166c4e210b55eb89752d77e81a29560998a..b18bcdaff4280455aca9d1b75232a70e9e26c945 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/compare.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/compare.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("Compare JsonArray with JsonArray") {
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonArray array = doc.to<JsonArray>();
@@ -43,15 +43,15 @@ TEST_CASE("Compare JsonArray with JsonArray") {
   }
 
   SECTION("Compare with identical array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
 
     CHECK(array1 == array2);
     CHECK(array1 <= array2);
@@ -62,15 +62,15 @@ TEST_CASE("Compare JsonArray with JsonArray") {
   }
 
   SECTION("Compare with different array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello1");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello2");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
 
     CHECK(array1 != array2);
     CHECK_FALSE(array1 == array2);
@@ -82,7 +82,7 @@ TEST_CASE("Compare JsonArray with JsonArray") {
 }
 
 TEST_CASE("Compare JsonArray with JsonVariant") {
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
 
   SECTION("Compare with self") {
     JsonArray array = doc.to<JsonArray>();
@@ -107,15 +107,15 @@ TEST_CASE("Compare JsonArray with JsonVariant") {
   }
 
   SECTION("Compare with identical array") {
-    JsonArray array = doc.createNestedArray();
+    JsonArray array = doc.add<JsonArray>();
     array.add(1);
     array.add("hello");
-    array.createNestedObject();
+    array.add<JsonObject>();
 
-    JsonVariant variant = doc.createNestedArray();
+    JsonVariant variant = doc.add<JsonArray>();
     variant.add(1);
     variant.add("hello");
-    variant.createNestedObject();
+    variant.add<JsonObject>();
 
     CHECK(array == variant);
     CHECK(array <= variant);
@@ -133,15 +133,15 @@ TEST_CASE("Compare JsonArray with JsonVariant") {
   }
 
   SECTION("Compare with different array") {
-    JsonArray array = doc.createNestedArray();
+    JsonArray array = doc.add<JsonArray>();
     array.add(1);
     array.add("hello1");
-    array.createNestedObject();
+    array.add<JsonObject>();
 
-    JsonVariant variant = doc.createNestedArray();
+    JsonVariant variant = doc.add<JsonArray>();
     variant.add(1);
     variant.add("hello2");
-    variant.createNestedObject();
+    variant.add<JsonObject>();
 
     CHECK(array != variant);
     CHECK_FALSE(array == variant);
@@ -153,7 +153,7 @@ TEST_CASE("Compare JsonArray with JsonVariant") {
 }
 
 TEST_CASE("Compare JsonArray with JsonVariantConst") {
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonArray array = doc.to<JsonArray>();
@@ -199,15 +199,15 @@ TEST_CASE("Compare JsonArray with JsonVariantConst") {
   }
 
   SECTION("Compare with identical array") {
-    JsonArray array = doc.createNestedArray();
+    JsonArray array = doc.add<JsonArray>();
     array.add(1);
     array.add("hello");
-    array.createNestedObject();
+    array.add<JsonObject>();
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonVariantConst variant = array2;
 
     CHECK(array == variant);
@@ -226,15 +226,15 @@ TEST_CASE("Compare JsonArray with JsonVariantConst") {
   }
 
   SECTION("Compare with different array") {
-    JsonArray array = doc.createNestedArray();
+    JsonArray array = doc.add<JsonArray>();
     array.add(1);
     array.add("hello1");
-    array.createNestedObject();
+    array.add<JsonObject>();
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello2");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonVariantConst variant = array2;
 
     CHECK(array != variant);
@@ -247,7 +247,7 @@ TEST_CASE("Compare JsonArray with JsonVariantConst") {
 }
 
 TEST_CASE("Compare JsonArray with JsonArrayConst") {
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonArray array = doc.to<JsonArray>();
@@ -292,15 +292,15 @@ TEST_CASE("Compare JsonArray with JsonArrayConst") {
   }
 
   SECTION("Compare with identical array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonArrayConst carray2 = array2;
 
     CHECK(array1 == carray2);
@@ -319,15 +319,15 @@ TEST_CASE("Compare JsonArray with JsonArrayConst") {
   }
 
   SECTION("Compare with different array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello1");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello2");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonArrayConst carray2 = array2;
 
     CHECK(array1 != carray2);
@@ -347,7 +347,7 @@ TEST_CASE("Compare JsonArray with JsonArrayConst") {
 }
 
 TEST_CASE("Compare JsonArrayConst with JsonArrayConst") {
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonArray array = doc.to<JsonArray>();
@@ -387,16 +387,16 @@ TEST_CASE("Compare JsonArrayConst with JsonArrayConst") {
   }
 
   SECTION("Compare with identical array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
     JsonArrayConst carray1 = array1;
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonArrayConst carray2 = array2;
 
     CHECK(carray1 == carray2);
@@ -408,16 +408,16 @@ TEST_CASE("Compare JsonArrayConst with JsonArrayConst") {
   }
 
   SECTION("Compare with different array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello1");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
     JsonArrayConst carray1 = array1;
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello2");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonArrayConst carray2 = array2;
 
     CHECK(carray1 != carray2);
@@ -430,7 +430,7 @@ TEST_CASE("Compare JsonArrayConst with JsonArrayConst") {
 }
 
 TEST_CASE("Compare JsonArrayConst with JsonVariant") {
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
 
   SECTION("Compare with self") {
     JsonArray array = doc.to<JsonArray>();
@@ -455,16 +455,16 @@ TEST_CASE("Compare JsonArrayConst with JsonVariant") {
   }
 
   SECTION("Compare with identical array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
     JsonArrayConst carray1 = array1;
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonVariant variant2 = array2;
 
     CHECK(carray1 == variant2);
@@ -483,16 +483,16 @@ TEST_CASE("Compare JsonArrayConst with JsonVariant") {
   }
 
   SECTION("Compare with different array") {
-    JsonArray array1 = doc.createNestedArray();
+    JsonArray array1 = doc.add<JsonArray>();
     array1.add(1);
     array1.add("hello1");
-    array1.createNestedObject();
+    array1.add<JsonObject>();
     JsonArrayConst carray1 = array1;
 
-    JsonArray array2 = doc.createNestedArray();
+    JsonArray array2 = doc.add<JsonArray>();
     array2.add(1);
     array2.add("hello2");
-    array2.createNestedObject();
+    array2.add<JsonObject>();
     JsonVariant variant2 = array2;
 
     CHECK(carray1 != variant2);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/copyArray.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/copyArray.cpp
index 7973c375c90354b2cf3185e6a65670937e1af90e..a82a6f83d65bf9880f187dfbf85de22524cf7a58 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/copyArray.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/copyArray.cpp
@@ -1,13 +1,16 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
 TEST_CASE("copyArray()") {
   SECTION("int[] -> JsonArray") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray array = doc.to<JsonArray>();
     char json[32];
     int source[] = {1, 2, 3};
@@ -16,11 +19,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(array, json);
-    CHECK(std::string("[1,2,3]") == json);
+    CHECK("[1,2,3]"_s == json);
   }
 
   SECTION("std::string[] -> JsonArray") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray array = doc.to<JsonArray>();
     char json[32];
     std::string source[] = {"a", "b", "c"};
@@ -29,11 +32,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(array, json);
-    CHECK(std::string("[\"a\",\"b\",\"c\"]") == json);
+    CHECK("[\"a\",\"b\",\"c\"]"_s == json);
   }
 
   SECTION("const char*[] -> JsonArray") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray array = doc.to<JsonArray>();
     char json[32];
     const char* source[] = {"a", "b", "c"};
@@ -42,11 +45,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(array, json);
-    CHECK(std::string("[\"a\",\"b\",\"c\"]") == json);
+    CHECK("[\"a\",\"b\",\"c\"]"_s == json);
   }
 
   SECTION("const char[][] -> JsonArray") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray array = doc.to<JsonArray>();
     char json[32];
     char source[][2] = {"a", "b", "c"};
@@ -55,11 +58,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(array, json);
-    CHECK(std::string("[\"a\",\"b\",\"c\"]") == json);
+    CHECK("[\"a\",\"b\",\"c\"]"_s == json);
   }
 
   SECTION("const char[][] -> JsonDocument") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[32];
     char source[][2] = {"a", "b", "c"};
 
@@ -67,11 +70,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(doc, json);
-    CHECK(std::string("[\"a\",\"b\",\"c\"]") == json);
+    CHECK("[\"a\",\"b\",\"c\"]"_s == json);
   }
 
   SECTION("const char[][] -> MemberProxy") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[32];
     char source[][2] = {"a", "b", "c"};
 
@@ -79,11 +82,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(doc, json);
-    CHECK(std::string("{\"data\":[\"a\",\"b\",\"c\"]}") == json);
+    CHECK("{\"data\":[\"a\",\"b\",\"c\"]}"_s == json);
   }
 
   SECTION("int[] -> JsonDocument") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[32];
     int source[] = {1, 2, 3};
 
@@ -91,11 +94,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(doc, json);
-    CHECK(std::string("[1,2,3]") == json);
+    CHECK("[1,2,3]"_s == json);
   }
 
   SECTION("int[] -> MemberProxy") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[32];
     int source[] = {1, 2, 3};
 
@@ -103,25 +106,20 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(doc, json);
-    CHECK(std::string("{\"data\":[1,2,3]}") == json);
+    CHECK("{\"data\":[1,2,3]}"_s == json);
   }
 
   SECTION("int[] -> JsonArray, but not enough memory") {
-    const size_t SIZE = JSON_ARRAY_SIZE(2);
-    StaticJsonDocument<SIZE> doc;
+    JsonDocument doc(FailingAllocator::instance());
     JsonArray array = doc.to<JsonArray>();
-    char json[32];
     int source[] = {1, 2, 3};
 
     bool ok = copyArray(source, array);
     REQUIRE_FALSE(ok);
-
-    serializeJson(array, json);
-    CHECK(std::string("[1,2]") == json);
   }
 
   SECTION("int[][] -> JsonArray") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray array = doc.to<JsonArray>();
     char json[32];
     int source[][3] = {{1, 2, 3}, {4, 5, 6}};
@@ -130,11 +128,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(array, json);
-    CHECK(std::string("[[1,2,3],[4,5,6]]") == json);
+    CHECK("[[1,2,3],[4,5,6]]"_s == json);
   }
 
   SECTION("int[][] -> MemberProxy") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[32];
     int source[][3] = {{1, 2, 3}, {4, 5, 6}};
 
@@ -142,11 +140,11 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(doc, json);
-    CHECK(std::string("{\"data\":[[1,2,3],[4,5,6]]}") == json);
+    CHECK("{\"data\":[[1,2,3],[4,5,6]]}"_s == json);
   }
 
   SECTION("int[][] -> JsonDocument") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[32];
     int source[][3] = {{1, 2, 3}, {4, 5, 6}};
 
@@ -154,29 +152,20 @@ TEST_CASE("copyArray()") {
     CHECK(ok);
 
     serializeJson(doc, json);
-    CHECK(std::string("[[1,2,3],[4,5,6]]") == json);
+    CHECK("[[1,2,3],[4,5,6]]"_s == json);
   }
 
   SECTION("int[][] -> JsonArray, but not enough memory") {
-    const size_t SIZE =
-        JSON_ARRAY_SIZE(2) + JSON_ARRAY_SIZE(3) + JSON_ARRAY_SIZE(2);
-    StaticJsonDocument<SIZE> doc;
+    JsonDocument doc(FailingAllocator::instance());
     JsonArray array = doc.to<JsonArray>();
-    char json[32] = "";
     int source[][3] = {{1, 2, 3}, {4, 5, 6}};
 
-    CAPTURE(SIZE);
-
     bool ok = copyArray(source, array);
-    CAPTURE(doc.memoryUsage());
-    CHECK_FALSE(ok);
-
-    serializeJson(array, json);
-    CHECK(std::string("[[1,2,3],[4,5]]") == json);
+    REQUIRE(ok == false);
   }
 
   SECTION("JsonArray -> int[], with more space than needed") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[1,2,3]";
     DeserializationError err = deserializeJson(doc, json);
     CHECK(err == DeserializationError::Ok);
@@ -193,7 +182,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("JsonArray -> int[], without enough space") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[1,2,3]";
     DeserializationError err = deserializeJson(doc, json);
     CHECK(err == DeserializationError::Ok);
@@ -208,7 +197,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("JsonArray -> std::string[]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[\"a\",\"b\",\"c\"]";
     DeserializationError err = deserializeJson(doc, json);
     CHECK(err == DeserializationError::Ok);
@@ -225,7 +214,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("JsonArray -> char[N][]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[\"a12345\",\"b123456\",\"c1234567\"]";
     DeserializationError err = deserializeJson(doc, json);
     CHECK(err == DeserializationError::Ok);
@@ -235,14 +224,14 @@ TEST_CASE("copyArray()") {
     size_t result = copyArray(array, destination);
 
     CHECK(3 == result);
-    CHECK(std::string("a12345") == destination[0]);
-    CHECK(std::string("b123456") == destination[1]);
-    CHECK(std::string("c123456") == destination[2]);  // truncated
+    CHECK("a12345"_s == destination[0]);
+    CHECK("b123456"_s == destination[1]);
+    CHECK("c123456"_s == destination[2]);  // truncated
     CHECK(std::string("") == destination[3]);
   }
 
   SECTION("JsonDocument -> int[]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[1,2,3]";
     DeserializationError err = deserializeJson(doc, json);
     CHECK(err == DeserializationError::Ok);
@@ -258,7 +247,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("MemberProxy -> int[]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "{\"data\":[1,2,3]}";
     DeserializationError err = deserializeJson(doc, json);
     CHECK(err == DeserializationError::Ok);
@@ -274,7 +263,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("ElementProxy -> int[]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[[1,2,3]]";
     DeserializationError err = deserializeJson(doc, json);
     CHECK(err == DeserializationError::Ok);
@@ -290,7 +279,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("JsonArray -> int[][]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[[1,2],[3],[4]]";
 
     DeserializationError err = deserializeJson(doc, json);
@@ -309,7 +298,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("JsonDocument -> int[][]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "[[1,2],[3],[4]]";
 
     DeserializationError err = deserializeJson(doc, json);
@@ -327,7 +316,7 @@ TEST_CASE("copyArray()") {
   }
 
   SECTION("MemberProxy -> int[][]") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     char json[] = "{\"data\":[[1,2],[3],[4]]}";
 
     DeserializationError err = deserializeJson(doc, json);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/createNested.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/createNested.cpp
deleted file mode 100644
index 4801dbe0ea08f8060a0b48fe81d08a2176538d0a..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/createNested.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonArray basics") {
-  DynamicJsonDocument doc(4096);
-  JsonArray array = doc.to<JsonArray>();
-
-  SECTION("CreateNestedArray") {
-    JsonArray arr = array.createNestedArray();
-    REQUIRE(arr == array[0].as<JsonArray>());
-  }
-
-  SECTION("CreateNestedObject") {
-    JsonObject obj = array.createNestedObject();
-    REQUIRE(obj == array[0].as<JsonObject>());
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/equals.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/equals.cpp
index 4998d388f26dd738a47df05b08f27145dca7e644..4f5af465a684e7e3d2470b0d02909328b300cfa0 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/equals.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/equals.cpp
@@ -1,25 +1,22 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonArray::operator==()") {
-  DynamicJsonDocument doc1(4096);
+  JsonDocument doc1;
   JsonArray array1 = doc1.to<JsonArray>();
-  JsonArrayConst array1c = array1;
 
-  DynamicJsonDocument doc2(4096);
+  JsonDocument doc2;
   JsonArray array2 = doc2.to<JsonArray>();
-  JsonArrayConst array2c = array2;
 
   SECTION("should return false when arrays differ") {
     array1.add("coucou");
     array2.add(1);
 
     REQUIRE_FALSE(array1 == array2);
-    REQUIRE_FALSE(array1c == array2c);
   }
 
   SECTION("should return false when LHS has more elements") {
@@ -28,7 +25,6 @@ TEST_CASE("JsonArray::operator==()") {
     array2.add(1);
 
     REQUIRE_FALSE(array1 == array2);
-    REQUIRE_FALSE(array1c == array2c);
   }
 
   SECTION("should return false when RHS has more elements") {
@@ -37,7 +33,6 @@ TEST_CASE("JsonArray::operator==()") {
     array2.add(2);
 
     REQUIRE_FALSE(array1 == array2);
-    REQUIRE_FALSE(array1c == array2c);
   }
 
   SECTION("should return true when arrays equal") {
@@ -45,7 +40,6 @@ TEST_CASE("JsonArray::operator==()") {
     array2.add("coucou");
 
     REQUIRE(array1 == array2);
-    REQUIRE(array1c == array2c);
   }
 
   SECTION("should return false when RHS is null") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/isNull.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/isNull.cpp
index 0da13be8d13dd554870872c30268b4fd32d680fa..dc22f4ba80a94071d6036763f21c4db00ca3a310 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/isNull.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/isNull.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -12,25 +12,12 @@ TEST_CASE("JsonArray::isNull()") {
   }
 
   SECTION("returns false") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray arr = doc.to<JsonArray>();
     REQUIRE(arr.isNull() == false);
   }
 }
 
-TEST_CASE("JsonArrayConst::isNull()") {
-  SECTION("returns true") {
-    JsonArrayConst arr;
-    REQUIRE(arr.isNull() == true);
-  }
-
-  SECTION("returns false") {
-    DynamicJsonDocument doc(4096);
-    JsonArrayConst arr = doc.to<JsonArray>();
-    REQUIRE(arr.isNull() == false);
-  }
-}
-
 TEST_CASE("JsonArray::operator bool()") {
   SECTION("returns false") {
     JsonArray arr;
@@ -38,21 +25,8 @@ TEST_CASE("JsonArray::operator bool()") {
   }
 
   SECTION("returns true") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray arr = doc.to<JsonArray>();
     REQUIRE(static_cast<bool>(arr) == true);
   }
 }
-
-TEST_CASE("JsonArrayConst::operator bool()") {
-  SECTION("returns false") {
-    JsonArrayConst arr;
-    REQUIRE(static_cast<bool>(arr) == false);
-  }
-
-  SECTION("returns true") {
-    DynamicJsonDocument doc(4096);
-    JsonArrayConst arr = doc.to<JsonArray>();
-    REQUIRE(static_cast<bool>(arr) == true);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/iterator.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/iterator.cpp
index 50eb27631b4325ffe7d5c20bb5c2d50ea267fc11..815897526e008cfb4d8ae50ac8f3cf02a85eea52 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/iterator.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/iterator.cpp
@@ -1,35 +1,29 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
-template <typename TArray>
-static void run_iterator_test() {
-  StaticJsonDocument<JSON_ARRAY_SIZE(2)> doc;
-  JsonArray tmp = doc.to<JsonArray>();
-  tmp.add(12);
-  tmp.add(34);
-
-  TArray array = tmp;
-  typename TArray::iterator it = array.begin();
-  typename TArray::iterator end = array.end();
-
-  REQUIRE(end != it);
-  REQUIRE(12 == it->template as<int>());
-  REQUIRE(12 == static_cast<int>(*it));
-  ++it;
-  REQUIRE(end != it);
-  REQUIRE(34 == it->template as<int>());
-  REQUIRE(34 == static_cast<int>(*it));
-  ++it;
-  REQUIRE(end == it);
-}
-
 TEST_CASE("JsonArray::begin()/end()") {
   SECTION("Non null JsonArray") {
-    run_iterator_test<JsonArray>();
+    JsonDocument doc;
+    JsonArray array = doc.to<JsonArray>();
+    array.add(12);
+    array.add(34);
+
+    auto it = array.begin();
+    auto end = array.end();
+
+    REQUIRE(end != it);
+    REQUIRE(12 == it->as<int>());
+    REQUIRE(12 == static_cast<int>(*it));
+    ++it;
+    REQUIRE(end != it);
+    REQUIRE(34 == it->as<int>());
+    REQUIRE(34 == static_cast<int>(*it));
+    ++it;
+    REQUIRE(end == it);
   }
 
   SECTION("Null JsonArray") {
@@ -38,15 +32,3 @@ TEST_CASE("JsonArray::begin()/end()") {
     REQUIRE(array.begin() == array.end());
   }
 }
-
-TEST_CASE("JsonArrayConst::begin()/end()") {
-  SECTION("Non null JsonArrayConst") {
-    run_iterator_test<JsonArrayConst>();
-  }
-
-  SECTION("Null JsonArrayConst") {
-    JsonArrayConst array;
-
-    REQUIRE(array.begin() == array.end());
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/memoryUsage.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/memoryUsage.cpp
deleted file mode 100644
index 9f4ea09b39f28a3b0ce94844a0d6bca3d2e49960..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/memoryUsage.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonArray::memoryUsage()") {
-  DynamicJsonDocument doc(4096);
-  JsonArray arr = doc.to<JsonArray>();
-
-  SECTION("return 0 if uninitialized") {
-    JsonArray unitialized;
-    REQUIRE(unitialized.memoryUsage() == 0);
-  }
-
-  SECTION("JSON_ARRAY_SIZE(0) if empty") {
-    REQUIRE(arr.memoryUsage() == JSON_ARRAY_SIZE(0));
-  }
-
-  SECTION("JSON_ARRAY_SIZE(1) after add") {
-    arr.add("hello");
-    REQUIRE(arr.memoryUsage() == JSON_ARRAY_SIZE(1));
-  }
-
-  SECTION("includes the size of the string") {
-    arr.add(std::string("hello"));
-    REQUIRE(arr.memoryUsage() == JSON_ARRAY_SIZE(1) + 6);
-  }
-
-  SECTION("includes the size of the nested array") {
-    JsonArray nested = arr.createNestedArray();
-    nested.add(42);
-    REQUIRE(arr.memoryUsage() == 2 * JSON_ARRAY_SIZE(1));
-  }
-
-  SECTION("includes the size of the nested arrect") {
-    JsonObject nested = arr.createNestedObject();
-    nested["hello"] = "world";
-    REQUIRE(arr.memoryUsage() == JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(1));
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/nesting.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/nesting.cpp
index fc1be8c6a839eb5da7e91e1a2867ff2c473a523a..a49b01c5ab404ace50ad949c3455a04b9dd10e97 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/nesting.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/nesting.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonArray::nesting()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonArray arr = doc.to<JsonArray>();
 
   SECTION("return 0 if uninitialized") {
@@ -24,12 +24,12 @@ TEST_CASE("JsonArray::nesting()") {
   }
 
   SECTION("returns 2 with nested array") {
-    arr.createNestedArray();
+    arr.add<JsonArray>();
     REQUIRE(arr.nesting() == 2);
   }
 
   SECTION("returns 2 with nested object") {
-    arr.createNestedObject();
+    arr.add<JsonObject>();
     REQUIRE(arr.nesting() == 2);
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/remove.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/remove.cpp
index f75efda31eb8ee6932efb97d11d0373e27adf5a0..9cbd90bb9c0e8b650ab6fbacbc71eb1183c36b83 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/remove.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/remove.cpp
@@ -1,12 +1,14 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+
 TEST_CASE("JsonArray::remove()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
   array.add(1);
   array.add(2);
@@ -86,4 +88,33 @@ TEST_CASE("JsonArray::remove()") {
     JsonArray unboundArray;
     unboundArray.remove(unboundArray.begin());
   }
+
+  SECTION("use JsonVariant as index") {
+    array.remove(array[3]);  // no effect with null variant
+    array.remove(array[0]);  // remove element at index 1
+
+    REQUIRE(2 == array.size());
+    REQUIRE(array[0] == 1);
+    REQUIRE(array[1] == 3);
+  }
+}
+
+TEST_CASE("Removed elements are recycled") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  JsonArray array = doc.to<JsonArray>();
+
+  // fill the pool entirely
+  for (int i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
+    array.add(i);
+
+  // free one slot in the pool
+  array.remove(0);
+
+  // add one element; it should use the free slot
+  array.add(42);
+
+  REQUIRE(spy.log() == AllocatorLog{
+                           Allocate(sizeofPool()),  // only one pool
+                       });
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/size.cpp
index 6edd4db86d5b547d5cff70c2c1d2748fda77d570..599b37cbf330cb0ec97cd0744d50cb84bc8ba00b 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/size.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/size.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonArray::size()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
 
   SECTION("returns 0 is empty") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/std_string.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/std_string.cpp
index 7d3ec9f452241e3ded8108a3ded59bcf8c906e1a..d6cae2e62256085903ecc82cf3bdf234e1b1a2f7 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/std_string.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/std_string.cpp
@@ -1,10 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 static void eraseString(std::string& str) {
   char* p = const_cast<char*>(str.c_str());
   while (*p)
@@ -12,14 +14,14 @@ static void eraseString(std::string& str) {
 }
 
 TEST_CASE("std::string") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
 
   SECTION("add()") {
     std::string value("hello");
     array.add(value);
     eraseString(value);
-    REQUIRE(std::string("hello") == array[0]);
+    REQUIRE("hello"_s == array[0]);
   }
 
   SECTION("operator[]") {
@@ -27,6 +29,6 @@ TEST_CASE("std::string") {
     array.add("hello");
     array[0] = value;
     eraseString(value);
-    REQUIRE(std::string("world") == array[0]);
+    REQUIRE("world"_s == array[0]);
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/subscript.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/subscript.cpp
index 3079d2f98d7565867c6df572c2a1bb759d981b78..b0332f8562163296944e1dd243fb513febde371c 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/subscript.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/subscript.cpp
@@ -1,13 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <stdint.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
 TEST_CASE("JsonArray::operator[]") {
-  DynamicJsonDocument doc(4096);
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
   JsonArray array = doc.to<JsonArray>();
 
   SECTION("Pad with null") {
@@ -65,7 +69,7 @@ TEST_CASE("JsonArray::operator[]") {
   }
 
   SECTION("nested array") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonArray arr2 = doc2.to<JsonArray>();
 
     array[0] = arr2;
@@ -76,7 +80,7 @@ TEST_CASE("JsonArray::operator[]") {
   }
 
   SECTION("nested object") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonObject obj = doc2.to<JsonObject>();
 
     array[0] = obj;
@@ -87,7 +91,7 @@ TEST_CASE("JsonArray::operator[]") {
   }
 
   SECTION("array subscript") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonArray arr2 = doc2.to<JsonArray>();
     const char* str = "hello";
 
@@ -100,7 +104,7 @@ TEST_CASE("JsonArray::operator[]") {
 
   SECTION("object subscript") {
     const char* str = "hello";
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonObject obj = doc2.to<JsonObject>();
 
     obj["x"] = str;
@@ -112,20 +116,25 @@ TEST_CASE("JsonArray::operator[]") {
 
   SECTION("should not duplicate const char*") {
     array[0] = "world";
-    const size_t expectedSize = JSON_ARRAY_SIZE(1);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                         });
   }
 
   SECTION("should duplicate char*") {
     array[0] = const_cast<char*>("world");
-    const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
   SECTION("should duplicate std::string") {
-    array[0] = std::string("world");
-    const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    array[0] = "world"_s;
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
   SECTION("array[0].to<JsonObject>()") {
@@ -142,7 +151,7 @@ TEST_CASE("JsonArray::operator[]") {
     array.add("hello");
     array[0].set(vla);
 
-    REQUIRE(std::string("world") == array[0]);
+    REQUIRE("world"_s == array[0]);
   }
 
   SECTION("operator=(VLA)") {
@@ -153,22 +162,16 @@ TEST_CASE("JsonArray::operator[]") {
     array.add("hello");
     array[0] = vla;
 
-    REQUIRE(std::string("world") == array[0]);
+    REQUIRE("world"_s == array[0]);
   }
 #endif
-}
-
-TEST_CASE("JsonArrayConst::operator[]") {
-  DynamicJsonDocument doc(4096);
-  JsonArray array = doc.to<JsonArray>();
-  array.add(0);
 
-  SECTION("int") {
-    array[0] = 123;
-    JsonArrayConst carr = array;
+  SECTION("Use a JsonVariant as index") {
+    array[0] = 1;
+    array[1] = 2;
+    array[2] = 3;
 
-    REQUIRE(123 == carr[0].as<int>());
-    REQUIRE(true == carr[0].is<int>());
-    REQUIRE(false == carr[0].is<bool>());
+    REQUIRE(array[array[1]] == 3);
+    REQUIRE(array[array[3]] == nullptr);
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/unbound.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/unbound.cpp
index 0e21310b161c8fb202a5e63bee972a964a408379..21d4ebfd15e5f436907575398ac47bcff019484e 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/unbound.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArray/unbound.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -19,14 +19,6 @@ TEST_CASE("Unbound JsonArray") {
     REQUIRE(0 == array.size());
   }
 
-  SECTION("CreateNestedArrayFails") {
-    REQUIRE(array.createNestedArray().isNull());
-  }
-
-  SECTION("CreateNestedObjectFails") {
-    REQUIRE(array.createNestedObject().isNull());
-  }
-
   SECTION("PrintToWritesBrackets") {
     char buffer[32];
     serializeJson(array, buffer, sizeof(buffer));
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8608a89519c27515b2cd19fbde07f2da5b9a90d0
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/CMakeLists.txt
@@ -0,0 +1,19 @@
+# ArduinoJson - https://arduinojson.org
+# Copyright © 2014-2024, Benoit BLANCHON
+# MIT License
+
+add_executable(JsonArrayConstTests
+	equals.cpp
+	isNull.cpp
+	iterator.cpp
+	nesting.cpp
+	size.cpp
+	subscript.cpp
+)
+
+add_test(JsonArrayConst JsonArrayConstTests)
+
+set_tests_properties(JsonArrayConst
+	PROPERTIES
+		LABELS "Catch"
+)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/equals.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/equals.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba60d385f19c376e79e4b8cbd3462eb9ec141c6e
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/equals.cpp
@@ -0,0 +1,63 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArrayConst::operator==()") {
+  JsonDocument doc1;
+  JsonArrayConst array1 = doc1.to<JsonArray>();
+
+  JsonDocument doc2;
+  JsonArrayConst array2 = doc2.to<JsonArray>();
+
+  SECTION("should return false when arrays differ") {
+    doc1.add("coucou");
+    doc2.add(1);
+
+    REQUIRE_FALSE(array1 == array2);
+  }
+
+  SECTION("should return false when LHS has more elements") {
+    doc1.add(1);
+    doc1.add(2);
+    doc2.add(1);
+
+    REQUIRE_FALSE(array1 == array2);
+  }
+
+  SECTION("should return false when RHS has more elements") {
+    doc1.add(1);
+    doc2.add(1);
+    doc2.add(2);
+
+    REQUIRE_FALSE(array1 == array2);
+  }
+
+  SECTION("should return true when arrays equal") {
+    doc1.add("coucou");
+    doc2.add("coucou");
+
+    REQUIRE(array1 == array2);
+  }
+
+  SECTION("should return false when RHS is null") {
+    JsonArrayConst null;
+
+    REQUIRE_FALSE(array1 == null);
+  }
+
+  SECTION("should return false when LHS is null") {
+    JsonArrayConst null;
+
+    REQUIRE_FALSE(null == array1);
+  }
+
+  SECTION("should return true when both are null") {
+    JsonArrayConst null1;
+    JsonArrayConst null2;
+
+    REQUIRE(null1 == null2);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/isNull.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/isNull.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..26b64ae5ad1d3b88366dd2764100add48e1426ee
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/isNull.cpp
@@ -0,0 +1,32 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArrayConst::isNull()") {
+  SECTION("returns true") {
+    JsonArrayConst arr;
+    REQUIRE(arr.isNull() == true);
+  }
+
+  SECTION("returns false") {
+    JsonDocument doc;
+    JsonArrayConst arr = doc.to<JsonArray>();
+    REQUIRE(arr.isNull() == false);
+  }
+}
+
+TEST_CASE("JsonArrayConst::operator bool()") {
+  SECTION("returns false") {
+    JsonArrayConst arr;
+    REQUIRE(static_cast<bool>(arr) == false);
+  }
+
+  SECTION("returns true") {
+    JsonDocument doc;
+    JsonArrayConst arr = doc.to<JsonArray>();
+    REQUIRE(static_cast<bool>(arr) == true);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/iterator.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/iterator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5eeb56aa42a18b4f53867623941341b9621d5f03
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/iterator.cpp
@@ -0,0 +1,34 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArrayConst::begin()/end()") {
+  SECTION("Non null JsonArrayConst") {
+    JsonDocument doc;
+    JsonArrayConst array = doc.to<JsonArray>();
+    doc.add(12);
+    doc.add(34);
+
+    auto it = array.begin();
+    auto end = array.end();
+
+    REQUIRE(end != it);
+    REQUIRE(12 == it->as<int>());
+    REQUIRE(12 == static_cast<int>(*it));
+    ++it;
+    REQUIRE(end != it);
+    REQUIRE(34 == it->as<int>());
+    REQUIRE(34 == static_cast<int>(*it));
+    ++it;
+    REQUIRE(end == it);
+  }
+
+  SECTION("Null JsonArrayConst") {
+    JsonArrayConst array;
+
+    REQUIRE(array.begin() == array.end());
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/nesting.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/nesting.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..860754ac22ae30c1cba6496c0e0c1397189dd293
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/nesting.cpp
@@ -0,0 +1,35 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArrayConst::nesting()") {
+  JsonDocument doc;
+  JsonArrayConst arr = doc.to<JsonArray>();
+
+  SECTION("return 0 if unbound") {
+    JsonArrayConst unbound;
+    REQUIRE(unbound.nesting() == 0);
+  }
+
+  SECTION("returns 1 for empty array") {
+    REQUIRE(arr.nesting() == 1);
+  }
+
+  SECTION("returns 1 for flat array") {
+    doc.add("hello");
+    REQUIRE(arr.nesting() == 1);
+  }
+
+  SECTION("returns 2 with nested array") {
+    doc.add<JsonArray>();
+    REQUIRE(arr.nesting() == 2);
+  }
+
+  SECTION("returns 2 with nested object") {
+    doc.add<JsonObject>();
+    REQUIRE(arr.nesting() == 2);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/size.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a59551ae34dd7c5710055b405af76f1ac27e389b
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/size.cpp
@@ -0,0 +1,27 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArrayConst::size()") {
+  JsonDocument doc;
+  JsonArrayConst array = doc.to<JsonArray>();
+
+  SECTION("returns 0 if unbound") {
+    JsonArrayConst unbound;
+    REQUIRE(0U == unbound.size());
+  }
+
+  SECTION("returns 0 is empty") {
+    REQUIRE(0U == array.size());
+  }
+
+  SECTION("return number of elements") {
+    doc.add("hello");
+    doc.add("world");
+
+    REQUIRE(2U == array.size());
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/subscript.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/subscript.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28512b20d9afc78687aba4374eb50df3ff8f07a7
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonArrayConst/subscript.cpp
@@ -0,0 +1,27 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <stdint.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonArrayConst::operator[]") {
+  JsonDocument doc;
+  JsonArrayConst arr = doc.to<JsonArray>();
+  doc.add(1);
+  doc.add(2);
+  doc.add(3);
+
+  SECTION("int") {
+    REQUIRE(1 == arr[0].as<int>());
+    REQUIRE(2 == arr[1].as<int>());
+    REQUIRE(3 == arr[2].as<int>());
+    REQUIRE(0 == arr[3].as<int>());
+  }
+
+  SECTION("JsonVariant") {
+    REQUIRE(2 == arr[arr[0]].as<int>());
+    REQUIRE(0 == arr[arr[3]].as<int>());
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt
index f60ad4161a786f7aedd436c30c3396620a53518d..a503f456b8e549e5832cb435e0be8b98e1cce773 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt
@@ -1,20 +1,18 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(JsonDeserializerTests
 	array.cpp
-	array_static.cpp
 	DeserializationError.cpp
+	destination_types.cpp
+	errors.cpp
 	filter.cpp
-	incomplete_input.cpp
 	input_types.cpp
-	invalid_input.cpp
 	misc.cpp
 	nestingLimit.cpp
 	number.cpp
 	object.cpp
-	object_static.cpp
 	string.cpp
 )
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp
index fd1b1885411a97bfa4f8733cf63cd113f1379536..17c8c4024a472c0d5ec133f35f8470bae5dbf08a 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/array.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/array.cpp
index b30269c19b503c1e585734baadccdca03aec132d..71a567d161b0400a292e2b9ee40cb4afa717fb41 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/array.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/array.cpp
@@ -1,12 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+
 TEST_CASE("deserialize JSON array") {
-  DynamicJsonDocument doc(4096);
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
 
   SECTION("An empty array") {
     DeserializationError err = deserializeJson(doc, "[]");
@@ -244,10 +249,71 @@ TEST_CASE("deserialize JSON array") {
 
   SECTION("Should clear the JsonArray") {
     deserializeJson(doc, "[1,2,3,4]");
+    spy.clearLog();
+
     deserializeJson(doc, "[]");
-    JsonArray arr = doc.as<JsonArray>();
 
+    JsonArray arr = doc.as<JsonArray>();
     REQUIRE(arr.size() == 0);
-    REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofArray(4)),
+                         });
+  }
+}
+
+TEST_CASE("deserialize JSON array under memory constraints") {
+  TimebombAllocator timebomb(100);
+  SpyingAllocator spy(&timebomb);
+  JsonDocument doc(&spy);
+
+  SECTION("empty array requires no allocation") {
+    timebomb.setCountdown(0);
+    char input[] = "[]";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::Ok);
+  }
+
+  SECTION("allocation of pool list fails") {
+    timebomb.setCountdown(0);
+    char input[] = "[1]";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::NoMemory);
+    REQUIRE(doc.as<std::string>() == "[]");
+  }
+
+  SECTION("allocation of pool fails") {
+    timebomb.setCountdown(0);
+    char input[] = "[1]";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::NoMemory);
+    REQUIRE(doc.as<std::string>() == "[]");
+  }
+
+  SECTION("allocation of string fails in array") {
+    timebomb.setCountdown(1);
+    char input[] = "[0,\"hi!\"]";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::NoMemory);
+    REQUIRE(doc.as<std::string>() == "[0,null]");
+  }
+
+  SECTION("don't store space characters") {
+    deserializeJson(doc, "  [ \"1234567\" ] ");
+
+    REQUIRE(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()),
+                Allocate(sizeofStringBuffer()),
+                Reallocate(sizeofStringBuffer(), sizeofString("1234567")),
+                Reallocate(sizeofPool(), sizeofArray(1)),
+            });
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/array_static.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/array_static.cpp
deleted file mode 100644
index 2d1f538d7db4e9d04aead0c676ad9cca2d74729e..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/array_static.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("deserialize JSON array with a StaticJsonDocument") {
-  SECTION("BufferOfTheRightSizeForEmptyArray") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(0)> doc;
-    char input[] = "[]";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::Ok);
-  }
-
-  SECTION("TooSmallBufferForArrayWithOneValue") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(0)> doc;
-    char input[] = "[1]";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::NoMemory);
-  }
-
-  SECTION("BufferOfTheRightSizeForArrayWithOneValue") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(1)> doc;
-    char input[] = "[1]";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::Ok);
-  }
-
-  SECTION("TooSmallBufferForArrayWithNestedObject") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(0) + JSON_OBJECT_SIZE(0)> doc;
-    char input[] = "[{}]";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::NoMemory);
-  }
-
-  SECTION("BufferOfTheRightSizeForArrayWithNestedObject") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(0)> doc;
-    char input[] = "[{}]";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::Ok);
-  }
-
-  SECTION("CopyStringNotSpaces") {
-    StaticJsonDocument<100> doc;
-
-    deserializeJson(doc, "  [ \"1234567\" ] ");
-
-    REQUIRE(JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(7) == doc.memoryUsage());
-    // note: we use a string of 8 bytes to be sure that the StaticMemoryPool
-    // will not insert bytes to enforce alignement
-  }
-
-  SECTION("Should clear the JsonArray") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(4)> doc;
-    char input[] = "[1,2,3,4]";
-
-    deserializeJson(doc, input);
-    deserializeJson(doc, "[]");
-
-    JsonArray arr = doc.as<JsonArray>();
-    REQUIRE(arr.size() == 0);
-    REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
-  }
-
-  SECTION("Array") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(2)> doc;
-    char input[] = "[1,2]";
-
-    DeserializationError err = deserializeJson(doc, input);
-    JsonArray arr = doc.as<JsonArray>();
-
-    REQUIRE(err == DeserializationError::Ok);
-    REQUIRE(doc.is<JsonArray>());
-    REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2));
-    REQUIRE(arr[0] == 1);
-    REQUIRE(arr[1] == 2);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/destination_types.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/destination_types.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc5c0123c221c753553ee1ac526b95728d8cd6a2
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/destination_types.cpp
@@ -0,0 +1,109 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+
+#include <catch.hpp>
+#include <string>
+
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+using ArduinoJson::detail::sizeofObject;
+
+TEST_CASE("deserializeJson(JsonDocument&)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  doc.add("hello"_s);
+  spy.clearLog();
+
+  auto err = deserializeJson(doc, "[42]");
+
+  REQUIRE(err == DeserializationError::Ok);
+  REQUIRE(doc.as<std::string>() == "[42]");
+  REQUIRE(spy.log() == AllocatorLog{
+                           Deallocate(sizeofPool()),
+                           Deallocate(sizeofString("hello")),
+                           Allocate(sizeofPool()),
+                           Reallocate(sizeofPool(), sizeofArray(1)),
+                       });
+}
+
+TEST_CASE("deserializeJson(JsonVariant)") {
+  SECTION("variant is bound") {
+    SpyingAllocator spy;
+    JsonDocument doc(&spy);
+    doc.add("hello"_s);
+    spy.clearLog();
+
+    JsonVariant variant = doc[0];
+
+    auto err = deserializeJson(variant, "[42]");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "[[42]]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("hello")),
+                         });
+  }
+
+  SECTION("variant is unbound") {
+    JsonVariant variant;
+
+    auto err = deserializeJson(variant, "[42]");
+
+    REQUIRE(err == DeserializationError::NoMemory);
+  }
+}
+
+TEST_CASE("deserializeJson(ElementProxy)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  doc.add("hello"_s);
+  spy.clearLog();
+
+  SECTION("element already exists") {
+    auto err = deserializeJson(doc[0], "[42]");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "[[42]]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("hello")),
+                         });
+  }
+
+  SECTION("element must be created") {
+    auto err = deserializeJson(doc[1], "[42]");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "[\"hello\",[42]]");
+    REQUIRE(spy.log() == AllocatorLog{});
+  }
+}
+
+TEST_CASE("deserializeJson(MemberProxy)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  doc["hello"_s] = "world"_s;
+  spy.clearLog();
+
+  SECTION("member already exists") {
+    auto err = deserializeJson(doc["hello"], "[42]");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "{\"hello\":[42]}");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("member must be created exists") {
+    auto err = deserializeJson(doc["value"], "[42]");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}");
+    REQUIRE(spy.log() == AllocatorLog{});
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/errors.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/errors.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..35b4de2ff63a5a721e272027832892612aefc5d6
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/errors.cpp
@@ -0,0 +1,120 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#define ARDUINOJSON_DECODE_UNICODE 1
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("deserializeJson() returns IncompleteInput") {
+  const char* testCases[] = {
+      // strings
+      "\"\\",
+      "\"hello",
+      "\'hello",
+      // unicode
+      "'\\u",
+      "'\\u00",
+      "'\\u000",
+      // false
+      "f",
+      "fa",
+      "fal",
+      "fals",
+      // true
+      "t",
+      "tr",
+      "tru",
+      // null
+      "n",
+      "nu",
+      "nul",
+      // object
+      "{",
+      "{a",
+      "{a:",
+      "{a:1",
+      "{a:1,",
+      "{a:1,b",
+      "{a:1,b:",
+  };
+
+  for (auto input : testCases) {
+    SECTION(input) {
+      JsonDocument doc;
+      REQUIRE(deserializeJson(doc, input) ==
+              DeserializationError::IncompleteInput);
+    }
+  }
+}
+
+TEST_CASE("deserializeJson() returns InvalidInput") {
+  const char* testCases[] = {
+      // unicode
+      "'\\u'", "'\\u000g'", "'\\u000'", "'\\u000G'", "'\\u000/'", "\\x1234",
+      // numbers
+      "6a9", "1,", "2]", "3}",
+      // constants
+      "nulL", "tru3", "fals3",
+      // garbage
+      "%*$£¤"};
+
+  for (auto input : testCases) {
+    SECTION(input) {
+      JsonDocument doc;
+      REQUIRE(deserializeJson(doc, input) ==
+              DeserializationError::InvalidInput);
+    }
+  }
+}
+
+TEST_CASE("deserializeJson() oversees some edge cases") {
+  const char* testCases[] = {
+      "'\\ud83d'",         // leading surrogate without a trailing surrogate
+      "'\\udda4'",         // trailing surrogate without a leading surrogate
+      "'\\ud83d\\ud83d'",  // two leading surrogates
+  };
+
+  for (auto input : testCases) {
+    SECTION(input) {
+      JsonDocument doc;
+      REQUIRE(deserializeJson(doc, input) == DeserializationError::Ok);
+    }
+  }
+}
+
+TEST_CASE("deserializeJson() returns EmptyInput") {
+  JsonDocument doc;
+
+  SECTION("null") {
+    auto err = deserializeJson(doc, static_cast<const char*>(0));
+    REQUIRE(err == DeserializationError::EmptyInput);
+  }
+
+  SECTION("Empty string") {
+    auto err = deserializeJson(doc, "");
+    REQUIRE(err == DeserializationError::EmptyInput);
+  }
+
+  SECTION("Only spaces") {
+    auto err = deserializeJson(doc, "  \t\n\r");
+    REQUIRE(err == DeserializationError::EmptyInput);
+  }
+}
+
+TEST_CASE("deserializeJson() returns NoMemory if string length overflows") {
+  JsonDocument doc;
+  auto maxLength = ArduinoJson::detail::StringNode::maxLength;
+
+  SECTION("max length should succeed") {
+    auto err = deserializeJson(doc, "\"" + std::string(maxLength, 'a') + "\"");
+
+    REQUIRE(err == DeserializationError::Ok);
+  }
+
+  SECTION("one above max length should fail") {
+    auto err =
+        deserializeJson(doc, "\"" + std::string(maxLength + 1, 'a') + "\"");
+    REQUIRE(err == DeserializationError::NoMemory);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp
index f1c6e2e4c020206e616e824d93667286ca0a2e38..4dc2fc6dc4c2e89f135f29a7119a564329ff7672 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_ENABLE_COMMENTS 1
@@ -9,8 +9,15 @@
 #include <sstream>
 #include <string>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+using ArduinoJson::detail::sizeofObject;
+
 TEST_CASE("Filtering") {
   struct TestCase {
+    const char* description;
     const char* input;
     const char* filter;
     uint8_t nestingLimit;
@@ -19,707 +26,699 @@ TEST_CASE("Filtering") {
     size_t memoryUsage;
   };
 
-  // clang-format off
   TestCase testCases[] = {
-    {
-      "{\"hello\":\"world\"}",   // 1. input
-      "null",                    // 2. filter
-      10,                        // 3. nestingLimit
-      DeserializationError::Ok,  // 4. error
-      "null",                    // 5. output
-      0                          // 6. memoryUsage
-    },
-    {
-      "{\"hello\":\"world\"}",
-      "false",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      "{\"abcdefg\":\"hijklmn\"}",
-      "true",
-      10,
-      DeserializationError::Ok,
-      "{\"abcdefg\":\"hijklmn\"}",
-      JSON_OBJECT_SIZE(1) + 16
-    },
-    {
-      "{\"hello\":\"world\"}",
-      "{}",
-      10,
-      DeserializationError::Ok,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // Input in an object, but filter wants an array
-      "{\"hello\":\"world\"}",
-      "[]",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // Member is a string, but filter wants an array
-      "{\"example\":\"example\"}",
-      "{\"example\":[true]}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":null}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // Member is a number, but filter wants an array
-      "{\"example\":42}",
-      "{\"example\":[true]}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":null}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // Input is an array, but filter wants an object
-      "[\"hello\",\"world\"]",
-      "{}",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // Input is a bool, but filter wants an object
-      "true",
-      "{}",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // Input is a string, but filter wants an object
-      "\"hello\"",
-      "{}",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // skip an integer
-      "{\"an_integer\":666,example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // skip a float
-      "{\"a_float\":12.34e-6,example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // skip false
-      "{\"a_bool\":false,example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // skip true
-      "{\"a_bool\":true,example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // skip null
-      "{\"a_bool\":null,example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip a double-quoted string
-      "{\"a_double_quoted_string\":\"hello\",example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip a single-quoted string
-      "{\"a_single_quoted_string\":'hello',example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip an empty array
-      "{\"an_empty_array\":[],example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip an empty array with spaces in it
-      "{\"an_empty_array\":[\t],example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip an array
-      "{\"an_array\":[1,2,3],example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip an array with spaces in it
-      "{\"an_array\": [ 1 , 2 , 3 ] ,example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip an empty object
-      "{\"an_empty_object\":{},example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip an empty object with spaces in it
-      "{\"an_empty_object\":{    },example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // can skip an object
-      "{\"an_object\":{a:1,'b':2,\"c\":3},example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // skip an object with spaces in it
-      "{\"an_object\" : { a : 1 , 'b' : 2 , \"c\" : 3 } ,example:42}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":42}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      "{\"an_integer\": 0,\"example\":{\"type\":\"int\",\"outcome\":42}}",
-      "{\"example\":{\"outcome\":true}}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":{\"outcome\":42}}",
-      2 * JSON_OBJECT_SIZE(1) + 16
-    },
-    {
-      // wildcard
-      "{\"example\":{\"type\":\"int\",\"outcome\":42}}",
-      "{\"*\":{\"outcome\":true}}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":{\"outcome\":42}}",
-      2 * JSON_OBJECT_SIZE(1) + 16
-    },
-    {
-      // exclusion filter (issue #1628)
-      "{\"example\":1,\"ignored\":2}",
-      "{\"*\":true,\"ignored\":false}",
-      10,
-      DeserializationError::Ok,
-      "{\"example\":1}",
-      JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      // only the first element of array counts
-      "[1,2,3]",
-      "[true, false]",
-      10,
-      DeserializationError::Ok,
-      "[1,2,3]",
-      JSON_ARRAY_SIZE(3)
-    },
-    {
-      // only the first element of array counts
-      "[1,2,3]",
-      "[false, true]",
-      10,
-      DeserializationError::Ok,
-      "[]",
-      JSON_ARRAY_SIZE(0)
-    },
-    {
-      // filter members of object in array
-      "[{\"example\":1,\"ignore\":2},{\"example\":3,\"ignore\":4}]",
-      "[{\"example\":true}]",
-      10,
-      DeserializationError::Ok,
-      "[{\"example\":1},{\"example\":3}]",
-      JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8
-    },
-    {
-      "[',2,3]",
-      "[false,true]",
-      10,
-      DeserializationError::IncompleteInput,
-      "[]",
-      JSON_ARRAY_SIZE(0)
-    },
-    {
-      "[\",2,3]",
-      "[false,true]",
-      10,
-      DeserializationError::IncompleteInput,
-      "[]",
-      JSON_ARRAY_SIZE(0)
-    },
-    {
-      // detect errors in skipped value
-      "[!,2,\\]",
-      "[false]",
-      10,
-      DeserializationError::InvalidInput,
-      "[]",
-      JSON_ARRAY_SIZE(0)
-    },
-    {
-      // detect incomplete string event if it's skipped
-      "\"ABC",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // detect incomplete string event if it's skipped
-      "'ABC",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // handle escaped quotes
-      "'A\\'BC'",
-      "false",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // handle escaped quotes
-      "\"A\\\"BC\"",
-      "false",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // detect incomplete string in presence of escaped quotes
-      "'A\\'BC",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // detect incomplete string in presence of escaped quotes
-      "\"A\\\"BC",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // skip empty array
-      "[]",
-      "false",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // skip empty array with spaces
-      " [ ] ",
-      "false",
-      10,
-      DeserializationError::Ok,
-      "null",
-      0
-    },
-    {
-      // bubble up element error even if array is skipped
-      "[1,'2,3]",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // bubble up member error even if object is skipped
-      "{'hello':'worl}",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // bubble up colon error even if object is skipped
-      "{'hello','world'}",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // bubble up key error even if object is skipped
-      "{'hello:1}",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // detect invalid value in skipped object
-      "{'hello':!}",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // ignore invalid value in skipped object
-      "{'hello':\\}",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // check nesting limit even for ignored objects
-      "{}",
-      "false",
-      0,
-      DeserializationError::TooDeep,
-      "null",
-      0
-    },
-    {
-      // check nesting limit even for ignored objects
-      "{'hello':{}}",
-      "false",
-      1,
-      DeserializationError::TooDeep,
-      "null",
-      0
-    },
-    {
-      // check nesting limit even for ignored values in objects
-      "{'hello':{}}",
-      "{}",
-      1,
-      DeserializationError::TooDeep,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // check nesting limit even for ignored arrays
-      "[]",
-      "false",
-      0,
-      DeserializationError::TooDeep,
-      "null",
-      0
-    },
-    {
-      // check nesting limit even for ignored arrays
-      "[[]]",
-      "false",
-      1,
-      DeserializationError::TooDeep,
-      "null",
-      0
-    },
-    {
-      // check nesting limit even for ignored values in arrays
-      "[[]]",
-      "[]",
-      1,
-      DeserializationError::TooDeep,
-      "[]",
-      JSON_ARRAY_SIZE(0)
-    },
-    {
-      // supports back-slash at the end of skipped string
-      "\"hell\\",
-      "false",
-      1,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // invalid comment at after an element in a skipped array
-      "[1/]",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // incomplete comment at after an element in a skipped array
-      "[1/*]",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // missing comma in a skipped array
-      "[1 2]",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // invalid comment at the beginning of array
-      "[/1]",
-      "[false]",
-      10,
-      DeserializationError::InvalidInput,
-      "[]",
-      JSON_ARRAY_SIZE(0)
-    },
-    {
-      // incomplete comment at the begining of an array
-      "[/*]",
-      "[false]",
-      10,
-      DeserializationError::IncompleteInput,
-      "[]",
-      JSON_ARRAY_SIZE(0)
-    },
-    {
-      // invalid comment before key
-      "{/1:2}",
-      "{}",
-      10,
-      DeserializationError::InvalidInput,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // incomplete comment before key
-      "{/*:2}",
-      "{}",
-      10,
-      DeserializationError::IncompleteInput,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // invalid comment after key
-      "{\"example\"/1:2}",
-      "{}",
-      10,
-      DeserializationError::InvalidInput,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // incomplete comment after key
-      "{\"example\"/*:2}",
-      "{}",
-      10,
-      DeserializationError::IncompleteInput,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // invalid comment after colon
-      "{\"example\":/12}",
-      "{}",
-      10,
-      DeserializationError::InvalidInput,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // incomplete comment after colon
-      "{\"example\":/*2}",
-      "{}",
-      10,
-      DeserializationError::IncompleteInput,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // comment next to an integer
-      "{\"ignore\":1//,\"example\":2\n}",
-      "{\"example\":true}",
-      10,
-      DeserializationError::Ok,
-      "{}",
-      JSON_OBJECT_SIZE(0)
-    },
-    {
-      // invalid comment after opening brace of a skipped object
-      "{/1:2}",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // incomplete after opening brace of a skipped object
-      "{/*:2}",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // invalid comment after key of a skipped object
-      "{\"example\"/:2}",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // incomplete comment after key of a skipped object
-      "{\"example\"/*:2}",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-    {
-      // invalid comment after value in a skipped object
-      "{\"example\":2/}",
-      "false",
-      10,
-      DeserializationError::InvalidInput,
-      "null",
-      0
-    },
-    {
-      // incomplete comment after value of a skipped object
-      "{\"example\":2/*}",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-     {
-      // incomplete comment after comma in skipped object
-      "{\"example\":2,/*}",
-      "false",
-      10,
-      DeserializationError::IncompleteInput,
-      "null",
-      0
-    },
-  };  // clang-format on
-
-  for (size_t i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) {
-    CAPTURE(i);
-
-    DynamicJsonDocument filter(256);
-    DynamicJsonDocument doc(256);
-    TestCase& tc = testCases[i];
-
-    CAPTURE(tc.filter);
-    REQUIRE(deserializeJson(filter, tc.filter) == DeserializationError::Ok);
-
-    CAPTURE(tc.input);
-    CAPTURE(tc.nestingLimit);
-    CHECK(deserializeJson(doc, tc.input, DeserializationOption::Filter(filter),
-                          DeserializationOption::NestingLimit(
-                              tc.nestingLimit)) == tc.error);
-
-    CHECK(doc.as<std::string>() == tc.output);
-    CHECK(doc.memoryUsage() == tc.memoryUsage);
-  }
-}
+      {
+          "Input is object, filter is null",  // description
+          "{\"hello\":\"world\"}",            // input
+          "null",                             // filter
+          10,                                 // nestingLimit
+          DeserializationError::Ok,           // error
+          "null",                             // output
+          0,                                  // memoryUsage
+      },
+      {
+          "Input is object, filter is false",
+          "{\"hello\":\"world\"}",
+          "false",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Input is object, filter is true",
+          "{\"abcdefg\":\"hijklmn\"}",
+          "true",
+          10,
+          DeserializationError::Ok,
+          "{\"abcdefg\":\"hijklmn\"}",
+          sizeofObject(1) + sizeofString("abcdefg") + sizeofString("hijklmn"),
+      },
+      {
+          "Input is object, filter is empty object",
+          "{\"hello\":\"world\"}",
+          "{}",
+          10,
+          DeserializationError::Ok,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Input in an object, but filter wants an array",
+          "{\"hello\":\"world\"}",
+          "[]",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Member is a string, but filter wants an array",
+          "{\"example\":\"example\"}",
+          "{\"example\":[true]}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":null}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Member is a number, but filter wants an array",
+          "{\"example\":42}",
+          "{\"example\":[true]}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":null}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Input is an array, but filter wants an object",
+          "[\"hello\",\"world\"]",
+          "{}",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Input is a bool, but filter wants an object",
+          "true",
+          "{}",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Input is a string, but filter wants an object",
+          "\"hello\"",
+          "{}",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Skip an integer",
+          "{\"an_integer\":666,example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip a float",
+          "{\"a_float\":12.34e-6,example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip false",
+          "{\"a_bool\":false,example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip true",
+          "{\"a_bool\":true,example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip null",
+          "{\"a_bool\":null,example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip a double-quoted string",
+          "{\"a_double_quoted_string\":\"hello\",example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip a single-quoted string",
+          "{\"a_single_quoted_string\":'hello',example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip an empty array",
+          "{\"an_empty_array\":[],example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip an empty array with spaces in it",
+          "{\"an_empty_array\":[\t],example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip an array",
+          "{\"an_array\":[1,2,3],example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip an array with spaces in it",
+          "{\"an_array\": [ 1 , 2 , 3 ] ,example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip an empty nested object",
+          "{\"an_empty_object\":{},example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip an empty nested object with spaces in it",
+          "{\"an_empty_object\":{    },example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip a nested object",
+          "{\"an_object\":{a:1,'b':2,\"c\":3},example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip an object with spaces in it",
+          "{\"an_object\" : { a : 1 , 'b' : 2 , \"c\" : 3 } ,example:42}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":42}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Skip a string in a nested object",
+          "{\"an_integer\": 0,\"example\":{\"type\":\"int\",\"outcome\":42}}",
+          "{\"example\":{\"outcome\":true}}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":{\"outcome\":42}}",
+          2 * sizeofObject(1) + 2 * sizeofString("example"),
+      },
+      {
+          "wildcard",
+          "{\"example\":{\"type\":\"int\",\"outcome\":42}}",
+          "{\"*\":{\"outcome\":true}}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":{\"outcome\":42}}",
+          2 * sizeofObject(1) + 2 * sizeofString("example"),
+      },
+      {
+          "exclusion filter (issue #1628)",
+          "{\"example\":1,\"ignored\":2}",
+          "{\"*\":true,\"ignored\":false}",
+          10,
+          DeserializationError::Ok,
+          "{\"example\":1}",
+          sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "only the first element of array counts",
+          "[1,2,3]",
+          "[true, false]",
+          10,
+          DeserializationError::Ok,
+          "[1,2,3]",
+          sizeofArray(3),
+      },
+      {
+          "only the first element of array counts",
+          "[1,2,3]",
+          "[false, true]",
+          10,
+          DeserializationError::Ok,
+          "[]",
+          sizeofArray(0),
+      },
+      {
+          "filter members of object in array",
+          "[{\"example\":1,\"ignore\":2},{\"example\":3,\"ignore\":4}]",
+          "[{\"example\":true}]",
+          10,
+          DeserializationError::Ok,
+          "[{\"example\":1},{\"example\":3}]",
+          sizeofArray(2) + 2 * sizeofObject(1) + sizeofString("example"),
+      },
+      {
+          "Unclosed single quote in skipped element",
+          "[',2,3]",
+          "[false,true]",
+          10,
+          DeserializationError::IncompleteInput,
+          "[]",
+          sizeofArray(0),
+      },
+      {
+          "Unclosed double quote in skipped element",
+          "[\",2,3]",
+          "[false,true]",
+          10,
+          DeserializationError::IncompleteInput,
+          "[]",
+          sizeofArray(0),
+      },
+      {
+          "Detect errors in skipped value",
+          "[!,2,\\]",
+          "[false]",
+          10,
+          DeserializationError::InvalidInput,
+          "[]",
+          sizeofArray(0),
+      },
+      {
+          "Detect incomplete string event if it's skipped",
+          "\"ABC",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Detect incomplete string event if it's skipped",
+          "'ABC",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Handle escaped quotes",
+          "'A\\'BC'",
+          "false",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Handle escaped quotes",
+          "\"A\\\"BC\"",
+          "false",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Detect incomplete string in presence of escaped quotes",
+          "'A\\'BC",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Detect incomplete string in presence of escaped quotes",
+          "\"A\\\"BC",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "skip empty array",
+          "[]",
+          "false",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Skip empty array with spaces",
+          " [ ] ",
+          "false",
+          10,
+          DeserializationError::Ok,
+          "null",
+          0,
+      },
+      {
+          "Bubble up element error even if array is skipped",
+          "[1,'2,3]",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Bubble up member error even if object is skipped",
+          "{'hello':'worl}",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Bubble up colon error even if object is skipped",
+          "{'hello','world'}",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Bubble up key error even if object is skipped",
+          "{'hello:1}",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Detect invalid value in skipped object",
+          "{'hello':!}",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Ignore invalid value in skipped object",
+          "{'hello':\\}",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Check nesting limit even for ignored objects",
+          "{}",
+          "false",
+          0,
+          DeserializationError::TooDeep,
+          "null",
+          0,
+      },
+      {
+          "Check nesting limit even for ignored objects",
+          "{'hello':{}}",
+          "false",
+          1,
+          DeserializationError::TooDeep,
+          "null",
+          0,
+      },
+      {
+          "Check nesting limit even for ignored values in objects",
+          "{'hello':{}}",
+          "{}",
+          1,
+          DeserializationError::TooDeep,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Check nesting limit even for ignored arrays",
+          "[]",
+          "false",
+          0,
+          DeserializationError::TooDeep,
+          "null",
+          0,
+      },
+      {
+          "Check nesting limit even for ignored arrays",
+          "[[]]",
+          "false",
+          1,
+          DeserializationError::TooDeep,
+          "null",
+          0,
+      },
+      {
+          "Check nesting limit even for ignored values in arrays",
+          "[[]]",
+          "[]",
+          1,
+          DeserializationError::TooDeep,
+          "[]",
+          sizeofArray(0),
+      },
+      {
+          "Supports back-slash at the end of skipped string",
+          "\"hell\\",
+          "false",
+          1,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Invalid comment at after an element in a skipped array",
+          "[1/]",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Incomplete comment at after an element in a skipped array",
+          "[1/*]",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Missing comma in a skipped array",
+          "[1 2]",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Invalid comment at the beginning of array",
+          "[/1]",
+          "[false]",
+          10,
+          DeserializationError::InvalidInput,
+          "[]",
+          sizeofArray(0),
+      },
+      {
+          "Incomplete comment at the begining of an array",
+          "[/*]",
+          "[false]",
+          10,
+          DeserializationError::IncompleteInput,
+          "[]",
+          sizeofArray(0),
+      },
+      {
+          "Invalid comment before key",
+          "{/1:2}",
+          "{}",
+          10,
+          DeserializationError::InvalidInput,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Incomplete comment before key",
+          "{/*:2}",
+          "{}",
+          10,
+          DeserializationError::IncompleteInput,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Invalid comment after key",
+          "{\"example\"/1:2}",
+          "{}",
+          10,
+          DeserializationError::InvalidInput,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Incomplete comment after key",
+          "{\"example\"/*:2}",
+          "{}",
+          10,
+          DeserializationError::IncompleteInput,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Invalid comment after colon",
+          "{\"example\":/12}",
+          "{}",
+          10,
+          DeserializationError::InvalidInput,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Incomplete comment after colon",
+          "{\"example\":/*2}",
+          "{}",
+          10,
+          DeserializationError::IncompleteInput,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Comment next to an integer",
+          "{\"ignore\":1//,\"example\":2\n}",
+          "{\"example\":true}",
+          10,
+          DeserializationError::Ok,
+          "{}",
+          sizeofObject(0),
+      },
+      {
+          "Invalid comment after opening brace of a skipped object",
+          "{/1:2}",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Incomplete after opening brace of a skipped object",
+          "{/*:2}",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Invalid comment after key of a skipped object",
+          "{\"example\"/:2}",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Incomplete comment after key of a skipped object",
+          "{\"example\"/*:2}",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Invalid comment after value in a skipped object",
+          "{\"example\":2/}",
+          "false",
+          10,
+          DeserializationError::InvalidInput,
+          "null",
+          0,
+      },
+      {
+          "Incomplete comment after value of a skipped object",
+          "{\"example\":2/*}",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+      {
+          "Incomplete comment after comma in skipped object",
+          "{\"example\":2,/*}",
+          "false",
+          10,
+          DeserializationError::IncompleteInput,
+          "null",
+          0,
+      },
+  };
+
+  for (auto& tc : testCases) {
+    SECTION(tc.description) {
+      SpyingAllocator spy;
+      JsonDocument filter;
+      JsonDocument doc(&spy);
 
-TEST_CASE("Zero-copy mode") {  // issue #1697
-  char input[] = "{\"include\":42,\"exclude\":666}";
+      REQUIRE(deserializeJson(filter, tc.filter) == DeserializationError::Ok);
 
-  StaticJsonDocument<256> filter;
-  filter["include"] = true;
+      CHECK(deserializeJson(
+                doc, tc.input, DeserializationOption::Filter(filter),
+                DeserializationOption::NestingLimit(tc.nestingLimit)) ==
+            tc.error);
 
-  StaticJsonDocument<256> doc;
-  DeserializationError err =
-      deserializeJson(doc, input, DeserializationOption::Filter(filter));
+      CHECK(doc.as<std::string>() == tc.output);
 
-  REQUIRE(err == DeserializationError::Ok);
-  CHECK(doc.as<std::string>() == "{\"include\":42}");
+      doc.shrinkToFit();
+      CHECK(spy.allocatedBytes() == tc.memoryUsage);
+    }
+  }
 }
 
 TEST_CASE("Overloads") {
-  StaticJsonDocument<256> doc;
-  StaticJsonDocument<256> filter;
+  JsonDocument doc;
+  JsonDocument filter;
 
   using namespace DeserializationOption;
 
@@ -734,7 +733,7 @@ TEST_CASE("Overloads") {
   }
 
   SECTION("const std::string&, Filter") {
-    deserializeJson(doc, std::string("{}"), Filter(filter));
+    deserializeJson(doc, "{}"_s, Filter(filter));
   }
 
   SECTION("std::istream&, Filter") {
@@ -762,7 +761,7 @@ TEST_CASE("Overloads") {
   }
 
   SECTION("const std::string&, Filter, NestingLimit") {
-    deserializeJson(doc, std::string("{}"), Filter(filter), NestingLimit(5));
+    deserializeJson(doc, "{}"_s, Filter(filter), NestingLimit(5));
   }
 
   SECTION("std::istream&, Filter, NestingLimit") {
@@ -790,7 +789,7 @@ TEST_CASE("Overloads") {
   }
 
   SECTION("const std::string&, NestingLimit, Filter") {
-    deserializeJson(doc, std::string("{}"), NestingLimit(5), Filter(filter));
+    deserializeJson(doc, "{}"_s, NestingLimit(5), Filter(filter));
   }
 
   SECTION("std::istream&, NestingLimit, Filter") {
@@ -807,3 +806,17 @@ TEST_CASE("Overloads") {
   }
 #endif
 }
+
+TEST_CASE("shrink filter") {
+  JsonDocument doc;
+  SpyingAllocator spy;
+  JsonDocument filter(&spy);
+  filter["a"] = true;
+  spy.clearLog();
+
+  deserializeJson(doc, "{}", DeserializationOption::Filter(filter));
+
+  REQUIRE(spy.log() == AllocatorLog{
+                           Reallocate(sizeofPool(), sizeofObject(1)),
+                       });
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/incomplete_input.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/incomplete_input.cpp
deleted file mode 100644
index a34aede64d5a69fab09bb1ac33b2274eb5c5cf87..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/incomplete_input.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#define ARDUINOJSON_DECODE_UNICODE 1
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("Truncated JSON input") {
-  const char* testCases[] = {"\"hello", "\'hello", "'\\u", "'\\u00", "'\\u000",
-                             // false
-                             "f", "fa", "fal", "fals",
-                             // true
-                             "t", "tr", "tru",
-                             // null
-                             "n", "nu", "nul",
-                             // object
-                             "{", "{a", "{a:", "{a:1", "{a:1,", "{a:1,"};
-  const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
-
-  DynamicJsonDocument doc(4096);
-
-  for (size_t i = 0; i < testCount; i++) {
-    const char* input = testCases[i];
-    CAPTURE(input);
-    REQUIRE(deserializeJson(doc, input) ==
-            DeserializationError::IncompleteInput);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp
index 788319f4623dbbd3862090b8855881e166155f59..a8a9e569650c942bd525a10daa5a0c3c61976434 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,25 +7,35 @@
 #include <catch.hpp>
 #include <sstream>
 
+#include "Allocators.hpp"
 #include "CustomReader.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofObject;
 
 TEST_CASE("deserializeJson(char*)") {
-  StaticJsonDocument<1024> doc;
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
 
-  SECTION("should not duplicate strings") {
-    char input[] = "{\"hello\":\"world\"}";
+  char input[] = "{\"hello\":\"world\"}";
 
-    DeserializationError err = deserializeJson(doc, input);
+  DeserializationError err = deserializeJson(doc, input);
 
-    REQUIRE(err == DeserializationError::Ok);
-    CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1));
-    CHECK(doc.as<JsonVariant>().memoryUsage() ==
-          JSON_OBJECT_SIZE(1));  // issue #1318
-  }
+  REQUIRE(err == DeserializationError::Ok);
+
+  REQUIRE(spy.log() ==
+          AllocatorLog{
+              Allocate(sizeofStringBuffer()),
+              Reallocate(sizeofStringBuffer(), sizeofString("hello")),
+              Allocate(sizeofPool()),
+              Allocate(sizeofStringBuffer()),
+              Reallocate(sizeofStringBuffer(), sizeofString("world")),
+              Reallocate(sizeofPool(), sizeofObject(1)),
+          });
 }
 
 TEST_CASE("deserializeJson(unsigned char*, unsigned int)") {  // issue #1897
-  StaticJsonDocument<1024> doc;
+  JsonDocument doc;
 
   unsigned char input[] = "{\"hello\":\"world\"}";
   unsigned char* input_ptr = input;
@@ -37,7 +47,7 @@ TEST_CASE("deserializeJson(unsigned char*, unsigned int)") {  // issue #1897
 }
 
 TEST_CASE("deserializeJson(uint8_t*, size_t)") {  // issue #1898
-  StaticJsonDocument<1024> doc;
+  JsonDocument doc;
 
   uint8_t input[] = "{\"hello\":\"world\"}";
   uint8_t* input_ptr = input;
@@ -49,7 +59,7 @@ TEST_CASE("deserializeJson(uint8_t*, size_t)") {  // issue #1898
 }
 
 TEST_CASE("deserializeJson(const std::string&)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("should accept const string") {
     const std::string input("[42]");
@@ -60,7 +70,7 @@ TEST_CASE("deserializeJson(const std::string&)") {
   }
 
   SECTION("should accept temporary string") {
-    DeserializationError err = deserializeJson(doc, std::string("[42]"));
+    DeserializationError err = deserializeJson(doc, "[42]"_s);
 
     REQUIRE(err == DeserializationError::Ok);
   }
@@ -73,12 +83,12 @@ TEST_CASE("deserializeJson(const std::string&)") {
 
     JsonArray array = doc.as<JsonArray>();
     REQUIRE(err == DeserializationError::Ok);
-    REQUIRE(std::string("hello") == array[0]);
+    REQUIRE("hello"_s == array[0]);
   }
 }
 
 TEST_CASE("deserializeJson(std::istream&)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("array") {
     std::istringstream json(" [ 42 ] ");
@@ -99,7 +109,7 @@ TEST_CASE("deserializeJson(std::istream&)") {
 
     REQUIRE(err == DeserializationError::Ok);
     REQUIRE(1 == obj.size());
-    REQUIRE(std::string("world") == obj["hello"]);
+    REQUIRE("world"_s == obj["hello"]);
   }
 
   SECTION("Should not read after the closing brace of an empty object") {
@@ -149,7 +159,7 @@ TEST_CASE("deserializeJson(VLA)") {
   char vla[i];
   strcpy(vla, "{\"a\":42}");
 
-  StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc;
+  JsonDocument doc;
   DeserializationError err = deserializeJson(doc, vla);
 
   REQUIRE(err == DeserializationError::Ok);
@@ -157,7 +167,7 @@ TEST_CASE("deserializeJson(VLA)") {
 #endif
 
 TEST_CASE("deserializeJson(CustomReader)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   CustomReader reader("[4,2]");
   DeserializationError err = deserializeJson(doc, reader);
 
@@ -168,10 +178,10 @@ TEST_CASE("deserializeJson(CustomReader)") {
 }
 
 TEST_CASE("deserializeJson(JsonDocument&, MemberProxy)") {
-  DynamicJsonDocument doc1(4096);
+  JsonDocument doc1;
   doc1["payload"] = "[4,2]";
 
-  DynamicJsonDocument doc2(4096);
+  JsonDocument doc2;
   DeserializationError err = deserializeJson(doc2, doc1["payload"]);
 
   REQUIRE(err == DeserializationError::Ok);
@@ -181,10 +191,10 @@ TEST_CASE("deserializeJson(JsonDocument&, MemberProxy)") {
 }
 
 TEST_CASE("deserializeJson(JsonDocument&, JsonVariant)") {
-  DynamicJsonDocument doc1(4096);
+  JsonDocument doc1;
   doc1["payload"] = "[4,2]";
 
-  DynamicJsonDocument doc2(4096);
+  JsonDocument doc2;
   DeserializationError err =
       deserializeJson(doc2, doc1["payload"].as<JsonVariant>());
 
@@ -195,10 +205,10 @@ TEST_CASE("deserializeJson(JsonDocument&, JsonVariant)") {
 }
 
 TEST_CASE("deserializeJson(JsonDocument&, JsonVariantConst)") {
-  DynamicJsonDocument doc1(4096);
+  JsonDocument doc1;
   doc1["payload"] = "[4,2]";
 
-  DynamicJsonDocument doc2(4096);
+  JsonDocument doc2;
   DeserializationError err =
       deserializeJson(doc2, doc1["payload"].as<JsonVariantConst>());
 
@@ -209,10 +219,10 @@ TEST_CASE("deserializeJson(JsonDocument&, JsonVariantConst)") {
 }
 
 TEST_CASE("deserializeJson(JsonDocument&, ElementProxy)") {
-  DynamicJsonDocument doc1(4096);
+  JsonDocument doc1;
   doc1[0] = "[4,2]";
 
-  DynamicJsonDocument doc2(4096);
+  JsonDocument doc2;
   DeserializationError err = deserializeJson(doc2, doc1[0]);
 
   REQUIRE(err == DeserializationError::Ok);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/invalid_input.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/invalid_input.cpp
deleted file mode 100644
index 52b5038348157ddd64bf18c84a9ee419ae4afaa2..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/invalid_input.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#define ARDUINOJSON_DECODE_UNICODE 1
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("Invalid JSON input") {
-  const char* testCases[] = {"'\\u'",     "'\\u000g'", "'\\u000'", "'\\u000G'",
-                             "'\\u000/'", "\\x1234",   "6a9",      "1,",
-                             "nulL",      "tru3",      "fals3",    "2]",
-                             "3}"};
-  const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
-
-  DynamicJsonDocument doc(4096);
-
-  for (size_t i = 0; i < testCount; i++) {
-    const char* input = testCases[i];
-    CAPTURE(input);
-    REQUIRE(deserializeJson(doc, input) == DeserializationError::InvalidInput);
-  }
-}
-
-TEST_CASE("Invalid JSON input that should pass") {
-  const char* testCases[] = {
-      "'\\ud83d'",         // leading surrogate without a trailing surrogate
-      "'\\udda4'",         // trailing surrogate without a leading surrogate
-      "'\\ud83d\\ud83d'",  // two leading surrogates
-  };
-  const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
-
-  DynamicJsonDocument doc(4096);
-
-  for (size_t i = 0; i < testCount; i++) {
-    const char* input = testCases[i];
-    CAPTURE(input);
-    REQUIRE(deserializeJson(doc, input) == DeserializationError::Ok);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp
index 4f22de15fd1ee27bd8fcd63587e8f1f40a4b099d..cc5d10f5f9eaedd5414b4398f638ee1ce5c743e6 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp
@@ -1,117 +1,49 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
-using namespace Catch::Matchers;
+#include "Allocators.hpp"
 
-TEST_CASE("deserializeJson(DynamicJsonDocument&)") {
-  DynamicJsonDocument doc(4096);
+using ArduinoJson::detail::sizeofArray;
 
-  SECTION("Edge cases") {
-    SECTION("null char*") {
-      DeserializationError err = deserializeJson(doc, static_cast<char*>(0));
+TEST_CASE("deserializeJson() misc cases") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
 
-      REQUIRE(err != DeserializationError::Ok);
-    }
-
-    SECTION("null const char*") {
-      DeserializationError err =
-          deserializeJson(doc, static_cast<const char*>(0));
-
-      REQUIRE(err != DeserializationError::Ok);
-    }
-
-    SECTION("Empty input") {
-      DeserializationError err = deserializeJson(doc, "");
-
-      REQUIRE(err == DeserializationError::EmptyInput);
-    }
-
-    SECTION("Only spaces") {
-      DeserializationError err = deserializeJson(doc, "  \t\n\r");
-
-      REQUIRE(err == DeserializationError::EmptyInput);
-    }
-
-    SECTION("issue #628") {
-      DeserializationError err = deserializeJson(doc, "null");
-      REQUIRE(err == DeserializationError::Ok);
-      REQUIRE(doc.is<float>() == false);
-    }
-
-    SECTION("Garbage") {
-      DeserializationError err = deserializeJson(doc, "%*$£¤");
-
-      REQUIRE(err == DeserializationError::InvalidInput);
-    }
+  SECTION("null") {
+    DeserializationError err = deserializeJson(doc, "null");
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.is<float>() == false);
   }
 
-  SECTION("Booleans") {
-    SECTION("True") {
-      DeserializationError err = deserializeJson(doc, "true");
+  SECTION("true") {
+    DeserializationError err = deserializeJson(doc, "true");
 
-      REQUIRE(err == DeserializationError::Ok);
-      REQUIRE(doc.is<bool>());
-      REQUIRE(doc.as<bool>() == true);
-    }
-
-    SECTION("False") {
-      DeserializationError err = deserializeJson(doc, "false");
-
-      REQUIRE(err == DeserializationError::Ok);
-      REQUIRE(doc.is<bool>());
-      REQUIRE(doc.as<bool>() == false);
-    }
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.is<bool>());
+    REQUIRE(doc.as<bool>() == true);
   }
 
-  SECTION("Premature null-terminator") {
-    SECTION("In escape sequence") {
-      DeserializationError err = deserializeJson(doc, "\"\\");
-
-      REQUIRE(err == DeserializationError::IncompleteInput);
-    }
+  SECTION("false") {
+    DeserializationError err = deserializeJson(doc, "false");
 
-    SECTION("In double quoted string") {
-      DeserializationError err = deserializeJson(doc, "\"hello");
-
-      REQUIRE(err == DeserializationError::IncompleteInput);
-    }
-
-    SECTION("In single quoted string") {
-      DeserializationError err = deserializeJson(doc, "'hello");
-
-      REQUIRE(err == DeserializationError::IncompleteInput);
-    }
-  }
-
-  SECTION("Premature end of input") {
-    SECTION("In escape sequence") {
-      DeserializationError err = deserializeJson(doc, "\"\\n\"", 2);
-
-      REQUIRE(err == DeserializationError::IncompleteInput);
-    }
-
-    SECTION("In double quoted string") {
-      DeserializationError err = deserializeJson(doc, "\"hello\"", 6);
-
-      REQUIRE(err == DeserializationError::IncompleteInput);
-    }
-
-    SECTION("In single quoted string") {
-      DeserializationError err = deserializeJson(doc, "'hello'", 6);
-
-      REQUIRE(err == DeserializationError::IncompleteInput);
-    }
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.is<bool>());
+    REQUIRE(doc.as<bool>() == false);
   }
 
   SECTION("Should clear the JsonVariant") {
     deserializeJson(doc, "[1,2,3]");
+    spy.clearLog();
+
     deserializeJson(doc, "{}");
 
     REQUIRE(doc.is<JsonObject>());
-    REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofArray(3)),
+                         });
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp
index cf55dc850f3767d981f1ca547eb5a263a418c2f3..cd118414583c0fb17b585759ebfc83391f2ede88 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,12 +7,14 @@
 
 #include <sstream>
 
+#include "Literals.hpp"
+
 #define SHOULD_WORK(expression) REQUIRE(DeserializationError::Ok == expression);
 #define SHOULD_FAIL(expression) \
   REQUIRE(DeserializationError::TooDeep == expression);
 
 TEST_CASE("JsonDeserializer nesting") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("Input = const char*") {
     SECTION("limit = 0") {
@@ -63,23 +65,23 @@ TEST_CASE("JsonDeserializer nesting") {
   SECTION("Input = std::string") {
     SECTION("limit = 0") {
       DeserializationOption::NestingLimit nesting(0);
-      SHOULD_WORK(deserializeJson(doc, std::string("\"toto\""), nesting));
-      SHOULD_WORK(deserializeJson(doc, std::string("123"), nesting));
-      SHOULD_WORK(deserializeJson(doc, std::string("true"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("[]"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("{}"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("[\"toto\"]"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("{\"toto\":1}"), nesting));
+      SHOULD_WORK(deserializeJson(doc, "\"toto\""_s, nesting));
+      SHOULD_WORK(deserializeJson(doc, "123"_s, nesting));
+      SHOULD_WORK(deserializeJson(doc, "true"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "[]"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "{}"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "[\"toto\"]"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "{\"toto\":1}"_s, nesting));
     }
 
     SECTION("limit = 1") {
       DeserializationOption::NestingLimit nesting(1);
-      SHOULD_WORK(deserializeJson(doc, std::string("[\"toto\"]"), nesting));
-      SHOULD_WORK(deserializeJson(doc, std::string("{\"toto\":1}"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("{\"toto\":{}}"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("{\"toto\":[]}"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("[[\"toto\"]]"), nesting));
-      SHOULD_FAIL(deserializeJson(doc, std::string("[{\"toto\":1}]"), nesting));
+      SHOULD_WORK(deserializeJson(doc, "[\"toto\"]"_s, nesting));
+      SHOULD_WORK(deserializeJson(doc, "{\"toto\":1}"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "{\"toto\":{}}"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "{\"toto\":[]}"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "[[\"toto\"]]"_s, nesting));
+      SHOULD_FAIL(deserializeJson(doc, "[{\"toto\":1}]"_s, nesting));
     }
   }
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/number.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/number.cpp
index 05cf89e8024b6374caa5b40a54808be461c8f1cc..53595732fbdc7db29687294e60cb018da6348988 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/number.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/number.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_USE_LONG_LONG 0
@@ -16,7 +16,7 @@ using ArduinoJson::detail::isnan;
 }  // namespace my
 
 TEST_CASE("deserialize an integer") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("Integer") {
     SECTION("0") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/object.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/object.cpp
index 814258ecdda979ee3e94adc0dedc798b3e3991a5..501e87a0893daa714f8572a6d5421eff2512881c 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/object.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/object.cpp
@@ -1,12 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+
+using ArduinoJson::detail::sizeofObject;
+
 TEST_CASE("deserialize JSON object") {
-  DynamicJsonDocument doc(4096);
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
 
   SECTION("An empty object") {
     DeserializationError err = deserializeJson(doc, "{}");
@@ -277,7 +282,22 @@ TEST_CASE("deserialize JSON object") {
       DeserializationError err = deserializeJson(doc, "{a:{b:{c:1}},a:2}");
 
       REQUIRE(err == DeserializationError::Ok);
-      REQUIRE(doc["a"] == 2);
+      REQUIRE(doc.as<std::string>() == "{\"a\":2}");
+      REQUIRE(spy.log() ==
+              AllocatorLog{
+                  Allocate(sizeofStringBuffer()),
+                  Reallocate(sizeofStringBuffer(), sizeofString("a")),
+                  Allocate(sizeofPool()),
+                  Allocate(sizeofStringBuffer()),
+                  Reallocate(sizeofStringBuffer(), sizeofString("b")),
+                  Allocate(sizeofStringBuffer()),
+                  Reallocate(sizeofStringBuffer(), sizeofString("c")),
+                  Allocate(sizeofStringBuffer()),
+                  Deallocate(sizeofString("b")),
+                  Deallocate(sizeofString("c")),
+                  Deallocate(sizeofStringBuffer()),
+                  Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
+              });
     }
 
     SECTION("Repeated key with zero copy mode") {  // issue #1697
@@ -299,12 +319,17 @@ TEST_CASE("deserialize JSON object") {
 
   SECTION("Should clear the JsonObject") {
     deserializeJson(doc, "{\"hello\":\"world\"}");
+    spy.clearLog();
+
     deserializeJson(doc, "{}");
-    JsonObject obj = doc.as<JsonObject>();
 
     REQUIRE(doc.is<JsonObject>());
-    REQUIRE(obj.size() == 0);
-    REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
+    REQUIRE(doc.size() == 0);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofObject(1)),
+                             Deallocate(sizeofString("hello")),
+                             Deallocate(sizeofString("world")),
+                         });
   }
 
   SECTION("Issue #1335") {
@@ -313,3 +338,48 @@ TEST_CASE("deserialize JSON object") {
     CHECK(doc.as<std::string>() == json);
   }
 }
+
+TEST_CASE("deserialize JSON object under memory constraints") {
+  TimebombAllocator timebomb(1024);
+  JsonDocument doc(&timebomb);
+
+  SECTION("empty object requires no allocation") {
+    timebomb.setCountdown(0);
+    char input[] = "{}";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "{}");
+  }
+
+  SECTION("key allocation fails") {
+    timebomb.setCountdown(0);
+    char input[] = "{\"a\":1}";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::NoMemory);
+    REQUIRE(doc.as<std::string>() == "{}");
+  }
+
+  SECTION("pool allocation fails") {
+    timebomb.setCountdown(2);
+    char input[] = "{\"a\":1}";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::NoMemory);
+    REQUIRE(doc.as<std::string>() == "{}");
+  }
+
+  SECTION("string allocation fails") {
+    timebomb.setCountdown(3);
+    char input[] = "{\"a\":\"b\"}";
+
+    DeserializationError err = deserializeJson(doc, input);
+
+    REQUIRE(err == DeserializationError::NoMemory);
+    REQUIRE(doc.as<std::string>() == "{\"a\":null}");
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/object_static.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/object_static.cpp
deleted file mode 100644
index b198ea74ff17f26b2b264ad25909c72e8c7b8fec..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/object_static.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("deserialize JSON object with StaticJsonDocument") {
-  SECTION("BufferOfTheRightSizeForEmptyObject") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(0)> doc;
-    char input[] = "{}";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::Ok);
-  }
-
-  SECTION("TooSmallBufferForObjectWithOneValue") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(0)> doc;
-    char input[] = "{\"a\":1}";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::NoMemory);
-  }
-
-  SECTION("BufferOfTheRightSizeForObjectWithOneValue") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc;
-    char input[] = "{\"a\":1}";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::Ok);
-  }
-
-  SECTION("TooSmallBufferForObjectWithNestedObject") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(0) + JSON_ARRAY_SIZE(0)> doc;
-    char input[] = "{\"a\":[]}";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::NoMemory);
-  }
-
-  SECTION("BufferOfTheRightSizeForObjectWithNestedObject") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(0)> doc;
-    char input[] = "{\"a\":[]}";
-
-    DeserializationError err = deserializeJson(doc, input);
-
-    REQUIRE(err == DeserializationError::Ok);
-  }
-
-  SECTION("Should clear the JsonObject") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc;
-    char input[] = "{\"hello\":\"world\"}";
-
-    deserializeJson(doc, input);
-    deserializeJson(doc, "{}");
-
-    REQUIRE(doc.as<JsonObject>().size() == 0);
-    REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/string.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/string.cpp
index 2379d45548ea5ad8ffdc7bbc80a7e7dba17c73e7..a47ab76e56a473a74d2384f359c5de6b3f5e5780 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/string.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDeserializer/string.cpp
@@ -1,11 +1,16 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_DECODE_UNICODE 1
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+using ArduinoJson::detail::sizeofObject;
+
 TEST_CASE("Valid JSON strings value") {
   struct TestCase {
     const char* input;
@@ -35,7 +40,7 @@ TEST_CASE("Valid JSON strings value") {
   };
   const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
 
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   for (size_t i = 0; i < testCount; i++) {
     const TestCase& testCase = testCases[i];
@@ -47,7 +52,7 @@ TEST_CASE("Valid JSON strings value") {
 }
 
 TEST_CASE("\\u0000") {
-  StaticJsonDocument<200> doc;
+  JsonDocument doc;
 
   DeserializationError err = deserializeJson(doc, "\"wx\\u0000yz\"");
   REQUIRE(err == DeserializationError::Ok);
@@ -68,7 +73,7 @@ TEST_CASE("Truncated JSON string") {
   const char* testCases[] = {"\"hello", "\'hello", "'\\u", "'\\u00", "'\\u000"};
   const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
 
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   for (size_t i = 0; i < testCount; i++) {
     const char* input = testCases[i];
@@ -83,7 +88,7 @@ TEST_CASE("Invalid JSON string") {
                              "'\\u000G'", "'\\u000/'", "'\\x1234'"};
   const size_t testCount = sizeof(testCases) / sizeof(testCases[0]);
 
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   for (size_t i = 0; i < testCount; i++) {
     const char* input = testCases[i];
@@ -92,41 +97,102 @@ TEST_CASE("Invalid JSON string") {
   }
 }
 
-TEST_CASE("Not enough room to save the key") {
-  DynamicJsonDocument doc(JSON_OBJECT_SIZE(1) + 8);
+TEST_CASE("Allocation of the key fails") {
+  TimebombAllocator timebomb(0);
+  SpyingAllocator spy(&timebomb);
+  JsonDocument doc(&spy);
 
-  SECTION("Quoted string") {
+  SECTION("Quoted string, first member") {
     REQUIRE(deserializeJson(doc, "{\"example\":1}") ==
-            DeserializationError::Ok);
-    REQUIRE(deserializeJson(doc, "{\"accuracy\":1}") ==
             DeserializationError::NoMemory);
+    REQUIRE(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofStringBuffer()),
+                         });
+  }
+
+  SECTION("Quoted string, second member") {
+    timebomb.setCountdown(3);
     REQUIRE(deserializeJson(doc, "{\"hello\":1,\"world\"}") ==
-            DeserializationError::NoMemory);  // fails in the second string
+            DeserializationError::NoMemory);
+    REQUIRE(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                Reallocate(sizeofStringBuffer(), sizeofString("hello")),
+                Allocate(sizeofPool()),
+                AllocateFail(sizeofStringBuffer()),
+                ReallocateFail(sizeofPool(), sizeofObject(1)),
+            });
   }
 
-  SECTION("Non-quoted string") {
-    REQUIRE(deserializeJson(doc, "{example:1}") == DeserializationError::Ok);
-    REQUIRE(deserializeJson(doc, "{accuracy:1}") ==
+  SECTION("Non-Quoted string, first member") {
+    REQUIRE(deserializeJson(doc, "{example:1}") ==
             DeserializationError::NoMemory);
+    REQUIRE(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofStringBuffer()),
+                         });
+  }
+
+  SECTION("Non-Quoted string, second member") {
+    timebomb.setCountdown(3);
     REQUIRE(deserializeJson(doc, "{hello:1,world}") ==
-            DeserializationError::NoMemory);  // fails in the second string
+            DeserializationError::NoMemory);
+    REQUIRE(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                Reallocate(sizeofStringBuffer(), sizeofString("hello")),
+                Allocate(sizeofPool()),
+                AllocateFail(sizeofStringBuffer()),
+                ReallocateFail(sizeofPool(), sizeofObject(1)),
+            });
   }
 }
 
-TEST_CASE("Empty memory pool") {
-  // NOLINTNEXTLINE(clang-analyzer-optin.portability.UnixAPI)
-  DynamicJsonDocument doc(0);
+TEST_CASE("String allocation fails") {
+  SpyingAllocator spy(FailingAllocator::instance());
+  JsonDocument doc(&spy);
 
   SECTION("Input is const char*") {
     REQUIRE(deserializeJson(doc, "\"hello\"") ==
             DeserializationError::NoMemory);
-    REQUIRE(deserializeJson(doc, "\"\"") == DeserializationError::NoMemory);
+    REQUIRE(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofStringBuffer()),
+                         });
   }
+}
 
-  SECTION("Input is const char*") {
-    char hello[] = "\"hello\"";
-    REQUIRE(deserializeJson(doc, hello) == DeserializationError::Ok);
-    char empty[] = "\"hello\"";
-    REQUIRE(deserializeJson(doc, empty) == DeserializationError::Ok);
-  }
+TEST_CASE("Deduplicate values") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  deserializeJson(doc, "[\"example\",\"example\"]");
+
+  CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
+  REQUIRE(spy.log() ==
+          AllocatorLog{
+              Allocate(sizeofPool()),
+              Allocate(sizeofStringBuffer()),
+              Reallocate(sizeofStringBuffer(), sizeofString("example")),
+              Allocate(sizeofStringBuffer()),
+              Deallocate(sizeofStringBuffer()),
+              Reallocate(sizeofPool(), sizeofArray(2)),
+          });
+}
+
+TEST_CASE("Deduplicate keys") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  deserializeJson(doc, "[{\"example\":1},{\"example\":2}]");
+
+  const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
+  const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
+  CHECK(key1 == key2);
+
+  REQUIRE(spy.log() ==
+          AllocatorLog{
+              Allocate(sizeofPool()),
+              Allocate(sizeofStringBuffer()),
+              Reallocate(sizeofStringBuffer(), sizeofString("example")),
+              Allocate(sizeofStringBuffer()),
+              Deallocate(sizeofStringBuffer()),
+              Reallocate(sizeofPool(), sizeofArray(2) + 2 * sizeofObject(1)),
+          });
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/BasicJsonDocument.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/BasicJsonDocument.cpp
deleted file mode 100644
index 348fd94cfcd77fe557a053786b9156caf94ec6ad..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/BasicJsonDocument.cpp
+++ /dev/null
@@ -1,180 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <stdlib.h>  // malloc, free
-#include <catch.hpp>
-#include <sstream>
-#include <utility>
-
-class SpyingAllocator {
- public:
-  SpyingAllocator(const SpyingAllocator& src) : log_(src.log_) {}
-  SpyingAllocator(std::ostream& log) : log_(log) {}
-  SpyingAllocator& operator=(const SpyingAllocator& src) = delete;
-
-  void* allocate(size_t n) {
-    log_ << "A" << n;
-    return malloc(n);
-  }
-  void deallocate(void* p) {
-    log_ << "F";
-    free(p);
-  }
-
- private:
-  std::ostream& log_;
-};
-
-class ControllableAllocator {
- public:
-  ControllableAllocator() : enabled_(true) {}
-
-  void* allocate(size_t n) {
-    return enabled_ ? malloc(n) : 0;
-  }
-
-  void deallocate(void* p) {
-    free(p);
-  }
-
-  void disable() {
-    enabled_ = false;
-  }
-
- private:
-  bool enabled_;
-};
-
-TEST_CASE("BasicJsonDocument") {
-  std::stringstream log;
-
-  SECTION("Construct/Destruct") {
-    { BasicJsonDocument<SpyingAllocator> doc(4096, log); }
-    REQUIRE(log.str() == "A4096F");
-  }
-
-  SECTION("Copy construct") {
-    {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
-      doc1.set(std::string("The size of this string is 32!!"));
-
-      BasicJsonDocument<SpyingAllocator> doc2(doc1);
-
-      REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.capacity() == 4096);
-    }
-    REQUIRE(log.str() == "A4096A4096FF");
-  }
-
-  SECTION("Move construct") {
-    {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
-      doc1.set(std::string("The size of this string is 32!!"));
-
-      BasicJsonDocument<SpyingAllocator> doc2(std::move(doc1));
-
-      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc1.as<std::string>() == "null");
-      REQUIRE(doc1.capacity() == 0);
-      REQUIRE(doc2.capacity() == 4096);
-    }
-    REQUIRE(log.str() == "A4096F");
-  }
-
-  SECTION("Copy assign larger") {
-    {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
-      doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> doc2(8, log);
-
-      doc2 = doc1;
-
-      REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.capacity() == 4096);
-    }
-    REQUIRE(log.str() == "A4096A8FA4096FF");
-  }
-
-  SECTION("Copy assign smaller") {
-    {
-      BasicJsonDocument<SpyingAllocator> doc1(1024, log);
-      doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> doc2(4096, log);
-
-      doc2 = doc1;
-
-      REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.capacity() == 1024);
-    }
-    REQUIRE(log.str() == "A1024A4096FA1024FF");
-  }
-
-  SECTION("Copy assign same size") {
-    {
-      BasicJsonDocument<SpyingAllocator> doc1(1024, log);
-      doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> doc2(1024, log);
-
-      doc2 = doc1;
-
-      REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc2.capacity() == 1024);
-    }
-    REQUIRE(log.str() == "A1024A1024FF");
-  }
-
-  SECTION("Move assign") {
-    {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
-      doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> doc2(8, log);
-
-      doc2 = std::move(doc1);
-
-      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
-      REQUIRE(doc1.as<std::string>() == "null");
-      REQUIRE(doc1.capacity() == 0);
-      REQUIRE(doc2.capacity() == 4096);
-    }
-    REQUIRE(log.str() == "A4096A8FF");
-  }
-
-  SECTION("garbageCollect()") {
-    BasicJsonDocument<ControllableAllocator> doc(4096);
-
-    SECTION("when allocation succeeds") {
-      deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}");
-      REQUIRE(doc.capacity() == 4096);
-      REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
-      doc.remove("blanket");
-
-      bool result = doc.garbageCollect();
-
-      REQUIRE(result == true);
-      REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
-      REQUIRE(doc.capacity() == 4096);
-      REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
-    }
-
-    SECTION("when allocation fails") {
-      deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}");
-      REQUIRE(doc.capacity() == 4096);
-      REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
-      doc.remove("blanket");
-      doc.allocator().disable();
-
-      bool result = doc.garbageCollect();
-
-      REQUIRE(result == false);
-      REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
-      REQUIRE(doc.capacity() == 4096);
-      REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
-    }
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt
index 3e9b4683500c8490066b7d1f953931efe274b408..e6b6f1dbbd3c2b98f9825dabc7697e61ab1d75b3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt
@@ -1,15 +1,15 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(JsonDocumentTests
 	add.cpp
-	BasicJsonDocument.cpp
+	assignment.cpp
 	cast.cpp
+	clear.cpp
 	compare.cpp
+	constructor.cpp
 	containsKey.cpp
-	createNested.cpp
-	DynamicJsonDocument.cpp
 	ElementProxy.cpp
 	isNull.cpp
 	issue1120.cpp
@@ -19,7 +19,6 @@ add_executable(JsonDocumentTests
 	remove.cpp
 	shrinkToFit.cpp
 	size.cpp
-	StaticJsonDocument.cpp
 	subscript.cpp
 	swap.cpp
 )
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/DynamicJsonDocument.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/DynamicJsonDocument.cpp
deleted file mode 100644
index 397f8c5dc19295ed705ed0a97b9f34952d971494..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/DynamicJsonDocument.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-using ArduinoJson::detail::addPadding;
-
-static void REQUIRE_JSON(JsonDocument& doc, const std::string& expected) {
-  std::string json;
-  serializeJson(doc, json);
-  REQUIRE(json == expected);
-}
-
-TEST_CASE("DynamicJsonDocument") {
-  DynamicJsonDocument doc(4096);
-
-  SECTION("serializeJson()") {
-    JsonObject obj = doc.to<JsonObject>();
-    obj["hello"] = "world";
-
-    std::string json;
-    serializeJson(doc, json);
-
-    REQUIRE(json == "{\"hello\":\"world\"}");
-  }
-
-  SECTION("memoryUsage()") {
-    SECTION("starts at zero") {
-      REQUIRE(doc.memoryUsage() == 0);
-    }
-
-    SECTION("JSON_ARRAY_SIZE(0)") {
-      doc.to<JsonArray>();
-      REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
-    }
-
-    SECTION("JSON_ARRAY_SIZE(1)") {
-      doc.to<JsonArray>().add(42);
-      REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1));
-    }
-
-    SECTION("JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(0)") {
-      doc.to<JsonArray>().createNestedArray();
-      REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(0));
-    }
-  }
-
-  SECTION("capacity()") {
-    SECTION("matches constructor argument") {
-      DynamicJsonDocument doc2(256);
-      REQUIRE(doc2.capacity() == 256);
-    }
-
-    SECTION("rounds up constructor argument") {
-      DynamicJsonDocument doc2(253);
-      REQUIRE(doc2.capacity() == 256);
-    }
-  }
-
-  SECTION("memoryUsage()") {
-    SECTION("Increases after adding value to array") {
-      JsonArray arr = doc.to<JsonArray>();
-
-      REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
-      arr.add(42);
-      REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1));
-      arr.add(43);
-      REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2));
-    }
-
-    SECTION("Increases after adding value to object") {
-      JsonObject obj = doc.to<JsonObject>();
-
-      REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
-      obj["a"] = 1;
-      REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1));
-      obj["b"] = 2;
-      REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2));
-    }
-  }
-}
-
-TEST_CASE("DynamicJsonDocument constructor") {
-  SECTION("Copy constructor") {
-    DynamicJsonDocument doc1(1234);
-    deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-    DynamicJsonDocument doc2 = doc1;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-
-    REQUIRE(doc2.capacity() == doc1.capacity());
-  }
-
-  SECTION("Construct from StaticJsonDocument") {
-    StaticJsonDocument<200> doc1;
-    deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-    DynamicJsonDocument doc2 = doc1;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    REQUIRE(doc2.capacity() == doc1.capacity());
-  }
-
-  SECTION("Construct from JsonObject") {
-    StaticJsonDocument<200> doc1;
-    JsonObject obj = doc1.to<JsonObject>();
-    obj["hello"] = "world";
-
-    DynamicJsonDocument doc2 = obj;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage()));
-  }
-
-  SECTION("Construct from JsonArray") {
-    StaticJsonDocument<200> doc1;
-    JsonArray arr = doc1.to<JsonArray>();
-    arr.add("hello");
-
-    DynamicJsonDocument doc2 = arr;
-
-    REQUIRE_JSON(doc2, "[\"hello\"]");
-    REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage()));
-  }
-
-  SECTION("Construct from JsonVariant") {
-    StaticJsonDocument<200> doc1;
-    deserializeJson(doc1, "42");
-
-    DynamicJsonDocument doc2 = doc1.as<JsonVariant>();
-
-    REQUIRE_JSON(doc2, "42");
-    REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage()));
-  }
-}
-
-TEST_CASE("DynamicJsonDocument assignment") {
-  SECTION("Copy assignment reallocates when capacity is smaller") {
-    DynamicJsonDocument doc1(1234);
-    deserializeJson(doc1, "{\"hello\":\"world\"}");
-    DynamicJsonDocument doc2(8);
-
-    doc2 = doc1;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    REQUIRE(doc2.capacity() == doc1.capacity());
-  }
-
-  SECTION("Copy assignment reallocates when capacity is larger") {
-    DynamicJsonDocument doc1(100);
-    deserializeJson(doc1, "{\"hello\":\"world\"}");
-    DynamicJsonDocument doc2(1234);
-
-    doc2 = doc1;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    REQUIRE(doc2.capacity() == doc1.capacity());
-  }
-
-  SECTION("Assign from StaticJsonDocument") {
-    StaticJsonDocument<200> doc1;
-    deserializeJson(doc1, "{\"hello\":\"world\"}");
-    DynamicJsonDocument doc2(4096);
-    doc2.to<JsonVariant>().set(666);
-
-    doc2 = doc1;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-  }
-
-  SECTION("Assign from JsonObject") {
-    StaticJsonDocument<200> doc1;
-    JsonObject obj = doc1.to<JsonObject>();
-    obj["hello"] = "world";
-
-    DynamicJsonDocument doc2(4096);
-    doc2 = obj;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    REQUIRE(doc2.capacity() == 4096);
-  }
-
-  SECTION("Assign from JsonArray") {
-    StaticJsonDocument<200> doc1;
-    JsonArray arr = doc1.to<JsonArray>();
-    arr.add("hello");
-
-    DynamicJsonDocument doc2(4096);
-    doc2 = arr;
-
-    REQUIRE_JSON(doc2, "[\"hello\"]");
-    REQUIRE(doc2.capacity() == 4096);
-  }
-
-  SECTION("Assign from JsonVariant") {
-    StaticJsonDocument<200> doc1;
-    deserializeJson(doc1, "42");
-
-    DynamicJsonDocument doc2(4096);
-    doc2 = doc1.as<JsonVariant>();
-
-    REQUIRE_JSON(doc2, "42");
-    REQUIRE(doc2.capacity() == 4096);
-  }
-
-  SECTION("Assign from MemberProxy") {
-    StaticJsonDocument<200> doc1;
-    doc1["value"] = 42;
-
-    DynamicJsonDocument doc2(4096);
-    doc2 = doc1["value"];
-
-    REQUIRE_JSON(doc2, "42");
-    REQUIRE(doc2.capacity() == 4096);
-  }
-
-  SECTION("Assign from ElementProxy") {
-    StaticJsonDocument<200> doc1;
-    doc1[0] = 42;
-
-    DynamicJsonDocument doc2(4096);
-    doc2 = doc1[0];
-
-    REQUIRE_JSON(doc2, "42");
-    REQUIRE(doc2.capacity() == 4096);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp
index 00fcdda26bb7a523ef4837fc95ceb39db2334237..0a9039d437ef1c44b06067cffb58751164bf8cc8 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp
@@ -1,15 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 typedef ArduinoJson::detail::ElementProxy<JsonDocument&> ElementProxy;
 
 TEST_CASE("ElementProxy::add()") {
-  DynamicJsonDocument doc(4096);
-  doc.add();
+  JsonDocument doc;
+  doc.add<JsonVariant>();
   ElementProxy ep = doc[0];
 
   SECTION("add(int)") {
@@ -34,8 +36,8 @@ TEST_CASE("ElementProxy::add()") {
 }
 
 TEST_CASE("ElementProxy::clear()") {
-  DynamicJsonDocument doc(4096);
-  doc.add();
+  JsonDocument doc;
+  doc.add<JsonVariant>();
   ElementProxy ep = doc[0];
 
   SECTION("size goes back to zero") {
@@ -54,7 +56,7 @@ TEST_CASE("ElementProxy::clear()") {
 }
 
 TEST_CASE("ElementProxy::operator==()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("1 vs 1") {
     doc.add(1);
@@ -94,8 +96,8 @@ TEST_CASE("ElementProxy::operator==()") {
 }
 
 TEST_CASE("ElementProxy::remove()") {
-  DynamicJsonDocument doc(4096);
-  doc.add();
+  JsonDocument doc;
+  doc.add<JsonVariant>();
   ElementProxy ep = doc[0];
 
   SECTION("remove(int)") {
@@ -121,7 +123,7 @@ TEST_CASE("ElementProxy::remove()") {
     ep["a"] = 1;
     ep["b"] = 2;
 
-    ep.remove(std::string("b"));
+    ep.remove("b"_s);
 
     REQUIRE(ep.as<std::string>() == "{\"a\":1}");
   }
@@ -142,7 +144,7 @@ TEST_CASE("ElementProxy::remove()") {
 }
 
 TEST_CASE("ElementProxy::set()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   ElementProxy ep = doc[0];
 
   SECTION("set(int)") {
@@ -167,8 +169,8 @@ TEST_CASE("ElementProxy::set()") {
 }
 
 TEST_CASE("ElementProxy::size()") {
-  DynamicJsonDocument doc(4096);
-  doc.add();
+  JsonDocument doc;
+  doc.add<JsonVariant>();
   ElementProxy ep = doc[0];
 
   SECTION("returns 0") {
@@ -188,23 +190,8 @@ TEST_CASE("ElementProxy::size()") {
   }
 }
 
-TEST_CASE("ElementProxy::memoryUsage()") {
-  DynamicJsonDocument doc(4096);
-  doc.add();
-  ElementProxy ep = doc[0];
-
-  SECTION("returns 0 for null") {
-    REQUIRE(ep.memoryUsage() == 0);
-  }
-
-  SECTION("returns size for string") {
-    ep.set(std::string("hello"));
-    REQUIRE(ep.memoryUsage() == 6);
-  }
-}
-
 TEST_CASE("ElementProxy::operator[]") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   ElementProxy ep = doc[1];
 
   SECTION("set member") {
@@ -221,7 +208,7 @@ TEST_CASE("ElementProxy::operator[]") {
 }
 
 TEST_CASE("ElementProxy cast to JsonVariantConst") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   doc[0] = "world";
 
   const ElementProxy ep = doc[0];
@@ -232,7 +219,7 @@ TEST_CASE("ElementProxy cast to JsonVariantConst") {
 }
 
 TEST_CASE("ElementProxy cast to JsonVariant") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   doc[0] = "world";
 
   ElementProxy ep = doc[0];
@@ -245,11 +232,3 @@ TEST_CASE("ElementProxy cast to JsonVariant") {
 
   CHECK(doc.as<std::string>() == "[\"toto\"]");
 }
-
-TEST_CASE("ElementProxy::shallowCopy()") {
-  StaticJsonDocument<1024> doc1, doc2;
-  doc2["hello"] = "world";
-  doc1[0].shallowCopy(doc2);
-
-  CHECK(doc1.as<std::string>() == "[{\"hello\":\"world\"}]");
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp
index a3707b03e5d5ca3353eda5ced21da1ffac695836..bb6acdfcc4a8f36bb29f5591aa28f1caef181018 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp
@@ -1,15 +1,24 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
+#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
+#define ARDUINOJSON_ENABLE_PROGMEM 1
 #include <ArduinoJson.h>
+
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+using ArduinoJson::detail::sizeofObject;
+
 typedef ArduinoJson::detail::MemberProxy<JsonDocument&, const char*>
     MemberProxy;
 
 TEST_CASE("MemberProxy::add()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   MemberProxy mp = doc["hello"];
 
   SECTION("add(int)") {
@@ -26,7 +35,7 @@ TEST_CASE("MemberProxy::add()") {
 }
 
 TEST_CASE("MemberProxy::clear()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   MemberProxy mp = doc["hello"];
 
   SECTION("size goes back to zero") {
@@ -45,7 +54,7 @@ TEST_CASE("MemberProxy::clear()") {
 }
 
 TEST_CASE("MemberProxy::operator==()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("1 vs 1") {
     doc["a"] = 1;
@@ -85,7 +94,7 @@ TEST_CASE("MemberProxy::operator==()") {
 }
 
 TEST_CASE("MemberProxy::containsKey()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   MemberProxy mp = doc["hello"];
 
   SECTION("containsKey(const char*)") {
@@ -98,19 +107,19 @@ TEST_CASE("MemberProxy::containsKey()") {
   SECTION("containsKey(std::string)") {
     mp["key"] = "value";
 
-    REQUIRE(mp.containsKey(std::string("key")) == true);
-    REQUIRE(mp.containsKey(std::string("key")) == true);
+    REQUIRE(mp.containsKey("key"_s) == true);
+    REQUIRE(mp.containsKey("key"_s) == true);
   }
 }
 
 TEST_CASE("MemberProxy::operator|()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("const char*") {
     doc["a"] = "hello";
 
-    REQUIRE((doc["a"] | "world") == std::string("hello"));
-    REQUIRE((doc["b"] | "world") == std::string("world"));
+    REQUIRE((doc["a"] | "world") == "hello"_s);
+    REQUIRE((doc["b"] | "world") == "world"_s);
   }
 
   SECTION("Issue #1411") {
@@ -120,14 +129,14 @@ TEST_CASE("MemberProxy::operator|()") {
                                 // to trigger the bug
     const char* sensor = doc["sensor"] | test;  // "gps"
 
-    REQUIRE(sensor == std::string("gps"));
+    REQUIRE(sensor == "gps"_s);
   }
 
   SECTION("Issue #1415") {
     JsonObject object = doc.to<JsonObject>();
     object["hello"] = "world";
 
-    StaticJsonDocument<0> emptyDoc;
+    JsonDocument emptyDoc;
     JsonObject anotherObject = object["hello"] | emptyDoc.to<JsonObject>();
 
     REQUIRE(anotherObject.isNull() == false);
@@ -136,7 +145,7 @@ TEST_CASE("MemberProxy::operator|()") {
 }
 
 TEST_CASE("MemberProxy::remove()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   MemberProxy mp = doc["hello"];
 
   SECTION("remove(int)") {
@@ -162,7 +171,7 @@ TEST_CASE("MemberProxy::remove()") {
     mp["a"] = 1;
     mp["b"] = 2;
 
-    mp.remove(std::string("b"));
+    mp.remove("b"_s);
 
     REQUIRE(mp.as<std::string>() == "{\"a\":1}");
   }
@@ -183,7 +192,7 @@ TEST_CASE("MemberProxy::remove()") {
 }
 
 TEST_CASE("MemberProxy::set()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   MemberProxy mp = doc["hello"];
 
   SECTION("set(int)") {
@@ -208,7 +217,7 @@ TEST_CASE("MemberProxy::set()") {
 }
 
 TEST_CASE("MemberProxy::size()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   MemberProxy mp = doc["hello"];
 
   SECTION("returns 0") {
@@ -230,22 +239,8 @@ TEST_CASE("MemberProxy::size()") {
   }
 }
 
-TEST_CASE("MemberProxy::memoryUsage()") {
-  DynamicJsonDocument doc(4096);
-  MemberProxy mp = doc["hello"];
-
-  SECTION("returns 0 when null") {
-    REQUIRE(mp.memoryUsage() == 0);
-  }
-
-  SECTION("return the size for a string") {
-    mp.set(std::string("hello"));
-    REQUIRE(mp.memoryUsage() == 6);
-  }
-}
-
 TEST_CASE("MemberProxy::operator[]") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   MemberProxy mp = doc["hello"];
 
   SECTION("set member") {
@@ -262,7 +257,7 @@ TEST_CASE("MemberProxy::operator[]") {
 }
 
 TEST_CASE("MemberProxy cast to JsonVariantConst") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   doc["hello"] = "world";
 
   const MemberProxy mp = doc["hello"];
@@ -273,7 +268,7 @@ TEST_CASE("MemberProxy cast to JsonVariantConst") {
 }
 
 TEST_CASE("MemberProxy cast to JsonVariant") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   doc["hello"] = "world";
 
   MemberProxy mp = doc["hello"];
@@ -287,42 +282,83 @@ TEST_CASE("MemberProxy cast to JsonVariant") {
   CHECK(doc.as<std::string>() == "{\"hello\":\"toto\"}");
 }
 
-TEST_CASE("MemberProxy::createNestedArray()") {
-  StaticJsonDocument<1024> doc;
-  JsonArray arr = doc["items"].createNestedArray();
-  arr.add(42);
+TEST_CASE("Deduplicate keys") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
 
-  CHECK(doc["items"][0][0] == 42);
-}
+  SECTION("std::string") {
+    doc[0]["example"_s] = 1;
+    doc[1]["example"_s] = 2;
 
-TEST_CASE("MemberProxy::createNestedArray(key)") {
-  StaticJsonDocument<1024> doc;
-  JsonArray arr = doc["weather"].createNestedArray("temp");
-  arr.add(42);
+    const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
+    const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
+    CHECK(key1 == key2);
 
-  CHECK(doc["weather"]["temp"][0] == 42);
-}
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
 
-TEST_CASE("MemberProxy::createNestedObject()") {
-  StaticJsonDocument<1024> doc;
-  JsonObject obj = doc["items"].createNestedObject();
-  obj["value"] = 42;
+  SECTION("char*") {
+    char key[] = "example";
+    doc[0][key] = 1;
+    doc[1][key] = 2;
 
-  CHECK(doc["items"][0]["value"] == 42);
-}
+    const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
+    const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
+    CHECK(key1 == key2);
+
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
+
+  SECTION("Arduino String") {
+    doc[0][String("example")] = 1;
+    doc[1][String("example")] = 2;
+
+    const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
+    const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
+    CHECK(key1 == key2);
+
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
+
+  SECTION("Flash string") {
+    doc[0][F("example")] = 1;
+    doc[1][F("example")] = 2;
 
-TEST_CASE("MemberProxy::createNestedObject(key)") {
-  StaticJsonDocument<1024> doc;
-  JsonObject obj = doc["status"].createNestedObject("weather");
-  obj["temp"] = 42;
+    const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
+    const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
+    CHECK(key1 == key2);
 
-  CHECK(doc["status"]["weather"]["temp"] == 42);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
 }
 
-TEST_CASE("MemberProxy::shallowCopy()") {
-  StaticJsonDocument<1024> doc1, doc2;
-  doc2["hello"] = "world";
-  doc1["obj"].shallowCopy(doc2);
+TEST_CASE("MemberProxy under memory constraints") {
+  KillswitchAllocator killswitch;
+  SpyingAllocator spy(&killswitch);
+  JsonDocument doc(&spy);
 
-  CHECK(doc1.as<std::string>() == "{\"obj\":{\"hello\":\"world\"}}");
+  SECTION("key allocation fails") {
+    killswitch.on();
+
+    doc["hello"_s] = "world";
+
+    REQUIRE(doc.is<JsonObject>());
+    REQUIRE(doc.size() == 0);
+    REQUIRE(doc.overflowed() == true);
+    REQUIRE(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofString("hello")),
+                         });
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/StaticJsonDocument.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/StaticJsonDocument.cpp
deleted file mode 100644
index 0d4089bc2a765bf5d7ecadd2a9fa0f68b18d4ff5..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/StaticJsonDocument.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-static void REQUIRE_JSON(JsonDocument& doc, const std::string& expected) {
-  std::string json;
-  serializeJson(doc, json);
-  REQUIRE(json == expected);
-}
-
-TEST_CASE("StaticJsonDocument") {
-  SECTION("capacity()") {
-    SECTION("matches template argument") {
-      StaticJsonDocument<256> doc;
-      REQUIRE(doc.capacity() == 256);
-    }
-
-    SECTION("rounds up template argument") {
-      StaticJsonDocument<253> doc;
-      REQUIRE(doc.capacity() == 256);
-    }
-  }
-
-  SECTION("serializeJson()") {
-    StaticJsonDocument<200> doc;
-    JsonObject obj = doc.to<JsonObject>();
-    obj["hello"] = "world";
-
-    std::string json;
-    serializeJson(doc, json);
-
-    REQUIRE(json == "{\"hello\":\"world\"}");
-  }
-
-  SECTION("Copy assignment") {
-    StaticJsonDocument<200> doc1, doc2;
-    doc1.to<JsonVariant>().set(666);
-    deserializeJson(doc2, "{\"hello\":\"world\"}");
-
-    doc1 = doc2;
-
-    REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-  }
-
-  SECTION("Contructor") {
-    SECTION("Copy constructor") {
-      StaticJsonDocument<200> doc1;
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      StaticJsonDocument<200> doc2 = doc1;
-
-      deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}");
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Construct from StaticJsonDocument of different size") {
-      StaticJsonDocument<300> doc1;
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      StaticJsonDocument<200> doc2 = doc1;
-
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Construct from DynamicJsonDocument") {
-      DynamicJsonDocument doc1(4096);
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      StaticJsonDocument<200> doc2 = doc1;
-
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Construct from JsonObject") {
-      DynamicJsonDocument doc1(4096);
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      StaticJsonDocument<200> doc2 = doc1.as<JsonObject>();
-
-      deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}");
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Construct from JsonArray") {
-      DynamicJsonDocument doc1(4096);
-      deserializeJson(doc1, "[\"hello\",\"world\"]");
-
-      StaticJsonDocument<200> doc2 = doc1.as<JsonArray>();
-
-      deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]");
-      REQUIRE_JSON(doc2, "[\"hello\",\"world\"]");
-    }
-
-    SECTION("Construct from JsonVariant") {
-      DynamicJsonDocument doc1(4096);
-      deserializeJson(doc1, "42");
-
-      StaticJsonDocument<200> doc2 = doc1.as<JsonVariant>();
-
-      REQUIRE_JSON(doc2, "42");
-    }
-  }
-
-  SECTION("Assignment") {
-    SECTION("Copy assignment") {
-      StaticJsonDocument<200> doc1, doc2;
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      doc2 = doc1;
-
-      deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}");
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Assign from StaticJsonDocument of different capacity") {
-      StaticJsonDocument<200> doc1;
-      StaticJsonDocument<300> doc2;
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      doc2 = doc1;
-
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Assign from DynamicJsonDocument") {
-      StaticJsonDocument<200> doc1;
-      DynamicJsonDocument doc2(4096);
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      doc2 = doc1;
-
-      deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}");
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Assign from JsonArray") {
-      StaticJsonDocument<200> doc1;
-      DynamicJsonDocument doc2(4096);
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "[\"hello\",\"world\"]");
-
-      doc2 = doc1.as<JsonArray>();
-
-      deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]");
-      REQUIRE_JSON(doc2, "[\"hello\",\"world\"]");
-    }
-
-    SECTION("Assign from JsonArrayConst") {
-      StaticJsonDocument<200> doc1;
-      DynamicJsonDocument doc2(4096);
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "[\"hello\",\"world\"]");
-
-      doc2 = doc1.as<JsonArrayConst>();
-
-      deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]");
-      REQUIRE_JSON(doc2, "[\"hello\",\"world\"]");
-    }
-
-    SECTION("Assign from JsonObject") {
-      StaticJsonDocument<200> doc1;
-      DynamicJsonDocument doc2(4096);
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      doc2 = doc1.as<JsonObject>();
-
-      deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}");
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Assign from JsonObjectConst") {
-      StaticJsonDocument<200> doc1;
-      DynamicJsonDocument doc2(4096);
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "{\"hello\":\"world\"}");
-
-      doc2 = doc1.as<JsonObjectConst>();
-
-      deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}");
-      REQUIRE_JSON(doc2, "{\"hello\":\"world\"}");
-    }
-
-    SECTION("Assign from JsonVariant") {
-      DynamicJsonDocument doc1(4096);
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "42");
-
-      StaticJsonDocument<200> doc2;
-      doc2 = doc1.as<JsonVariant>();
-
-      REQUIRE_JSON(doc2, "42");
-    }
-
-    SECTION("Assign from JsonVariantConst") {
-      DynamicJsonDocument doc1(4096);
-      doc1.to<JsonVariant>().set(666);
-      deserializeJson(doc1, "42");
-
-      StaticJsonDocument<200> doc2;
-      doc2 = doc1.as<JsonVariantConst>();
-
-      REQUIRE_JSON(doc2, "42");
-    }
-  }
-
-  SECTION("garbageCollect()") {
-    StaticJsonDocument<256> doc;
-    doc[std::string("example")] = std::string("jukebox");
-    doc.remove("example");
-    REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 16);
-
-    doc.garbageCollect();
-
-    REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
-    REQUIRE_JSON(doc, "{}");
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/add.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/add.cpp
index 5843ac96700ef2939ffa4d30366550ed4c583756..498663c208a271763d979eb2c42e31e1a661f800 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/add.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/add.cpp
@@ -1,22 +1,147 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
+#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
+#define ARDUINOJSON_ENABLE_PROGMEM 1
 #include <ArduinoJson.h>
+
 #include <catch.hpp>
 
-TEST_CASE("JsonDocument::add()") {
-  DynamicJsonDocument doc(4096);
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+
+TEST_CASE("JsonDocument::add(T)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
 
   SECTION("integer") {
     doc.add(42);
 
     REQUIRE(doc.as<std::string>() == "[42]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                         });
   }
 
   SECTION("const char*") {
     doc.add("hello");
 
     REQUIRE(doc.as<std::string>() == "[\"hello\"]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                         });
+  }
+
+  SECTION("std::string") {
+    doc.add("example"_s);
+    doc.add("example"_s);
+
+    CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
+
+  SECTION("char*") {
+    char value[] = "example";
+    doc.add(value);
+    doc.add(value);
+
+    CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
+
+  SECTION("Arduino String") {
+    doc.add(String("example"));
+    doc.add(String("example"));
+
+    CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
+
+  SECTION("Flash string") {
+    doc.add(F("example"));
+    doc.add(F("example"));
+
+    CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("example")),
+                         });
+  }
+}
+
+TEST_CASE("JsonDocument::add<T>()") {
+  JsonDocument doc;
+
+  SECTION("JsonArray") {
+    JsonArray array = doc.add<JsonArray>();
+    array.add(1);
+    array.add(2);
+    REQUIRE(doc.as<std::string>() == "[[1,2]]");
+  }
+
+  SECTION("JsonVariant") {
+    JsonVariant variant = doc.add<JsonVariant>();
+    variant.set(42);
+    REQUIRE(doc.as<std::string>() == "[42]");
+  }
+}
+
+TEST_CASE("JsonObject::add(JsonObject) ") {
+  JsonDocument doc1;
+  doc1["hello"_s] = "world"_s;
+
+  TimebombAllocator allocator(10);
+  SpyingAllocator spy(&allocator);
+  JsonDocument doc2(&spy);
+
+  SECTION("success") {
+    bool result = doc2.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == true);
+    REQUIRE(doc2.as<std::string>() == "[{\"hello\":\"world\"}]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("partial failure") {  // issue #2081
+    allocator.setCountdown(2);
+
+    bool result = doc2.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == false);
+    REQUIRE(doc2.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("hello")),
+                             AllocateFail(sizeofString("world")),
+                             Deallocate(sizeofString("hello")),
+                         });
+  }
+
+  SECTION("complete failure") {
+    allocator.setCountdown(0);
+
+    bool result = doc2.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == false);
+    REQUIRE(doc2.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofPool()),
+                         });
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/assignment.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/assignment.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..42cef3e517595144052e7b7cac22444c2dc387a9
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/assignment.cpp
@@ -0,0 +1,135 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+TEST_CASE("JsonDocument assignment") {
+  SpyingAllocator spyingAllocator;
+
+  SECTION("Copy assignment same capacity") {
+    JsonDocument doc1(&spyingAllocator);
+    deserializeJson(doc1, "{\"hello\":\"world\"}");
+    JsonDocument doc2(&spyingAllocator);
+    spyingAllocator.clearLog();
+
+    doc2 = doc1;
+
+    REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
+
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello")),
+                                         Allocate(sizeofPool()),
+                                         Allocate(sizeofString("world")),
+                                     });
+  }
+
+  SECTION("Copy assignment reallocates when capacity is smaller") {
+    JsonDocument doc1(&spyingAllocator);
+    deserializeJson(doc1, "[{\"hello\":\"world\"}]");
+    JsonDocument doc2(&spyingAllocator);
+    spyingAllocator.clearLog();
+
+    doc2 = doc1;
+
+    REQUIRE(doc2.as<std::string>() == "[{\"hello\":\"world\"}]");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofPool()),
+                                         Allocate(sizeofString("hello")),
+                                         Allocate(sizeofString("world")),
+                                     });
+  }
+
+  SECTION("Copy assignment reallocates when capacity is larger") {
+    JsonDocument doc1(&spyingAllocator);
+    deserializeJson(doc1, "{\"hello\":\"world\"}");
+    JsonDocument doc2(&spyingAllocator);
+    spyingAllocator.clearLog();
+
+    doc2 = doc1;
+
+    REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello")),
+                                         Allocate(sizeofPool()),
+                                         Allocate(sizeofString("world")),
+                                     });
+  }
+
+  SECTION("Move assign") {
+    {
+      JsonDocument doc1(&spyingAllocator);
+      doc1["hello"_s] = "world"_s;
+      JsonDocument doc2(&spyingAllocator);
+
+      doc2 = std::move(doc1);
+
+      REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
+      REQUIRE(doc1.as<std::string>() == "null");
+    }
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello")),
+                                         Allocate(sizeofPool()),
+                                         Allocate(sizeofString("world")),
+                                         Deallocate(sizeofString("hello")),
+                                         Deallocate(sizeofString("world")),
+                                         Deallocate(sizeofPool()),
+                                     });
+  }
+
+  SECTION("Assign from JsonObject") {
+    JsonDocument doc1;
+    JsonObject obj = doc1.to<JsonObject>();
+    obj["hello"] = "world";
+
+    JsonDocument doc2;
+    doc2 = obj;
+
+    REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
+  }
+
+  SECTION("Assign from JsonArray") {
+    JsonDocument doc1;
+    JsonArray arr = doc1.to<JsonArray>();
+    arr.add("hello");
+
+    JsonDocument doc2;
+    doc2 = arr;
+
+    REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
+  }
+
+  SECTION("Assign from JsonVariant") {
+    JsonDocument doc1;
+    deserializeJson(doc1, "42");
+
+    JsonDocument doc2;
+    doc2 = doc1.as<JsonVariant>();
+
+    REQUIRE(doc2.as<std::string>() == "42");
+  }
+
+  SECTION("Assign from MemberProxy") {
+    JsonDocument doc1;
+    doc1["value"] = 42;
+
+    JsonDocument doc2;
+    doc2 = doc1["value"];
+
+    REQUIRE(doc2.as<std::string>() == "42");
+  }
+
+  SECTION("Assign from ElementProxy") {
+    JsonDocument doc1;
+    doc1[0] = 42;
+
+    JsonDocument doc2;
+    doc2 = doc1[0];
+
+    REQUIRE(doc2.as<std::string>() == "42");
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/cast.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/cast.cpp
index 253e1fc763ba3d139ec8ff37bb62b2bc086f4b6f..05c9ee71d3b703342f96810f393d93257be099d0 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/cast.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/cast.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -8,7 +8,7 @@
 #include <string>
 
 TEST_CASE("Implicit cast to JsonVariant") {
-  StaticJsonDocument<128> doc;
+  JsonDocument doc;
 
   doc["hello"] = "world";
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/clear.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/clear.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3b9f6ee57991d2e44a1863c9cb0721eb47e2ea5c
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/clear.cpp
@@ -0,0 +1,48 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include <stdlib.h>  // malloc, free
+#include <string>
+
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+TEST_CASE("JsonDocument::clear()") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+
+  SECTION("null") {
+    doc.clear();
+
+    REQUIRE(doc.isNull());
+    REQUIRE(spy.log() == AllocatorLog{});
+  }
+
+  SECTION("releases resources") {
+    doc["hello"_s] = "world"_s;
+    spy.clearLog();
+
+    doc.clear();
+
+    REQUIRE(doc.isNull());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofPool()),
+                             Deallocate(sizeofString("hello")),
+                             Deallocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("clear free list") {  // issue #2034
+    JsonObject obj = doc.to<JsonObject>();
+    obj["a"] = 1;
+    obj.clear();  // puts the slot in the free list
+
+    doc.clear();
+
+    doc["b"] = 2;  // will it pick from the free list?
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/compare.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/compare.cpp
index d834b226237bcd81f0304222e0462c6eb1ed9c4e..a1b8ef204e9c89c7701404d0d09468e7a4f32328 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/compare.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/compare.cpp
@@ -1,60 +1,13 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
-TEST_CASE("DynamicJsonDocument::operator==(const DynamicJsonDocument&)") {
-  DynamicJsonDocument doc1(4096);
-  DynamicJsonDocument doc2(4096);
-
-  SECTION("Empty") {
-    REQUIRE(doc1 == doc2);
-    REQUIRE_FALSE(doc1 != doc2);
-  }
-
-  SECTION("With same object") {
-    doc1["hello"] = "world";
-    doc2["hello"] = "world";
-    REQUIRE(doc1 == doc2);
-    REQUIRE_FALSE(doc1 != doc2);
-  }
-  SECTION("With different object") {
-    doc1["hello"] = "world";
-    doc2["world"] = "hello";
-    REQUIRE_FALSE(doc1 == doc2);
-    REQUIRE(doc1 != doc2);
-  }
-}
-
-TEST_CASE("DynamicJsonDocument::operator==(const StaticJsonDocument&)") {
-  DynamicJsonDocument doc1(4096);
-  StaticJsonDocument<256> doc2;
-
-  SECTION("Empty") {
-    REQUIRE(doc1 == doc2);
-    REQUIRE_FALSE(doc1 != doc2);
-  }
-
-  SECTION("With same object") {
-    doc1["hello"] = "world";
-    doc2["hello"] = "world";
-    REQUIRE(doc1 == doc2);
-    REQUIRE_FALSE(doc1 != doc2);
-  }
-
-  SECTION("With different object") {
-    doc1["hello"] = "world";
-    doc2["world"] = "hello";
-    REQUIRE_FALSE(doc1 == doc2);
-    REQUIRE(doc1 != doc2);
-  }
-}
-
-TEST_CASE("StaticJsonDocument::operator==(const DynamicJsonDocument&)") {
-  StaticJsonDocument<256> doc1;
-  DynamicJsonDocument doc2(4096);
+TEST_CASE("JsonDocument::operator==(const JsonDocument&)") {
+  JsonDocument doc1;
+  JsonDocument doc2;
 
   SECTION("Empty") {
     REQUIRE(doc1 == doc2);
@@ -67,7 +20,6 @@ TEST_CASE("StaticJsonDocument::operator==(const DynamicJsonDocument&)") {
     REQUIRE(doc1 == doc2);
     REQUIRE_FALSE(doc1 != doc2);
   }
-
   SECTION("With different object") {
     doc1["hello"] = "world";
     doc2["world"] = "hello";
@@ -75,29 +27,3 @@ TEST_CASE("StaticJsonDocument::operator==(const DynamicJsonDocument&)") {
     REQUIRE(doc1 != doc2);
   }
 }
-
-TEST_CASE("JsonDocument::operator==(const JsonDocument&)") {
-  StaticJsonDocument<256> doc1;
-  StaticJsonDocument<256> doc2;
-  const JsonDocument& ref1 = doc1;
-  const JsonDocument& ref2 = doc2;
-
-  SECTION("Empty") {
-    REQUIRE(ref1 == ref2);
-    REQUIRE_FALSE(ref1 != ref2);
-  }
-
-  SECTION("With same object") {
-    doc1["hello"] = "world";
-    doc2["hello"] = "world";
-    REQUIRE(ref1 == ref2);
-    REQUIRE_FALSE(ref1 != ref2);
-  }
-
-  SECTION("With different object") {
-    doc1["hello"] = "world";
-    doc2["world"] = "hello";
-    REQUIRE_FALSE(ref1 == ref2);
-    REQUIRE(ref1 != ref2);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/constructor.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/constructor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..239b3bf73dd0802fc6ac91650328cbef580b982a
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/constructor.cpp
@@ -0,0 +1,148 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::addPadding;
+
+TEST_CASE("JsonDocument constructor") {
+  SpyingAllocator spyingAllocator;
+
+  SECTION("JsonDocument(size_t)") {
+    { JsonDocument doc(&spyingAllocator); }
+    REQUIRE(spyingAllocator.log() == AllocatorLog{});
+  }
+
+  SECTION("JsonDocument(const JsonDocument&)") {
+    {
+      JsonDocument doc1(&spyingAllocator);
+      doc1.set("The size of this string is 32!!"_s);
+
+      JsonDocument doc2(doc1);
+
+      REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
+      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
+    }
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofStringBuffer()),
+                                         Allocate(sizeofStringBuffer()),
+                                         Deallocate(sizeofStringBuffer()),
+                                         Deallocate(sizeofStringBuffer()),
+                                     });
+  }
+
+  SECTION("JsonDocument(JsonDocument&&)") {
+    {
+      JsonDocument doc1(&spyingAllocator);
+      doc1.set("The size of this string is 32!!"_s);
+
+      JsonDocument doc2(std::move(doc1));
+
+      REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
+      REQUIRE(doc1.as<std::string>() == "null");
+    }
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofStringBuffer()),
+                                         Deallocate(sizeofStringBuffer()),
+                                     });
+  }
+
+  SECTION("JsonDocument(JsonObject, Allocator*)") {
+    JsonDocument doc1;
+    JsonObject obj = doc1.to<JsonObject>();
+    obj["hello"] = "world";
+
+    JsonDocument doc2(obj, &spyingAllocator);
+
+    REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofPool()),
+                                     });
+  }
+
+  SECTION("JsonDocument(JsonObject)") {
+    JsonDocument doc1;
+    JsonObject obj = doc1.to<JsonObject>();
+    obj["hello"] = "world";
+
+    JsonDocument doc2(obj);
+
+    REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
+  }
+
+  SECTION("JsonDocument(JsonArray, Allocator*)") {
+    JsonDocument doc1;
+    JsonArray arr = doc1.to<JsonArray>();
+    arr.add("hello");
+
+    JsonDocument doc2(arr, &spyingAllocator);
+
+    REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofPool()),
+                                     });
+  }
+
+  SECTION("JsonDocument(JsonArray)") {
+    JsonDocument doc1;
+    JsonArray arr = doc1.to<JsonArray>();
+    arr.add("hello");
+
+    JsonDocument doc2(arr);
+
+    REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
+  }
+
+  SECTION("JsonDocument(JsonVariant, Allocator*)") {
+    JsonDocument doc1;
+    deserializeJson(doc1, "\"hello\"");
+
+    JsonDocument doc2(doc1.as<JsonVariant>(), &spyingAllocator);
+
+    REQUIRE(doc2.as<std::string>() == "hello");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello")),
+                                     });
+  }
+
+  SECTION("JsonDocument(JsonVariant)") {
+    JsonDocument doc1;
+    deserializeJson(doc1, "\"hello\"");
+
+    JsonDocument doc2(doc1.as<JsonVariant>());
+
+    REQUIRE(doc2.as<std::string>() == "hello");
+  }
+
+  SECTION("JsonDocument(JsonVariantConst)") {
+    JsonDocument doc1;
+    deserializeJson(doc1, "\"hello\"");
+
+    JsonDocument doc2(doc1.as<JsonVariantConst>());
+
+    REQUIRE(doc2.as<std::string>() == "hello");
+  }
+
+  SECTION("JsonDocument(ElementProxy)") {
+    JsonDocument doc1;
+    deserializeJson(doc1, "[\"hello\",\"world\"]");
+
+    JsonDocument doc2(doc1[1]);
+
+    REQUIRE(doc2.as<std::string>() == "world");
+  }
+
+  SECTION("JsonDocument(MemberProxy)") {
+    JsonDocument doc1;
+    deserializeJson(doc1, "{\"hello\":\"world\"}");
+
+    JsonDocument doc2(doc1["hello"]);
+
+    REQUIRE(doc2.as<std::string>() == "world");
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp
index 8274fc76b27e3c65724b4ce06309f28c7f8dd3ce..a68236e3a110e69bb4e6f5be2662f0ae14adc461 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp
@@ -1,12 +1,14 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("JsonDocument::containsKey()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("returns true on object") {
     doc["hello"] = "world";
@@ -23,7 +25,7 @@ TEST_CASE("JsonDocument::containsKey()") {
   SECTION("returns true when key is a std::string") {
     doc["hello"] = "world";
 
-    REQUIRE(doc.containsKey(std::string("hello")) == true);
+    REQUIRE(doc.containsKey("hello"_s) == true);
   }
 
   SECTION("returns false  on object") {
@@ -41,4 +43,12 @@ TEST_CASE("JsonDocument::containsKey()") {
   SECTION("returns false on null") {
     REQUIRE(doc.containsKey("hello") == false);
   }
+
+  SECTION("support JsonVariant") {
+    doc["hello"] = "world";
+    doc["key"] = "hello";
+
+    REQUIRE(doc.containsKey(doc["key"]) == true);
+    REQUIRE(doc.containsKey(doc["foo"]) == false);
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/createNested.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/createNested.cpp
deleted file mode 100644
index 0c9435757091dc151795540cd8385638ae9412d1..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/createNested.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonDocument::createNestedArray()") {
-  DynamicJsonDocument doc(4096);
-
-  SECTION("promotes to array") {
-    doc.createNestedArray();
-
-    REQUIRE(doc.is<JsonArray>());
-  }
-}
-
-TEST_CASE("JsonDocument::createNestedArray(key)") {
-  DynamicJsonDocument doc(4096);
-
-  SECTION("key is const char*") {
-    SECTION("promotes to object") {
-      doc.createNestedArray("hello");
-
-      REQUIRE(doc.is<JsonObject>());
-    }
-  }
-
-  SECTION("key is std::string") {
-    SECTION("promotes to object") {
-      doc.createNestedArray(std::string("hello"));
-
-      REQUIRE(doc.is<JsonObject>());
-    }
-  }
-}
-
-TEST_CASE("JsonDocument::createNestedObject()") {
-  DynamicJsonDocument doc(4096);
-
-  SECTION("promotes to array") {
-    doc.createNestedObject();
-
-    REQUIRE(doc.is<JsonArray>());
-  }
-}
-
-TEST_CASE("JsonDocument::createNestedObject(key)") {
-  DynamicJsonDocument doc(4096);
-
-  SECTION("key is const char*") {
-    SECTION("promotes to object") {
-      doc.createNestedObject("hello");
-
-      REQUIRE(doc.is<JsonObject>());
-    }
-  }
-
-  SECTION("key is std::string") {
-    SECTION("promotes to object") {
-      doc.createNestedObject(std::string("hello"));
-
-      REQUIRE(doc.is<JsonObject>());
-    }
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/isNull.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/isNull.cpp
index d5aaae022ea367700ee3a5f8673136945a2fafca..219c306c5e045ec5adb6ca6f5f0ad12493ffe295 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/isNull.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/isNull.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonDocument::isNull()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("returns true if uninitialized") {
     REQUIRE(doc.isNull() == true);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/issue1120.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/issue1120.cpp
index fbee477877406b10b08129dcb79a2c758b924af8..07ad9324c6e7f350a0b1f81151e3037b0f029245 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/issue1120.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/issue1120.cpp
@@ -2,20 +2,22 @@
 
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("Issue #1120") {
-  StaticJsonDocument<500> doc;
+  JsonDocument doc;
   constexpr char str[] =
       "{\"contents\":[{\"module\":\"Packet\"},{\"module\":\"Analog\"}]}";
   deserializeJson(doc, str);
 
   SECTION("MemberProxy<std::string>::isNull()") {
     SECTION("returns false") {
-      auto value = doc[std::string("contents")];
+      auto value = doc["contents"_s];
       CHECK(value.isNull() == false);
     }
 
     SECTION("returns true") {
-      auto value = doc[std::string("zontents")];
+      auto value = doc["zontents"_s];
       CHECK(value.isNull() == true);
     }
   }
@@ -46,12 +48,12 @@ TEST_CASE("Issue #1120") {
 
   SECTION("MemberProxy<ElementProxy<MemberProxy>, std::string>::isNull()") {
     SECTION("returns false") {
-      auto value = doc["contents"][1][std::string("module")];
+      auto value = doc["contents"][1]["module"_s];
       CHECK(value.isNull() == false);
     }
 
     SECTION("returns true") {
-      auto value = doc["contents"][1][std::string("zodule")];
+      auto value = doc["contents"][1]["zodule"_s];
       CHECK(value.isNull() == true);
     }
   }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/nesting.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/nesting.cpp
index 7ac7e7685e51a4030b1c5cb11586e8d9713c459a..4f4a3a5270aecd68f3d3fe598ec5ca7209ee1ae4 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/nesting.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/nesting.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonDocument::nesting()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("return 0 if uninitialized") {
     REQUIRE(doc.nesting() == 0);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp
index e46d21b09b298f6e09f9f42c089293596c6a2a7a..9dfa83ff45e1cc6de5ce3f6f6bfc2c0bd5a44f9c 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp
@@ -1,85 +1,96 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
 TEST_CASE("JsonDocument::overflowed()") {
+  TimebombAllocator timebomb(10);
+  JsonDocument doc(&timebomb);
+
   SECTION("returns false on a fresh object") {
-    StaticJsonDocument<0> doc;
+    timebomb.setCountdown(0);
     CHECK(doc.overflowed() == false);
   }
 
   SECTION("returns true after a failed insertion") {
-    StaticJsonDocument<0> doc;
+    timebomb.setCountdown(0);
     doc.add(0);
     CHECK(doc.overflowed() == true);
   }
 
   SECTION("returns false after successful insertion") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(1)> doc;
+    timebomb.setCountdown(2);
     doc.add(0);
     CHECK(doc.overflowed() == false);
   }
 
   SECTION("returns true after a failed string copy") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(1)> doc;
-    doc.add(std::string("example"));
+    timebomb.setCountdown(0);
+    doc.add("example"_s);
     CHECK(doc.overflowed() == true);
   }
 
   SECTION("returns false after a successful string copy") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(1) + 8> doc;
-    doc.add(std::string("example"));
+    timebomb.setCountdown(3);
+    doc.add("example"_s);
     CHECK(doc.overflowed() == false);
   }
 
   SECTION("returns true after a failed member add") {
-    StaticJsonDocument<1> doc;
+    timebomb.setCountdown(0);
     doc["example"] = true;
     CHECK(doc.overflowed() == true);
   }
 
   SECTION("returns true after a failed deserialization") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(1)> doc;
-    deserializeJson(doc, "[\"example\"]");
+    timebomb.setCountdown(0);
+    deserializeJson(doc, "[1, 2]");
     CHECK(doc.overflowed() == true);
   }
 
   SECTION("returns false after a successful deserialization") {
-    StaticJsonDocument<JSON_ARRAY_SIZE(1) + 8> doc;
+    timebomb.setCountdown(3);
     deserializeJson(doc, "[\"example\"]");
     CHECK(doc.overflowed() == false);
   }
 
   SECTION("returns false after clear()") {
-    StaticJsonDocument<0> doc;
+    timebomb.setCountdown(0);
     doc.add(0);
     doc.clear();
     CHECK(doc.overflowed() == false);
   }
 
   SECTION("remains false after shrinkToFit()") {
-    DynamicJsonDocument doc(JSON_ARRAY_SIZE(1));
+    timebomb.setCountdown(2);
     doc.add(0);
+    timebomb.setCountdown(2);
     doc.shrinkToFit();
     CHECK(doc.overflowed() == false);
   }
 
   SECTION("remains true after shrinkToFit()") {
-    DynamicJsonDocument doc(JSON_ARRAY_SIZE(1));
-    doc.add(0);
+    timebomb.setCountdown(0);
     doc.add(0);
+    timebomb.setCountdown(2);
     doc.shrinkToFit();
     CHECK(doc.overflowed() == true);
   }
 
-  SECTION("return false after garbageCollect()") {
-    DynamicJsonDocument doc(JSON_ARRAY_SIZE(1));
-    doc.add(0);
-    doc.add(0);
-    doc.garbageCollect();
+  SECTION("returns false when string length doesn't overflow") {
+    auto maxLength = ArduinoJson::detail::StringNode::maxLength;
+    CHECK(doc.set(std::string(maxLength, 'a')) == true);
     CHECK(doc.overflowed() == false);
   }
+
+  SECTION("returns true when string length overflows") {
+    auto maxLength = ArduinoJson::detail::StringNode::maxLength;
+    CHECK(doc.set(std::string(maxLength + 1, 'a')) == false);
+    CHECK(doc.overflowed() == true);
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/remove.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/remove.cpp
index 724b324d5deb1e71ab52ade2563cecdbc8047f23..afabf4eb195effb9f9850ca892e497354a484864 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/remove.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/remove.cpp
@@ -1,12 +1,14 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("JsonDocument::remove()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("remove(int)") {
     doc.add(1);
@@ -31,7 +33,7 @@ TEST_CASE("JsonDocument::remove()") {
     doc["a"] = 1;
     doc["b"] = 2;
 
-    doc.remove(std::string("b"));
+    doc.remove("b"_s);
 
     REQUIRE(doc.as<std::string>() == "{\"a\":1}");
   }
@@ -49,4 +51,25 @@ TEST_CASE("JsonDocument::remove()") {
     REQUIRE(doc.as<std::string>() == "{\"a\":1}");
   }
 #endif
+
+  SECTION("remove(JsonVariant) from object") {
+    doc["a"] = 1;
+    doc["b"] = 2;
+    doc["c"] = "b";
+
+    doc.remove(doc["c"]);
+
+    REQUIRE(doc.as<std::string>() == "{\"a\":1,\"c\":\"b\"}");
+  }
+
+  SECTION("remove(JsonVariant) from array") {
+    doc[0] = 3;
+    doc[1] = 2;
+    doc[2] = 1;
+
+    doc.remove(doc[2]);
+    doc.remove(doc[3]);  // noop
+
+    REQUIRE(doc.as<std::string>() == "[3,1]");
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp
index a4423340486cae0504b655abb2220478fdbe4ccd..1d431401152e4e521985fbed667f7444e1201185 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -8,145 +8,177 @@
 #include <stdlib.h>  // malloc, free
 #include <string>
 
-class ArmoredAllocator {
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+using ArduinoJson::detail::sizeofObject;
+
+class ArmoredAllocator : public Allocator {
  public:
-  ArmoredAllocator() : ptr_(0), size_(0) {}
+  virtual ~ArmoredAllocator() {}
 
-  void* allocate(size_t size) {
-    ptr_ = malloc(size);
-    size_ = size;
-    return ptr_;
+  void* allocate(size_t size) override {
+    return malloc(size);
   }
 
-  void deallocate(void* ptr) {
-    REQUIRE(ptr == ptr_);
+  void deallocate(void* ptr) override {
     free(ptr);
-    ptr_ = 0;
-    size_ = 0;
   }
 
-  void* reallocate(void* ptr, size_t new_size) {
-    REQUIRE(ptr == ptr_);
+  void* reallocate(void* ptr, size_t new_size) override {
     // don't call realloc, instead alloc a new buffer and erase the old one
     // this way we make sure we support relocation
     void* new_ptr = malloc(new_size);
-    memcpy(new_ptr, ptr_, std::min(new_size, size_));
-    memset(ptr_, '#', size_);  // erase
-    free(ptr_);
-    ptr_ = new_ptr;
+    memset(new_ptr, '#', new_size);  // erase
+    if (ptr) {
+      memcpy(new_ptr, ptr, std::min(new_size, new_size));
+      free(ptr);
+    }
     return new_ptr;
   }
-
- private:
-  void* ptr_;
-  size_t size_;
 };
 
-typedef BasicJsonDocument<ArmoredAllocator> ShrinkToFitTestDocument;
+TEST_CASE("JsonDocument::shrinkToFit()") {
+  ArmoredAllocator armoredAllocator;
+  SpyingAllocator spyingAllocator(&armoredAllocator);
+  JsonDocument doc(&spyingAllocator);
 
-void testShrinkToFit(ShrinkToFitTestDocument& doc, std::string expected_json,
-                     size_t expected_size) {
-  // test twice: shrinkToFit() should be idempotent
-  for (int i = 0; i < 2; i++) {
+  SECTION("null") {
     doc.shrinkToFit();
 
-    REQUIRE(doc.capacity() == expected_size);
-    REQUIRE(doc.memoryUsage() == expected_size);
-
-    std::string json;
-    serializeJson(doc, json);
-    REQUIRE(json == expected_json);
-  }
-}
-
-TEST_CASE("BasicJsonDocument::shrinkToFit()") {
-  ShrinkToFitTestDocument doc(4096);
-
-  SECTION("null") {
-    testShrinkToFit(doc, "null", 0);
+    REQUIRE(doc.as<std::string>() == "null");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{});
   }
 
   SECTION("empty object") {
     deserializeJson(doc, "{}");
-    testShrinkToFit(doc, "{}", JSON_OBJECT_SIZE(0));
+
+    doc.shrinkToFit();
+
+    REQUIRE(doc.as<std::string>() == "{}");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{});
   }
 
   SECTION("empty array") {
     deserializeJson(doc, "[]");
-    testShrinkToFit(doc, "[]", JSON_ARRAY_SIZE(0));
+
+    doc.shrinkToFit();
+
+    REQUIRE(doc.as<std::string>() == "[]");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{});
   }
 
   SECTION("linked string") {
     doc.set("hello");
-    testShrinkToFit(doc, "\"hello\"", 0);
+
+    doc.shrinkToFit();
+
+    REQUIRE(doc.as<std::string>() == "hello");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{});
   }
 
   SECTION("owned string") {
-    doc.set(std::string("abcdefg"));
-    testShrinkToFit(doc, "\"abcdefg\"", 8);
-  }
+    doc.set("abcdefg"_s);
+    REQUIRE(doc.as<std::string>() == "abcdefg");
+
+    doc.shrinkToFit();
 
-  SECTION("linked raw") {
-    doc.set(serialized("[{},123]"));
-    testShrinkToFit(doc, "[{},123]", 0);
+    REQUIRE(doc.as<std::string>() == "abcdefg");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("abcdefg")),
+                                     });
   }
 
-  SECTION("owned raw") {
-    doc.set(serialized(std::string("[{},12]")));
-    testShrinkToFit(doc, "[{},12]", 8);
+  SECTION("raw string") {
+    doc.set(serialized("[{},12]"));
+
+    doc.shrinkToFit();
+
+    REQUIRE(doc.as<std::string>() == "[{},12]");
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("[{},12]")),
+                                     });
   }
 
   SECTION("linked key") {
     doc["key"] = 42;
-    testShrinkToFit(doc, "{\"key\":42}", JSON_OBJECT_SIZE(1));
+
+    doc.shrinkToFit();
+
+    REQUIRE(doc.as<std::string>() == "{\"key\":42}");
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()),
+                Reallocate(sizeofPool(), sizeofObject(1)),
+            });
   }
 
   SECTION("owned key") {
-    doc[std::string("abcdefg")] = 42;
-    testShrinkToFit(doc, "{\"abcdefg\":42}", JSON_OBJECT_SIZE(1) + 8);
+    doc["abcdefg"_s] = 42;
+
+    doc.shrinkToFit();
+
+    REQUIRE(doc.as<std::string>() == "{\"abcdefg\":42}");
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofString("abcdefg")),
+                Allocate(sizeofPool()),
+                Reallocate(sizeofPool(), sizeofObject(1)),
+            });
   }
 
   SECTION("linked string in array") {
     doc.add("hello");
-    testShrinkToFit(doc, "[\"hello\"]", JSON_ARRAY_SIZE(1));
+
+    doc.shrinkToFit();
+
+    REQUIRE(doc.as<std::string>() == "[\"hello\"]");
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()),
+                Reallocate(sizeofPool(), sizeofArray(1)),
+            });
   }
 
   SECTION("owned string in array") {
-    doc.add(std::string("abcdefg"));
-    testShrinkToFit(doc, "[\"abcdefg\"]", JSON_ARRAY_SIZE(1) + 8);
-  }
+    doc.add("abcdefg"_s);
 
-  SECTION("linked string in object") {
-    doc["key"] = "hello";
-    testShrinkToFit(doc, "{\"key\":\"hello\"}", JSON_OBJECT_SIZE(1));
-  }
+    doc.shrinkToFit();
 
-  SECTION("owned string in object") {
-    doc["key"] = std::string("abcdefg");
-    testShrinkToFit(doc, "{\"key\":\"abcdefg\"}", JSON_ARRAY_SIZE(1) + 8);
+    REQUIRE(doc.as<std::string>() == "[\"abcdefg\"]");
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()),
+                Allocate(sizeofString("abcdefg")),
+                Reallocate(sizeofPool(), sizeofArray(1)),
+            });
   }
 
-  SECTION("unaligned") {
-    doc.add(std::string("?"));  // two bytes in the string pool
-    REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 2);
+  SECTION("linked string in object") {
+    doc["key"] = "hello";
 
     doc.shrinkToFit();
 
-    // the new capacity should be padded to align the pointers
-    REQUIRE(doc.capacity() == JSON_OBJECT_SIZE(1) + sizeof(void*));
-    REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 2);
-    REQUIRE(doc[0] == "?");
+    REQUIRE(doc.as<std::string>() == "{\"key\":\"hello\"}");
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()),
+                Reallocate(sizeofPool(), sizeofObject(1)),
+            });
   }
-}
-
-TEST_CASE("DynamicJsonDocument::shrinkToFit()") {
-  DynamicJsonDocument doc(4096);
 
-  deserializeJson(doc, "{\"hello\":[\"world\"]");
+  SECTION("owned string in object") {
+    doc["key"] = "abcdefg"_s;
 
-  doc.shrinkToFit();
+    doc.shrinkToFit();
 
-  std::string json;
-  serializeJson(doc, json);
-  REQUIRE(json == "{\"hello\":[\"world\"]}");
+    REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}");
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()),
+                Allocate(sizeofString("abcdefg")),
+                Reallocate(sizeofPool(), sizeofPool(1)),
+            });
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/size.cpp
index b3205a14693350ee6f541c93c5caee6effb2d467..53af226a8143949e30c1b1eb0de9ac66212b62b3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/size.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/size.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonDocument::size()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("returns 0") {
     REQUIRE(doc.size() == 0);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/subscript.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/subscript.cpp
index f4186f85d516ddca04e26c91356c728abf9e9d03..5c7a65d58295daf53e45c3ecda02fbeb130f71c3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/subscript.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/subscript.cpp
@@ -1,12 +1,14 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("JsonDocument::operator[]") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   const JsonDocument& cdoc = doc;
 
   SECTION("object") {
@@ -18,26 +20,40 @@ TEST_CASE("JsonDocument::operator[]") {
     }
 
     SECTION("std::string") {
-      REQUIRE(doc[std::string("hello")] == "world");
-      REQUIRE(cdoc[std::string("hello")] == "world");
+      REQUIRE(doc["hello"_s] == "world");
+      REQUIRE(cdoc["hello"_s] == "world");
+    }
+
+    SECTION("JsonVariant") {
+      doc["key"] = "hello";
+      REQUIRE(doc[doc["key"]] == "world");
+      REQUIRE(cdoc[cdoc["key"]] == "world");
     }
 
     SECTION("supports operator|") {
-      REQUIRE((doc["hello"] | "nope") == std::string("world"));
-      REQUIRE((doc["world"] | "nope") == std::string("nope"));
+      REQUIRE((doc["hello"] | "nope") == "world"_s);
+      REQUIRE((doc["world"] | "nope") == "nope"_s);
     }
   }
 
   SECTION("array") {
     deserializeJson(doc, "[\"hello\",\"world\"]");
 
-    REQUIRE(doc[1] == "world");
-    REQUIRE(cdoc[1] == "world");
+    SECTION("int") {
+      REQUIRE(doc[1] == "world");
+      REQUIRE(cdoc[1] == "world");
+    }
+
+    SECTION("JsonVariant") {
+      doc[2] = 1;
+      REQUIRE(doc[doc[2]] == "world");
+      REQUIRE(cdoc[doc[2]] == "world");
+    }
   }
 }
 
 TEST_CASE("JsonDocument automatically promotes to object") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   doc["one"]["two"]["three"] = 4;
 
@@ -45,7 +61,7 @@ TEST_CASE("JsonDocument automatically promotes to object") {
 }
 
 TEST_CASE("JsonDocument automatically promotes to array") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   doc[2] = 2;
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/swap.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/swap.cpp
index 60d672f889ae82cbf87931b1f0d27f0d93f4dff6..353830896bf62912d401b131f011882288fb9d74 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/swap.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonDocument/swap.cpp
@@ -7,21 +7,19 @@
 using namespace std;
 
 TEST_CASE("std::swap") {
-  SECTION("DynamicJsonDocument*") {
-    DynamicJsonDocument *p1, *p2;
+  SECTION("JsonDocument*") {
+    JsonDocument *p1, *p2;
     swap(p1, p2);  // issue #1678
   }
 
-  SECTION("DynamicJsonDocument") {
-    DynamicJsonDocument doc1(0x10), doc2(0x20);
+  SECTION("JsonDocument") {
+    JsonDocument doc1, doc2;
     doc1.set("hello");
     doc2.set("world");
 
     swap(doc1, doc2);
 
-    CHECK(doc1.capacity() == 0x20);
     CHECK(doc1.as<string>() == "world");
-    CHECK(doc2.capacity() == 0x10);
     CHECK(doc2.as<string>() == "hello");
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt
index 8676811ca0e0d6595190a10f58ae2a8e79609715..dd2dfaa67546ca0591863bd08203dcd50cff9aee 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt
@@ -1,24 +1,21 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(JsonObjectTests
 	clear.cpp
 	compare.cpp
 	containsKey.cpp
-	copy.cpp
-	createNestedArray.cpp
-	createNestedObject.cpp
 	equals.cpp
-	invalid.cpp
 	isNull.cpp
 	iterator.cpp
-	memoryUsage.cpp
 	nesting.cpp
 	remove.cpp
+	set.cpp
 	size.cpp
 	std_string.cpp
 	subscript.cpp
+	unbound.cpp
 )
 
 add_test(JsonObject JsonObjectTests)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/clear.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/clear.cpp
index e112a8dcd730f9a430cd54bb87ef0474a7e47bcc..6673d1cb65ba586384670e9a531659fa625d7ecb 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/clear.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/clear.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -14,7 +14,7 @@ TEST_CASE("JsonObject::clear()") {
   }
 
   SECTION("Removes all elements") {
-    StaticJsonDocument<64> doc;
+    JsonDocument doc;
     JsonObject obj = doc.to<JsonObject>();
     obj["hello"] = 1;
     obj["world"] = 2;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/compare.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/compare.cpp
index 9b747e3bf40936ae14f44083383ea3899c843c24..0c3ff7e28883a4944b55612ed2cdd4b2784d7035 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/compare.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/compare.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("Compare JsonObject with JsonObject") {
-  StaticJsonDocument<512> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonObject object = doc.to<JsonObject>();
@@ -43,12 +43,12 @@ TEST_CASE("Compare JsonObject with JsonObject") {
   }
 
   SECTION("Compare with identical object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello";
     object1["c"][0] = false;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello";
     object2["c"][0] = false;
@@ -62,12 +62,12 @@ TEST_CASE("Compare JsonObject with JsonObject") {
   }
 
   SECTION("Compare with different object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello1";
     object1["c"][0] = false;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello2";
     object2["c"][0] = false;
@@ -82,7 +82,7 @@ TEST_CASE("Compare JsonObject with JsonObject") {
 }
 
 TEST_CASE("Compare JsonObject with JsonVariant") {
-  StaticJsonDocument<512> doc;
+  JsonDocument doc;
 
   SECTION("Compare with self") {
     JsonObject object = doc.to<JsonObject>();
@@ -107,12 +107,12 @@ TEST_CASE("Compare JsonObject with JsonVariant") {
   }
 
   SECTION("Compare with identical object") {
-    JsonObject object = doc.createNestedObject();
+    JsonObject object = doc.add<JsonObject>();
     object["a"] = 1;
     object["b"] = "hello";
     object["c"][0] = false;
 
-    JsonVariant variant = doc.createNestedObject();
+    JsonVariant variant = doc.add<JsonObject>();
     variant["a"] = 1;
     variant["b"] = "hello";
     variant["c"][0] = false;
@@ -133,12 +133,12 @@ TEST_CASE("Compare JsonObject with JsonVariant") {
   }
 
   SECTION("Compare with different object") {
-    JsonObject object = doc.createNestedObject();
+    JsonObject object = doc.add<JsonObject>();
     object["a"] = 1;
     object["b"] = "hello1";
     object["c"][0] = false;
 
-    JsonVariant variant = doc.createNestedObject();
+    JsonVariant variant = doc.add<JsonObject>();
     variant["a"] = 1;
     variant["b"] = "hello2";
     variant["c"][0] = false;
@@ -153,7 +153,7 @@ TEST_CASE("Compare JsonObject with JsonVariant") {
 }
 
 TEST_CASE("Compare JsonObject with JsonVariantConst") {
-  StaticJsonDocument<512> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonObject object = doc.to<JsonObject>();
@@ -199,12 +199,12 @@ TEST_CASE("Compare JsonObject with JsonVariantConst") {
   }
 
   SECTION("Compare with identical object") {
-    JsonObject object = doc.createNestedObject();
+    JsonObject object = doc.add<JsonObject>();
     object["a"] = 1;
     object["b"] = "hello";
     object["c"][0] = false;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello";
     object2["c"][0] = false;
@@ -226,12 +226,12 @@ TEST_CASE("Compare JsonObject with JsonVariantConst") {
   }
 
   SECTION("Compare with different object") {
-    JsonObject object = doc.createNestedObject();
+    JsonObject object = doc.add<JsonObject>();
     object["a"] = 1;
     object["b"] = "hello1";
     object["c"][0] = false;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello2";
     object2["c"][0] = false;
@@ -247,7 +247,7 @@ TEST_CASE("Compare JsonObject with JsonVariantConst") {
 }
 
 TEST_CASE("Compare JsonObject with JsonObjectConst") {
-  StaticJsonDocument<512> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonObject object = doc.to<JsonObject>();
@@ -292,12 +292,12 @@ TEST_CASE("Compare JsonObject with JsonObjectConst") {
   }
 
   SECTION("Compare with identical object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello";
     object1["c"][0] = false;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello";
     object2["c"][0] = false;
@@ -319,12 +319,12 @@ TEST_CASE("Compare JsonObject with JsonObjectConst") {
   }
 
   SECTION("Compare with different object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello1";
     object1["c"][0] = false;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello2";
     object2["c"][0] = false;
@@ -347,7 +347,7 @@ TEST_CASE("Compare JsonObject with JsonObjectConst") {
 }
 
 TEST_CASE("Compare JsonObjectConst with JsonObjectConst") {
-  StaticJsonDocument<512> doc;
+  JsonDocument doc;
 
   SECTION("Compare with unbound") {
     JsonObject object = doc.to<JsonObject>();
@@ -387,13 +387,13 @@ TEST_CASE("Compare JsonObjectConst with JsonObjectConst") {
   }
 
   SECTION("Compare with identical object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello";
     object1["c"][0] = false;
     JsonObjectConst carray1 = object1;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello";
     object2["c"][0] = false;
@@ -408,13 +408,13 @@ TEST_CASE("Compare JsonObjectConst with JsonObjectConst") {
   }
 
   SECTION("Compare with different object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello1";
     object1["c"][0] = false;
     JsonObjectConst carray1 = object1;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello2";
     object2["c"][0] = false;
@@ -430,7 +430,7 @@ TEST_CASE("Compare JsonObjectConst with JsonObjectConst") {
 }
 
 TEST_CASE("Compare JsonObjectConst with JsonVariant") {
-  StaticJsonDocument<512> doc;
+  JsonDocument doc;
 
   SECTION("Compare with self") {
     JsonObject object = doc.to<JsonObject>();
@@ -455,13 +455,13 @@ TEST_CASE("Compare JsonObjectConst with JsonVariant") {
   }
 
   SECTION("Compare with identical object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello";
     object1["c"][0] = false;
     JsonObjectConst carray1 = object1;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello";
     object2["c"][0] = false;
@@ -483,13 +483,13 @@ TEST_CASE("Compare JsonObjectConst with JsonVariant") {
   }
 
   SECTION("Compare with different object") {
-    JsonObject object1 = doc.createNestedObject();
+    JsonObject object1 = doc.add<JsonObject>();
     object1["a"] = 1;
     object1["b"] = "hello1";
     object1["c"][0] = false;
     JsonObjectConst carray1 = object1;
 
-    JsonObject object2 = doc.createNestedObject();
+    JsonObject object2 = doc.add<JsonObject>();
     object2["a"] = 1;
     object2["b"] = "hello2";
     object2["c"][0] = false;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/containsKey.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/containsKey.cpp
index 85335a8890176c9143d5d66671010b9d00912705..d7dd8d1e4b186c83ca221a20e80e5c55d017e774 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/containsKey.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/containsKey.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonObject::containsKey()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
   obj["hello"] = 42;
 
@@ -15,12 +15,6 @@ TEST_CASE("JsonObject::containsKey()") {
     REQUIRE(true == obj.containsKey("hello"));
   }
 
-  SECTION("works with JsonObjectConst") {
-    JsonObjectConst cobj = obj;
-    REQUIRE(false == cobj.containsKey("world"));
-    REQUIRE(true == cobj.containsKey("hello"));
-  }
-
   SECTION("returns false after remove()") {
     obj.remove("hello");
 
@@ -36,4 +30,10 @@ TEST_CASE("JsonObject::containsKey()") {
     REQUIRE(true == obj.containsKey(vla));
   }
 #endif
+
+  SECTION("key is a JsonVariant") {
+    doc["key"] = "hello";
+    REQUIRE(true == obj.containsKey(obj["key"]));
+    REQUIRE(false == obj.containsKey(obj["hello"]));
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/copy.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/copy.cpp
deleted file mode 100644
index a4d5b5fa941e39e330dfb2273e4239f3c38ab416..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/copy.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonObject::set()") {
-  DynamicJsonDocument doc1(4096);
-  DynamicJsonDocument doc2(4096);
-
-  JsonObject obj1 = doc1.to<JsonObject>();
-  JsonObject obj2 = doc2.to<JsonObject>();
-
-  SECTION("doesn't copy static string in key or value") {
-    obj1["hello"] = "world";
-
-    bool success = obj2.set(obj1);
-
-    REQUIRE(success == true);
-    REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
-    REQUIRE(obj2["hello"] == std::string("world"));
-  }
-
-  SECTION("copy local string value") {
-    obj1["hello"] = std::string("world");
-
-    bool success = obj2.set(obj1);
-
-    REQUIRE(success == true);
-    REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
-    REQUIRE(obj2["hello"] == std::string("world"));
-  }
-
-  SECTION("copy local key") {
-    obj1[std::string("hello")] = "world";
-
-    bool success = obj2.set(obj1);
-
-    REQUIRE(success == true);
-    REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
-    REQUIRE(obj2["hello"] == std::string("world"));
-  }
-
-  SECTION("copy string from deserializeJson()") {
-    deserializeJson(doc1, "{'hello':'world'}");
-
-    bool success = obj2.set(obj1);
-
-    REQUIRE(success == true);
-    REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
-    REQUIRE(obj2["hello"] == std::string("world"));
-  }
-
-  SECTION("copy string from deserializeMsgPack()") {
-    deserializeMsgPack(doc1, "\x81\xA5hello\xA5world");
-
-    bool success = obj2.set(obj1);
-
-    REQUIRE(success == true);
-    REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
-    REQUIRE(obj2["hello"] == std::string("world"));
-  }
-
-  SECTION("should work with JsonObjectConst") {
-    obj1["hello"] = "world";
-
-    obj2.set(static_cast<JsonObjectConst>(obj1));
-
-    REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
-    REQUIRE(obj2["hello"] == std::string("world"));
-  }
-
-  SECTION("destination too small to store the key") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc3;
-    JsonObject obj3 = doc3.to<JsonObject>();
-
-    obj1[std::string("hello")] = "world";
-
-    bool success = obj3.set(obj1);
-
-    REQUIRE(success == false);
-    REQUIRE(doc3.as<std::string>() == "{}");
-  }
-
-  SECTION("destination too small to store the value") {
-    StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc3;
-    JsonObject obj3 = doc3.to<JsonObject>();
-
-    obj1["hello"] = std::string("world");
-
-    bool success = obj3.set(obj1);
-
-    REQUIRE(success == false);
-    REQUIRE(doc3.as<std::string>() == "{\"hello\":null}");
-  }
-
-  SECTION("destination is null") {
-    JsonObject null;
-    obj1["hello"] = "world";
-
-    bool success = null.set(obj1);
-
-    REQUIRE(success == false);
-  }
-
-  SECTION("source is null") {
-    JsonObject null;
-    obj1["hello"] = "world";
-
-    bool success = obj1.set(null);
-
-    REQUIRE(success == false);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/createNestedArray.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/createNestedArray.cpp
deleted file mode 100644
index 26c1a86055ade38c39705d2ba9fa20ee615c6a53..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/createNestedArray.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonObject::createNestedArray()") {
-  DynamicJsonDocument doc(4096);
-  JsonObject obj = doc.to<JsonObject>();
-
-  SECTION("key is a const char*") {
-    JsonArray arr = obj.createNestedArray("hello");
-    REQUIRE(arr.isNull() == false);
-  }
-
-#ifdef HAS_VARIABLE_LENGTH_ARRAY
-  SECTION("key is a VLA") {
-    size_t i = 16;
-    char vla[i];
-    strcpy(vla, "hello");
-
-    JsonArray arr = obj.createNestedArray(vla);
-    REQUIRE(arr.isNull() == false);
-  }
-#endif
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/createNestedObject.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/createNestedObject.cpp
deleted file mode 100644
index 9ea067a734a174b407c5dcdf76f7f8b456874ca9..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/createNestedObject.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonObject::createNestedObject()") {
-  DynamicJsonDocument doc(4096);
-  JsonObject obj = doc.to<JsonObject>();
-
-  SECTION("key is a const char*") {
-    obj.createNestedObject("hello");
-  }
-
-#ifdef HAS_VARIABLE_LENGTH_ARRAY
-  SECTION("key is a VLA") {
-    size_t i = 16;
-    char vla[i];
-    strcpy(vla, "hello");
-
-    obj.createNestedObject(vla);
-  }
-#endif
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/equals.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/equals.cpp
index 02d451f96627ed50f05e812fd1da48e2ab5671ec..0ec4673739edf7650571a05d34a59686f0434b51 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/equals.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/equals.cpp
@@ -1,25 +1,22 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonObject::operator==()") {
-  DynamicJsonDocument doc1(4096);
+  JsonDocument doc1;
   JsonObject obj1 = doc1.to<JsonObject>();
-  JsonObjectConst obj1c = obj1;
 
-  DynamicJsonDocument doc2(4096);
+  JsonDocument doc2;
   JsonObject obj2 = doc2.to<JsonObject>();
-  JsonObjectConst obj2c = obj2;
 
   SECTION("should return false when objs differ") {
     obj1["hello"] = "coucou";
     obj2["world"] = 1;
 
     REQUIRE_FALSE(obj1 == obj2);
-    REQUIRE_FALSE(obj1c == obj2c);
   }
 
   SECTION("should return false when LHS has more elements") {
@@ -28,7 +25,6 @@ TEST_CASE("JsonObject::operator==()") {
     obj2["hello"] = "coucou";
 
     REQUIRE_FALSE(obj1 == obj2);
-    REQUIRE_FALSE(obj1c == obj2c);
   }
 
   SECTION("should return false when RKS has more elements") {
@@ -37,7 +33,6 @@ TEST_CASE("JsonObject::operator==()") {
     obj2["world"] = 666;
 
     REQUIRE_FALSE(obj1 == obj2);
-    REQUIRE_FALSE(obj1c == obj2c);
   }
 
   SECTION("should return true when objs equal") {
@@ -48,20 +43,17 @@ TEST_CASE("JsonObject::operator==()") {
     obj2["hello"] = "world";
 
     REQUIRE(obj1 == obj2);
-    REQUIRE(obj1c == obj2c);
   }
 
   SECTION("should return false when RHS is null") {
     JsonObject null;
 
     REQUIRE_FALSE(obj1 == null);
-    REQUIRE_FALSE(obj1c == null);
   }
 
   SECTION("should return false when LHS is null") {
     JsonObject null;
 
     REQUIRE_FALSE(null == obj2);
-    REQUIRE_FALSE(null == obj2c);
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/isNull.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/isNull.cpp
index 9fdff955fb35a31c60e84acba4f8f3fd3e4c4e1e..51f7880a0b48fc9b562dc8d5d6e72c381b80db03 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/isNull.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/isNull.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -12,25 +12,12 @@ TEST_CASE("JsonObject::isNull()") {
   }
 
   SECTION("returns false") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonObject obj = doc.to<JsonObject>();
     REQUIRE(obj.isNull() == false);
   }
 }
 
-TEST_CASE("JsonObjectConst::isNull()") {
-  SECTION("returns true") {
-    JsonObjectConst obj;
-    REQUIRE(obj.isNull() == true);
-  }
-
-  SECTION("returns false") {
-    DynamicJsonDocument doc(4096);
-    JsonObjectConst obj = doc.to<JsonObject>();
-    REQUIRE(obj.isNull() == false);
-  }
-}
-
 TEST_CASE("JsonObject::operator bool()") {
   SECTION("returns false") {
     JsonObject obj;
@@ -38,21 +25,8 @@ TEST_CASE("JsonObject::operator bool()") {
   }
 
   SECTION("returns true") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonObject obj = doc.to<JsonObject>();
     REQUIRE(static_cast<bool>(obj) == true);
   }
 }
-
-TEST_CASE("JsonObjectConst::operator bool()") {
-  SECTION("returns false") {
-    JsonObjectConst obj;
-    REQUIRE(static_cast<bool>(obj) == false);
-  }
-
-  SECTION("returns true") {
-    DynamicJsonDocument doc(4096);
-    JsonObjectConst obj = doc.to<JsonObject>();
-    REQUIRE(static_cast<bool>(obj) == true);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/iterator.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/iterator.cpp
index c48de5f6e3507c5106dcdb0ebdb3677298cc38a6..4918d275828e982d61b7476e6ebc3b9f2b74dd18 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/iterator.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/iterator.cpp
@@ -1,14 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
-using namespace Catch::Matchers;
-
 TEST_CASE("JsonObject::begin()/end()") {
-  StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
   obj["ab"] = 12;
   obj["cd"] = 34;
@@ -36,38 +34,3 @@ TEST_CASE("JsonObject::begin()/end()") {
     REQUIRE(null.begin() == null.end());
   }
 }
-
-TEST_CASE("JsonObjectConst::begin()/end()") {
-  StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
-  JsonObject obj = doc.to<JsonObject>();
-  obj["ab"] = 12;
-  obj["cd"] = 34;
-
-  JsonObjectConst cobj = obj;
-
-  SECTION("Iteration") {
-    JsonObjectConst::iterator it = cobj.begin();
-    REQUIRE(cobj.end() != it);
-    REQUIRE(it->key() == "ab");
-    REQUIRE(12 == it->value());
-
-    ++it;
-    REQUIRE(cobj.end() != it);
-    JsonPairConst pair = *it;
-    REQUIRE(pair.key() == "cd");
-    REQUIRE(34 == pair.value());
-
-    ++it;
-    REQUIRE(cobj.end() == it);
-  }
-
-  SECTION("Dereferencing end() is safe") {
-    REQUIRE(cobj.end()->key().isNull());
-    REQUIRE(cobj.end()->value().isNull());
-  }
-
-  SECTION("null JsonObjectConst") {
-    JsonObjectConst null;
-    REQUIRE(null.begin() == null.end());
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/memoryUsage.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/memoryUsage.cpp
deleted file mode 100644
index 406bcda4fcf7a9680cb1dcc2646060cc0bd914a7..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/memoryUsage.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-#include <string>
-
-TEST_CASE("JsonObject::memoryUsage()") {
-  DynamicJsonDocument doc(4096);
-  JsonObject obj = doc.to<JsonObject>();
-
-  SECTION("return 0 if uninitialized") {
-    JsonObject unitialized;
-    REQUIRE(unitialized.memoryUsage() == 0);
-  }
-
-  SECTION("JSON_OBJECT_SIZE(0) for empty object") {
-    REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(0));
-  }
-
-  SECTION("JSON_OBJECT_SIZE(1) after add") {
-    obj["hello"] = 42;
-    REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(1));
-  }
-
-  SECTION("includes the size of the key") {
-    obj[std::string("hello")] = 42;
-    REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(1) + 6);
-  }
-
-  SECTION("includes the size of the nested array") {
-    JsonArray nested = obj.createNestedArray("nested");
-    nested.add(42);
-    REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(1));
-  }
-
-  SECTION("includes the size of the nested object") {
-    JsonObject nested = obj.createNestedObject("nested");
-    nested["hello"] = "world";
-    REQUIRE(obj.memoryUsage() == 2 * JSON_OBJECT_SIZE(1));
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/nesting.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/nesting.cpp
index 2ba98adf6aad0e984b8f1520a9310c4de7e9ed41..39c26fa5d57ce931160816100038d8264b9f779a 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/nesting.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/nesting.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonObject::nesting()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
 
   SECTION("return 0 if uninitialized") {
@@ -24,12 +24,12 @@ TEST_CASE("JsonObject::nesting()") {
   }
 
   SECTION("returns 2 with nested array") {
-    obj.createNestedArray("nested");
+    obj["nested"].to<JsonArray>();
     REQUIRE(obj.nesting() == 2);
   }
 
   SECTION("returns 2 with nested object") {
-    obj.createNestedObject("nested");
+    obj["nested"].to<JsonObject>();
     REQUIRE(obj.nesting() == 2);
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/remove.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/remove.cpp
index 1dbd4bd53dd474fec81150d392b5e91c8722c65a..4c3e35d7d7775266705a4fba92e4a0ac85981fe4 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/remove.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/remove.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,7 +7,7 @@
 #include <string>
 
 TEST_CASE("JsonObject::remove()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
   obj["a"] = 0;
   obj["b"] = 1;
@@ -51,7 +51,8 @@ TEST_CASE("JsonObject::remove()") {
     }
 
     SECTION("Remove last") {
-      it += 2;
+      ++it;
+      ++it;
       obj.remove(it);
       serializeJson(obj, result);
       REQUIRE("{\"a\":0,\"b\":1}" == result);
@@ -79,4 +80,10 @@ TEST_CASE("JsonObject::remove()") {
     JsonObject unboundObject;
     unboundObject.remove(unboundObject.begin());
   }
+
+  SECTION("remove(JsonVariant)") {
+    obj["key"] = "b";
+    obj.remove(obj["key"]);
+    REQUIRE("{\"a\":0,\"c\":2,\"key\":\"b\"}" == doc.as<std::string>());
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/set.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/set.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f80f8a8239b9b67dbc0bf120297e4d23f7e4b767
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/set.cpp
@@ -0,0 +1,142 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+TEST_CASE("JsonObject::set()") {
+  SpyingAllocator spy;
+  JsonDocument doc1(&spy);
+  JsonDocument doc2(&spy);
+
+  JsonObject obj1 = doc1.to<JsonObject>();
+  JsonObject obj2 = doc2.to<JsonObject>();
+
+  SECTION("doesn't copy static string in key or value") {
+    obj1["hello"] = "world";
+    spy.clearLog();
+
+    bool success = obj2.set(obj1);
+
+    REQUIRE(success == true);
+    REQUIRE(obj2["hello"] == "world"_s);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                         });
+  }
+
+  SECTION("copy local string value") {
+    obj1["hello"] = "world"_s;
+    spy.clearLog();
+
+    bool success = obj2.set(obj1);
+
+    REQUIRE(success == true);
+    REQUIRE(obj2["hello"] == "world"_s);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("copy local key") {
+    obj1["hello"_s] = "world";
+    spy.clearLog();
+
+    bool success = obj2.set(obj1);
+
+    REQUIRE(success == true);
+    REQUIRE(obj2["hello"] == "world"_s);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                         });
+  }
+
+  SECTION("copy string from deserializeJson()") {
+    deserializeJson(doc1, "{'hello':'world'}");
+    spy.clearLog();
+
+    bool success = obj2.set(obj1);
+
+    REQUIRE(success == true);
+    REQUIRE(obj2["hello"] == "world"_s);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("copy string from deserializeMsgPack()") {
+    deserializeMsgPack(doc1, "\x81\xA5hello\xA5world");
+    spy.clearLog();
+
+    bool success = obj2.set(obj1);
+
+    REQUIRE(success == true);
+    REQUIRE(obj2["hello"] == "world"_s);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("should work with JsonObjectConst") {
+    obj1["hello"] = "world";
+
+    obj2.set(static_cast<JsonObjectConst>(obj1));
+
+    REQUIRE(obj2["hello"] == "world"_s);
+  }
+
+  SECTION("copy fails in the middle of an object") {
+    TimebombAllocator timebomb(2);
+    JsonDocument doc3(&timebomb);
+    JsonObject obj3 = doc3.to<JsonObject>();
+
+    obj1["a"_s] = 1;
+    obj1["b"_s] = 2;
+
+    bool success = obj3.set(obj1);
+
+    REQUIRE(success == false);
+    REQUIRE(doc3.as<std::string>() == "{\"a\":1}");
+  }
+
+  SECTION("copy fails in the middle of an array") {
+    TimebombAllocator timebomb(1);
+    JsonDocument doc3(&timebomb);
+    JsonObject obj3 = doc3.to<JsonObject>();
+
+    obj1["hello"][0] = "world"_s;
+
+    bool success = obj3.set(obj1);
+
+    REQUIRE(success == false);
+    REQUIRE(doc3.as<std::string>() == "{\"hello\":[]}");
+  }
+
+  SECTION("destination is null") {
+    JsonObject null;
+    obj1["hello"] = "world";
+
+    bool success = null.set(obj1);
+
+    REQUIRE(success == false);
+  }
+
+  SECTION("source is null") {
+    JsonObject null;
+    obj1["hello"] = "world";
+
+    bool success = obj1.set(null);
+
+    REQUIRE(success == false);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/size.cpp
index d9c7bb78569d0167d209e32d9f3ae291dcf10e7f..5f4864551c9155c2b031a9cba47b2a23d985965a 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/size.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/size.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,7 +7,7 @@
 #include <string>
 
 TEST_CASE("JsonObject::size()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
 
   SECTION("initial size is zero") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/std_string.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/std_string.cpp
index 8a93d3f08b0ba76731f24c22abf0b052386a245b..613e0bdc0d75c9ad7981f895eb07bffcd5b21909 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/std_string.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/std_string.cpp
@@ -1,10 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 static void eraseString(std::string& str) {
   char* p = const_cast<char*>(str.c_str());
   while (*p)
@@ -12,7 +14,7 @@ static void eraseString(std::string& str) {
 }
 
 TEST_CASE("std::string") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("operator[]") {
     char json[] = "{\"key\":\"value\"}";
@@ -20,7 +22,7 @@ TEST_CASE("std::string") {
     deserializeJson(doc, json);
     JsonObject obj = doc.as<JsonObject>();
 
-    REQUIRE(std::string("value") == obj[std::string("key")]);
+    REQUIRE("value"_s == obj["key"_s]);
   }
 
   SECTION("operator[] const") {
@@ -29,41 +31,21 @@ TEST_CASE("std::string") {
     deserializeJson(doc, json);
     JsonObject obj = doc.as<JsonObject>();
 
-    REQUIRE(std::string("value") == obj[std::string("key")]);
-  }
-
-  SECTION("createNestedObject()") {
-    JsonObject obj = doc.to<JsonObject>();
-    std::string key = "key";
-    char json[64];
-    obj.createNestedObject(key);
-    eraseString(key);
-    serializeJson(doc, json, sizeof(json));
-    REQUIRE(std::string("{\"key\":{}}") == json);
-  }
-
-  SECTION("createNestedArray()") {
-    JsonObject obj = doc.to<JsonObject>();
-    std::string key = "key";
-    char json[64];
-    obj.createNestedArray(key);
-    eraseString(key);
-    serializeJson(doc, json, sizeof(json));
-    REQUIRE(std::string("{\"key\":[]}") == json);
+    REQUIRE("value"_s == obj["key"_s]);
   }
 
   SECTION("containsKey()") {
     char json[] = "{\"key\":\"value\"}";
     deserializeJson(doc, json);
     JsonObject obj = doc.as<JsonObject>();
-    REQUIRE(true == obj.containsKey(std::string("key")));
+    REQUIRE(true == obj.containsKey("key"_s));
   }
 
   SECTION("remove()") {
     JsonObject obj = doc.to<JsonObject>();
     obj["key"] = "value";
 
-    obj.remove(std::string("key"));
+    obj.remove("key"_s);
 
     REQUIRE(0 == obj.size());
   }
@@ -73,7 +55,7 @@ TEST_CASE("std::string") {
     JsonObject obj = doc.to<JsonObject>();
     obj[key] = "world";
     eraseString(key);
-    REQUIRE(std::string("world") == obj["hello"]);
+    REQUIRE("world"_s == obj["hello"]);
   }
 
   SECTION("operator[], set value") {
@@ -81,30 +63,6 @@ TEST_CASE("std::string") {
     JsonObject obj = doc.to<JsonObject>();
     obj["hello"] = value;
     eraseString(value);
-    REQUIRE(std::string("world") == obj["hello"]);
-  }
-
-  SECTION("memoryUsage() increases when adding a new key") {
-    std::string key1("hello"), key2("world");
-    JsonObject obj = doc.to<JsonObject>();
-
-    obj[key1] = 1;
-    size_t sizeBefore = doc.memoryUsage();
-    obj[key2] = 2;
-    size_t sizeAfter = doc.memoryUsage();
-
-    REQUIRE(sizeAfter - sizeBefore >= key2.size());
-  }
-
-  SECTION("memoryUsage() remains when adding the same key") {
-    std::string key("hello");
-    JsonObject obj = doc.to<JsonObject>();
-
-    obj[key] = 1;
-    size_t sizeBefore = doc.memoryUsage();
-    obj[key] = 2;
-    size_t sizeAfter = doc.memoryUsage();
-
-    REQUIRE(sizeBefore == sizeAfter);
+    REQUIRE("world"_s == obj["hello"]);
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/subscript.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/subscript.cpp
index afc567a6e2dc3e19fabf99ff116059ab1870a850..775d149f89f61d6e9d66aa61dcd80d9b22ec3af4 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/subscript.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/subscript.cpp
@@ -1,12 +1,16 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
 TEST_CASE("JsonObject::operator[]") {
-  DynamicJsonDocument doc(4096);
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
   JsonObject obj = doc.to<JsonObject>();
 
   SECTION("int") {
@@ -47,11 +51,11 @@ TEST_CASE("JsonObject::operator[]") {
 
     REQUIRE(true == obj["hello"].is<const char*>());
     REQUIRE(false == obj["hello"].is<long>());
-    REQUIRE(std::string("h3110") == obj["hello"].as<const char*>());
+    REQUIRE("h3110"_s == obj["hello"].as<const char*>());
   }
 
   SECTION("array") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonArray arr = doc2.to<JsonArray>();
 
     obj["hello"] = arr;
@@ -62,7 +66,7 @@ TEST_CASE("JsonObject::operator[]") {
   }
 
   SECTION("object") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonObject obj2 = doc2.to<JsonObject>();
 
     obj["hello"] = obj2;
@@ -73,7 +77,7 @@ TEST_CASE("JsonObject::operator[]") {
   }
 
   SECTION("array subscript") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonArray arr = doc2.to<JsonArray>();
     arr.add(42);
 
@@ -83,7 +87,7 @@ TEST_CASE("JsonObject::operator[]") {
   }
 
   SECTION("object subscript") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonObject obj2 = doc2.to<JsonObject>();
     obj2["x"] = 42;
 
@@ -100,56 +104,72 @@ TEST_CASE("JsonObject::operator[]") {
 
   SECTION("should not duplicate const char*") {
     obj["hello"] = "world";
-    const size_t expectedSize = JSON_OBJECT_SIZE(1);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{Allocate(sizeofPool())});
   }
 
   SECTION("should duplicate char* value") {
     obj["hello"] = const_cast<char*>("world");
-    const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
   SECTION("should duplicate char* key") {
     obj[const_cast<char*>("hello")] = "world";
-    const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                         });
   }
 
   SECTION("should duplicate char* key&value") {
     obj[const_cast<char*>("hello")] = const_cast<char*>("world");
-    const size_t expectedSize = JSON_OBJECT_SIZE(1) + 2 * JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize <= doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
   SECTION("should duplicate std::string value") {
-    obj["hello"] = std::string("world");
-    const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    obj["hello"] = "world"_s;
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
   SECTION("should duplicate std::string key") {
-    obj[std::string("hello")] = "world";
-    const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    obj["hello"_s] = "world";
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                         });
   }
 
   SECTION("should duplicate std::string key&value") {
-    obj[std::string("hello")] = std::string("world");
-    const size_t expectedSize = JSON_OBJECT_SIZE(1) + 2 * JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize <= doc.memoryUsage());
+    obj["hello"_s] = "world"_s;
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("world")),
+                         });
   }
 
   SECTION("should duplicate a non-static JsonString key") {
     obj[JsonString("hello", JsonString::Copied)] = "world";
-    const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofPool()),
+                         });
   }
 
   SECTION("should not duplicate a static JsonString key") {
     obj[JsonString("hello", JsonString::Linked)] = "world";
-    const size_t expectedSize = JSON_OBJECT_SIZE(1);
-    REQUIRE(expectedSize == doc.memoryUsage());
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                         });
   }
 
   SECTION("should ignore null key") {
@@ -178,7 +198,7 @@ TEST_CASE("JsonObject::operator[]") {
 
     obj[vla] = "world";
 
-    REQUIRE(std::string("world") == obj["hello"]);
+    REQUIRE("world"_s == obj["hello"]);
   }
 
   SECTION("obj[str] = VLA") {  // issue #416
@@ -188,7 +208,7 @@ TEST_CASE("JsonObject::operator[]") {
 
     obj["hello"] = vla;
 
-    REQUIRE(std::string("world") == obj["hello"].as<const char*>());
+    REQUIRE("world"_s == obj["hello"].as<const char*>());
   }
 
   SECTION("obj.set(VLA, str)") {
@@ -198,7 +218,7 @@ TEST_CASE("JsonObject::operator[]") {
 
     obj[vla] = "world";
 
-    REQUIRE(std::string("world") == obj["hello"]);
+    REQUIRE("world"_s == obj["hello"]);
   }
 
   SECTION("obj.set(str, VLA)") {
@@ -208,7 +228,7 @@ TEST_CASE("JsonObject::operator[]") {
 
     obj["hello"].set(vla);
 
-    REQUIRE(std::string("world") == obj["hello"].as<const char*>());
+    REQUIRE("world"_s == obj["hello"].as<const char*>());
   }
 
   SECTION("obj[VLA]") {
@@ -219,15 +239,23 @@ TEST_CASE("JsonObject::operator[]") {
     deserializeJson(doc, "{\"hello\":\"world\"}");
 
     obj = doc.as<JsonObject>();
-    REQUIRE(std::string("world") == obj[vla]);
+    REQUIRE("world"_s == obj[vla]);
   }
 #endif
 
   SECTION("chain") {
-    obj.createNestedObject("hello")["world"] = 123;
+    obj["hello"]["world"] = 123;
 
     REQUIRE(123 == obj["hello"]["world"].as<int>());
     REQUIRE(true == obj["hello"]["world"].is<int>());
     REQUIRE(false == obj["hello"]["world"].is<bool>());
   }
+
+  SECTION("JsonVariant") {
+    obj["hello"] = "world";
+    doc["key"] = "hello";
+
+    REQUIRE(obj[obj["key"]] == "world");
+    REQUIRE(obj[obj["foo"]] == nullptr);
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/invalid.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/unbound.cpp
similarity index 50%
rename from neosensor/libraries/ArduinoJson/extras/tests/JsonObject/invalid.cpp
rename to neosensor/libraries/ArduinoJson/extras/tests/JsonObject/unbound.cpp
index 0695b4814ab99829df6fb0f1d161ffb73e943b4b..cb2b6878168a173031acc63df7c718df1f6be430 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/invalid.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObject/unbound.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,27 +7,19 @@
 
 using namespace Catch::Matchers;
 
-TEST_CASE("JsonObject::invalid()") {
+TEST_CASE("Unbound JsonObject") {
   JsonObject obj;
 
-  SECTION("SubscriptFails") {
+  SECTION("retrieve member") {
     REQUIRE(obj["key"].isNull());
   }
 
-  SECTION("AddFails") {
+  SECTION("add member") {
     obj["hello"] = "world";
     REQUIRE(0 == obj.size());
   }
 
-  SECTION("CreateNestedArrayFails") {
-    REQUIRE(obj.createNestedArray("hello").isNull());
-  }
-
-  SECTION("CreateNestedObjectFails") {
-    REQUIRE(obj.createNestedObject("world").isNull());
-  }
-
-  SECTION("serialize to 'null'") {
+  SECTION("serialize") {
     char buffer[32];
     serializeJson(obj, buffer, sizeof(buffer));
     REQUIRE_THAT(buffer, Equals("null"));
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dadb910e6ccf3d928eaab4e769c0bc1d9f846458
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/CMakeLists.txt
@@ -0,0 +1,20 @@
+# ArduinoJson - https://arduinojson.org
+# Copyright © 2014-2024, Benoit BLANCHON
+# MIT License
+
+add_executable(JsonObjectConstTests
+	containsKey.cpp
+	equals.cpp
+	isNull.cpp
+	iterator.cpp
+	nesting.cpp
+	size.cpp
+	subscript.cpp
+)
+
+add_test(JsonObjectConst JsonObjectConstTests)
+
+set_tests_properties(JsonObjectConst
+	PROPERTIES
+		LABELS "Catch"
+)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/containsKey.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/containsKey.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3283c08d5edbfaaf8ea4b323527395df4210a1ab
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/containsKey.cpp
@@ -0,0 +1,40 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include "Literals.hpp"
+
+TEST_CASE("JsonObjectConst::containsKey()") {
+  JsonDocument doc;
+  doc["hello"] = 42;
+  auto obj = doc.as<JsonObjectConst>();
+
+  SECTION("supports const char*") {
+    REQUIRE(false == obj.containsKey("world"));
+    REQUIRE(true == obj.containsKey("hello"));
+  }
+
+  SECTION("supports std::string") {
+    REQUIRE(false == obj.containsKey("world"_s));
+    REQUIRE(true == obj.containsKey("hello"_s));
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("supports VLA") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "hello");
+
+    REQUIRE(true == obj.containsKey(vla));
+  }
+#endif
+
+  SECTION("supports JsonVariant") {
+    doc["key"] = "hello";
+    REQUIRE(true == obj.containsKey(obj["key"]));
+    REQUIRE(false == obj.containsKey(obj["hello"]));
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/equals.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/equals.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0efa3cf970768af2b31a42696a8a12c127082b6c
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/equals.cpp
@@ -0,0 +1,65 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonObjectConst::operator==()") {
+  JsonDocument doc1;
+  JsonObjectConst obj1 = doc1.to<JsonObject>();
+
+  JsonDocument doc2;
+  JsonObjectConst obj2 = doc2.to<JsonObject>();
+
+  SECTION("should return false when objs differ") {
+    doc1["hello"] = "coucou";
+    doc2["world"] = 1;
+
+    REQUIRE_FALSE(obj1 == obj2);
+  }
+
+  SECTION("should return false when LHS has more elements") {
+    doc1["hello"] = "coucou";
+    doc1["world"] = 666;
+    doc2["hello"] = "coucou";
+
+    REQUIRE_FALSE(obj1 == obj2);
+  }
+
+  SECTION("should return false when RKS has more elements") {
+    doc1["hello"] = "coucou";
+    doc2["hello"] = "coucou";
+    doc2["world"] = 666;
+
+    REQUIRE_FALSE(obj1 == obj2);
+  }
+
+  SECTION("should return true when objs equal") {
+    doc1["hello"] = "world";
+    doc1["anwser"] = 42;
+    // insert in different order
+    doc2["anwser"] = 42;
+    doc2["hello"] = "world";
+
+    REQUIRE(obj1 == obj2);
+  }
+
+  SECTION("should return false when RHS is null") {
+    JsonObjectConst null;
+
+    REQUIRE_FALSE(obj1 == null);
+  }
+
+  SECTION("should return false when LHS is null") {
+    JsonObjectConst null;
+
+    REQUIRE_FALSE(null == obj2);
+  }
+
+  SECTION("should return true when both are null") {
+    JsonObjectConst null1, null2;
+
+    REQUIRE(null1 == null2);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/isNull.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/isNull.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c689cce55722fd6f299cf88c1d4a89fd617e8184
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/isNull.cpp
@@ -0,0 +1,32 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonObjectConst::isNull()") {
+  SECTION("returns true") {
+    JsonObjectConst obj;
+    REQUIRE(obj.isNull() == true);
+  }
+
+  SECTION("returns false") {
+    JsonDocument doc;
+    JsonObjectConst obj = doc.to<JsonObject>();
+    REQUIRE(obj.isNull() == false);
+  }
+}
+
+TEST_CASE("JsonObjectConst::operator bool()") {
+  SECTION("returns false") {
+    JsonObjectConst obj;
+    REQUIRE(static_cast<bool>(obj) == false);
+  }
+
+  SECTION("returns true") {
+    JsonDocument doc;
+    JsonObjectConst obj = doc.to<JsonObject>();
+    REQUIRE(static_cast<bool>(obj) == true);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/iterator.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/iterator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0ad911ef530bd6f9fc37966c02619efaa4cdaa1c
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/iterator.cpp
@@ -0,0 +1,39 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonObjectConst::begin()/end()") {
+  JsonDocument doc;
+  JsonObjectConst obj = doc.to<JsonObject>();
+  doc["ab"] = 12;
+  doc["cd"] = 34;
+
+  SECTION("Iteration") {
+    JsonObjectConst::iterator it = obj.begin();
+    REQUIRE(obj.end() != it);
+    REQUIRE(it->key() == "ab");
+    REQUIRE(12 == it->value());
+
+    ++it;
+    REQUIRE(obj.end() != it);
+    JsonPairConst pair = *it;
+    REQUIRE(pair.key() == "cd");
+    REQUIRE(34 == pair.value());
+
+    ++it;
+    REQUIRE(obj.end() == it);
+  }
+
+  SECTION("Dereferencing end() is safe") {
+    REQUIRE(obj.end()->key().isNull());
+    REQUIRE(obj.end()->value().isNull());
+  }
+
+  SECTION("null JsonObjectConst") {
+    JsonObjectConst null;
+    REQUIRE(null.begin() == null.end());
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/nesting.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/nesting.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7bddd8f55cd60b49a616f1ed0b2f8966f6e98472
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/nesting.cpp
@@ -0,0 +1,35 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonObjectConst::nesting()") {
+  JsonDocument doc;
+  JsonObjectConst obj = doc.to<JsonObject>();
+
+  SECTION("return 0 if unbound") {
+    JsonObjectConst unbound;
+    REQUIRE(unbound.nesting() == 0);
+  }
+
+  SECTION("returns 1 for empty object") {
+    REQUIRE(obj.nesting() == 1);
+  }
+
+  SECTION("returns 1 for flat object") {
+    doc["hello"] = "world";
+    REQUIRE(obj.nesting() == 1);
+  }
+
+  SECTION("returns 2 with nested array") {
+    doc["nested"].to<JsonArray>();
+    REQUIRE(obj.nesting() == 2);
+  }
+
+  SECTION("returns 2 with nested object") {
+    doc["nested"].to<JsonObject>();
+    REQUIRE(obj.nesting() == 2);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/size.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..25de11a416d4f14a82bd62b226ff9a9c05ea21f8
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/size.cpp
@@ -0,0 +1,22 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+#include <string>
+
+TEST_CASE("JsonObjectConst::size()") {
+  JsonDocument doc;
+  JsonObjectConst obj = doc.to<JsonObject>();
+
+  SECTION("returns 0 when empty") {
+    REQUIRE(0 == obj.size());
+  }
+
+  SECTION("returns the number of members") {
+    doc["hello"] = 1;
+    doc["world"] = 2;
+    REQUIRE(2 == obj.size());
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/subscript.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/subscript.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..12f080c1919392611b4b0ae5c8d7b8765561ed44
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonObjectConst/subscript.cpp
@@ -0,0 +1,40 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+TEST_CASE("JsonObjectConst::operator[]") {
+  JsonDocument doc;
+  doc["hello"] = "world";
+  JsonObjectConst obj = doc.as<JsonObjectConst>();
+
+  SECTION("supports const char*") {
+    REQUIRE(obj["hello"] == "world");  // issue #2019
+  }
+
+  SECTION("supports std::string") {
+    REQUIRE(obj["hello"_s] == "world");  // issue #2019
+  }
+
+#if defined(HAS_VARIABLE_LENGTH_ARRAY) && \
+    !defined(SUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR)
+  SECTION("supports VLA") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "hello");
+
+    REQUIRE("world"_s == obj[vla]);
+  }
+#endif
+
+  SECTION("supports JsonVariant") {
+    doc["key"] = "hello";
+    REQUIRE(obj[obj["key"]] == "world");
+    REQUIRE(obj[obj["foo"]] == nullptr);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt
index 0ec4937fb2ee70aec6977aba5d9d60e8585f7c4b..8a6b19fec13d514a68b81ad8cd9156210cbf7201 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(JsonSerializerTests
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp
index a84657a72f8391487d11a4c55f67bbb0439de127..002aa57bb3864fe1e4d7ad5d25b5cc906a94c629 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -30,7 +30,7 @@ class CustomWriter {
 };
 
 TEST_CASE("CustomWriter") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
   array.add(4);
   array.add(2);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp
index 1fad69ddc715c503f145d40b5c371a57ae3a7db9..b4bf8385fe96e61e32c58882ba26f771653511d5 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -15,7 +15,7 @@ static void check(JsonArray array, std::string expected) {
 }
 
 TEST_CASE("serializeJson(JsonArray)") {
-  StaticJsonDocument<JSON_ARRAY_SIZE(2)> doc;
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
 
   SECTION("Empty") {
@@ -41,14 +41,6 @@ TEST_CASE("serializeJson(JsonArray)") {
     check(array, "[\"hello\",\"world\"]");
   }
 
-  SECTION("OneStringOverCapacity") {
-    array.add("hello");
-    array.add("world");
-    array.add("lost");
-
-    check(array, "[\"hello\",\"world\"]");
-  }
-
   SECTION("One double") {
     array.add(3.1415927);
     check(array, "[3.1415927]");
@@ -80,14 +72,6 @@ TEST_CASE("serializeJson(JsonArray)") {
     check(array, "[{\"key\":\"value\"}]");
   }
 
-  SECTION("OneIntegerOverCapacity") {
-    array.add(1);
-    array.add(2);
-    array.add(3);
-
-    check(array, "[1,2]");
-  }
-
   SECTION("OneTrue") {
     array.add(true);
 
@@ -107,22 +91,14 @@ TEST_CASE("serializeJson(JsonArray)") {
     check(array, "[false,true]");
   }
 
-  SECTION("OneBooleanOverCapacity") {
-    array.add(false);
-    array.add(true);
-    array.add(false);
-
-    check(array, "[false,true]");
-  }
-
   SECTION("OneEmptyNestedArray") {
-    array.createNestedArray();
+    array.add<JsonArray>();
 
     check(array, "[[]]");
   }
 
   SECTION("OneEmptyNestedHash") {
-    array.createNestedObject();
+    array.add<JsonObject>();
 
     check(array, "[{}]");
   }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp
index 9abeb0151411ab8b7c8aebd124ce229719f3c610..ede368c4a6098537dcce70721137536b635fe324 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -15,7 +15,7 @@ static void checkArray(JsonArray array, std::string expected) {
 }
 
 TEST_CASE("serializeJsonPretty(JsonArray)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
 
   SECTION("Empty") {
@@ -43,8 +43,8 @@ TEST_CASE("serializeJsonPretty(JsonArray)") {
   }
 
   SECTION("EmptyNestedArrays") {
-    array.createNestedArray();
-    array.createNestedArray();
+    array.add<JsonArray>();
+    array.add<JsonArray>();
 
     checkArray(array,
                "[\r\n"
@@ -54,11 +54,11 @@ TEST_CASE("serializeJsonPretty(JsonArray)") {
   }
 
   SECTION("NestedArrays") {
-    JsonArray nested1 = array.createNestedArray();
+    JsonArray nested1 = array.add<JsonArray>();
     nested1.add(1);
     nested1.add(2);
 
-    JsonObject nested2 = array.createNestedObject();
+    JsonObject nested2 = array.add<JsonObject>();
     nested2["key"] = 3;
 
     checkArray(array,
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp
index 9ba21141d46595deb8413239e6f6eed50ae3859b..70788864a5249322bc3d26616b548f52d61314bf 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -20,7 +20,7 @@ static void checkObject(const JsonObject obj, const std::string& expected) {
 }
 
 TEST_CASE("serializeJson(JsonObject)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
 
   SECTION("EmptyObject") {
@@ -96,10 +96,10 @@ TEST_CASE("serializeJson(JsonObject)") {
   }
 
   SECTION("ThreeNestedArrays") {
-    DynamicJsonDocument b(4096);
-    DynamicJsonDocument c(4096);
+    JsonDocument b;
+    JsonDocument c;
 
-    obj.createNestedArray("a");
+    obj["a"].to<JsonArray>();
     obj["b"] = b.to<JsonArray>();
     obj["c"] = c.to<JsonArray>();
 
@@ -107,10 +107,10 @@ TEST_CASE("serializeJson(JsonObject)") {
   }
 
   SECTION("ThreeNestedObjects") {
-    DynamicJsonDocument b(4096);
-    DynamicJsonDocument c(4096);
+    JsonDocument b;
+    JsonDocument c;
 
-    obj.createNestedObject("a");
+    obj["a"].to<JsonObject>();
     obj["b"] = b.to<JsonObject>();
     obj["c"] = c.to<JsonObject>();
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp
index 4e8d113e22161c53d9972cb7388c153f4c2a984e..01d7377201587908ae483d55cb226691027d5d4d 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -19,7 +19,7 @@ static void checkObjectPretty(const JsonObject obj,
 }
 
 TEST_CASE("serializeJsonPretty(JsonObject)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
 
   SECTION("EmptyObject") {
@@ -47,8 +47,8 @@ TEST_CASE("serializeJsonPretty(JsonObject)") {
   }
 
   SECTION("EmptyNestedContainers") {
-    obj.createNestedObject("key1");
-    obj.createNestedArray("key2");
+    obj["key1"].to<JsonObject>();
+    obj["key2"].to<JsonArray>();
 
     checkObjectPretty(obj,
                       "{\r\n"
@@ -58,10 +58,10 @@ TEST_CASE("serializeJsonPretty(JsonObject)") {
   }
 
   SECTION("NestedContainers") {
-    JsonObject nested1 = obj.createNestedObject("key1");
+    JsonObject nested1 = obj["key1"].to<JsonObject>();
     nested1["a"] = 1;
 
-    JsonArray nested2 = obj.createNestedArray("key2");
+    JsonArray nested2 = obj["key2"].to<JsonArray>();
     nested2.add(2);
 
     checkObjectPretty(obj,
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp
index 81553d315d9b55b0880f0880dccaecb1abdb2a71..800c3a92fbe996bf772dc0cb95d13d959ef4fded 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp
@@ -1,14 +1,16 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 #include <limits>
 
+#include "Literals.hpp"
+
 template <typename T>
 void check(T value, const std::string& expected) {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   doc.to<JsonVariant>().set(value);
   char buffer[256] = "";
   size_t returnValue = serializeJson(doc, buffer, sizeof(buffer));
@@ -30,42 +32,42 @@ TEST_CASE("serializeJson(JsonVariant)") {
   }
 
   SECTION("string") {
-    check(std::string("hello"), "\"hello\"");
+    check("hello"_s, "\"hello\"");
 
     SECTION("Escape quotation mark") {
-      check(std::string("hello \"world\""), "\"hello \\\"world\\\"\"");
+      check("hello \"world\""_s, "\"hello \\\"world\\\"\"");
     }
 
     SECTION("Escape reverse solidus") {
-      check(std::string("hello\\world"), "\"hello\\\\world\"");
+      check("hello\\world"_s, "\"hello\\\\world\"");
     }
 
     SECTION("Don't escape solidus") {
-      check(std::string("fifty/fifty"), "\"fifty/fifty\"");
+      check("fifty/fifty"_s, "\"fifty/fifty\"");
     }
 
     SECTION("Escape backspace") {
-      check(std::string("hello\bworld"), "\"hello\\bworld\"");
+      check("hello\bworld"_s, "\"hello\\bworld\"");
     }
 
     SECTION("Escape formfeed") {
-      check(std::string("hello\fworld"), "\"hello\\fworld\"");
+      check("hello\fworld"_s, "\"hello\\fworld\"");
     }
 
     SECTION("Escape linefeed") {
-      check(std::string("hello\nworld"), "\"hello\\nworld\"");
+      check("hello\nworld"_s, "\"hello\\nworld\"");
     }
 
     SECTION("Escape carriage return") {
-      check(std::string("hello\rworld"), "\"hello\\rworld\"");
+      check("hello\rworld"_s, "\"hello\\rworld\"");
     }
 
     SECTION("Escape tab") {
-      check(std::string("hello\tworld"), "\"hello\\tworld\"");
+      check("hello\tworld"_s, "\"hello\\tworld\"");
     }
 
     SECTION("NUL char") {
-      check(std::string("hello\0world", 11), "\"hello\\u0000world\"");
+      check("hello\0world"_s, "\"hello\\u0000world\"");
     }
   }
 
@@ -74,7 +76,7 @@ TEST_CASE("serializeJson(JsonVariant)") {
   }
 
   SECTION("SerializedValue<std::string>") {
-    check(serialized(std::string("[1,2]")), "[1,2]");
+    check(serialized("[1,2]"_s), "[1,2]");
   }
 
   SECTION("Double") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/misc.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/misc.cpp
index 59c09ebacfa4d122f06cd0ee9554de44a32e1f07..46d7da5a5b6706a213904d6157bdd975d4b869e0 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/misc.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/misc.cpp
@@ -3,7 +3,7 @@
 #include <limits>
 
 TEST_CASE("serializeJson(MemberProxy)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   deserializeJson(doc, "{\"hello\":42}");
   JsonObject obj = doc.as<JsonObject>();
   std::string result;
@@ -14,7 +14,7 @@ TEST_CASE("serializeJson(MemberProxy)") {
 }
 
 TEST_CASE("serializeJson(ElementProxy)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   deserializeJson(doc, "[42]");
   JsonArray arr = doc.as<JsonArray>();
   std::string result;
@@ -25,7 +25,7 @@ TEST_CASE("serializeJson(ElementProxy)") {
 }
 
 TEST_CASE("serializeJson(JsonVariantSubscript)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   deserializeJson(doc, "[42]");
   JsonVariant var = doc.as<JsonVariant>();
   std::string result;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp
index 411f4792428e290f9dd480b2aea00255caf74d33..5fd89b99a98e85a2fd33e8aacb9b0c24cff6e894 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,7 +7,7 @@
 #include <sstream>
 
 TEST_CASE("operator<<(std::ostream)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   std::ostringstream os;
 
   SECTION("JsonVariant containing false") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp
index af0d8096ed03bd4267362ec34d8c2f89129fe76a..33b8f50e01dfe7a0542fe8e4d1ee1692309a134b 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp
@@ -1,25 +1,27 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("serialize JsonArray to std::string") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
   array.add(4);
   array.add(2);
 
   SECTION("serializeJson()") {
-    std::string json;
+    std::string json = "erase me";
     serializeJson(array, json);
 
     REQUIRE("[4,2]" == json);
   }
 
   SECTION("serializeJsonPretty") {
-    std::string json;
+    std::string json = "erase me";
     serializeJsonPretty(array, json);
 
     REQUIRE("[\r\n  4,\r\n  2\r\n]" == json);
@@ -27,19 +29,19 @@ TEST_CASE("serialize JsonArray to std::string") {
 }
 
 TEST_CASE("serialize JsonObject to std::string") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject obj = doc.to<JsonObject>();
   obj["key"] = "value";
 
   SECTION("object") {
-    std::string json;
+    std::string json = "erase me";
     serializeJson(doc, json);
 
     REQUIRE("{\"key\":\"value\"}" == json);
   }
 
   SECTION("serializeJsonPretty") {
-    std::string json;
+    std::string json = "erase me";
     serializeJsonPretty(doc, json);
 
     REQUIRE("{\r\n  \"key\": \"value\"\r\n}" == json);
@@ -47,11 +49,10 @@ TEST_CASE("serialize JsonObject to std::string") {
 }
 
 TEST_CASE("serialize an std::string containing a NUL") {
-  StaticJsonDocument<256> doc;
-  doc.set(std::string("hello\0world", 11));
-  CHECK(doc.memoryUsage() == 12);
+  JsonDocument doc;
+  doc.set("hello\0world"_s);
 
-  std::string json;
+  std::string json = "erase me";
   serializeJson(doc, json);
   CHECK("\"hello\\u0000world\"" == json);
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt
index d43a971c19e312884541d9853a2ae9b8c8dd0bd9..e14e5481fc73aa5f452ba120bc1e4e3a9521fb0d 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(JsonVariantTests
@@ -10,10 +10,8 @@ add_executable(JsonVariantTests
 	containsKey.cpp
 	converters.cpp
 	copy.cpp
-	createNested.cpp
 	is.cpp
 	isnull.cpp
-	memoryUsage.cpp
 	misc.cpp
 	nesting.cpp
 	nullptr.cpp
@@ -21,7 +19,6 @@ add_executable(JsonVariantTests
 	overflow.cpp
 	remove.cpp
 	set.cpp
-	shallowCopy.cpp
 	size.cpp
 	stl_containers.cpp
 	subscript.cpp
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/add.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/add.cpp
index 6907ae3ed76dde74c524c68bd0fe42065272bcf9..8b3e602078c9ce282915fdf138f9966a8e192f10 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/add.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/add.cpp
@@ -1,13 +1,16 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <stdint.h>
 #include <catch.hpp>
 
-TEST_CASE("JsonVariant::add()") {
-  DynamicJsonDocument doc(4096);
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+TEST_CASE("JsonVariant::add(T)") {
+  JsonDocument doc;
   JsonVariant var = doc.to<JsonVariant>();
 
   SECTION("add integer to new variant") {
@@ -23,7 +26,7 @@ TEST_CASE("JsonVariant::add()") {
   }
 
   SECTION("add std::string to new variant") {
-    var.add(std::string("hello"));
+    var.add("hello"_s);
 
     REQUIRE(var.as<std::string>() == "[\"hello\"]");
   }
@@ -44,3 +47,70 @@ TEST_CASE("JsonVariant::add()") {
     REQUIRE(var.as<std::string>() == "{\"val\":123}");
   }
 }
+
+TEST_CASE("JsonVariant::add<T>()") {
+  JsonDocument doc;
+  JsonVariant var = doc.to<JsonVariant>();
+
+  SECTION("JsonArray") {
+    JsonArray array = var.add<JsonArray>();
+    array.add(1);
+    array.add(2);
+    REQUIRE(doc.as<std::string>() == "[[1,2]]");
+  }
+
+  SECTION("JsonVariant") {
+    JsonVariant variant = var.add<JsonVariant>();
+    variant.set(42);
+    REQUIRE(doc.as<std::string>() == "[42]");
+  }
+}
+
+TEST_CASE("JsonObject::add(JsonObject) ") {
+  JsonDocument doc1;
+  doc1["hello"_s] = "world"_s;
+
+  TimebombAllocator allocator(10);
+  SpyingAllocator spy(&allocator);
+  JsonDocument doc2(&spy);
+  JsonVariant variant = doc2.to<JsonVariant>();
+
+  SECTION("success") {
+    bool result = variant.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == true);
+    REQUIRE(doc2.as<std::string>() == "[{\"hello\":\"world\"}]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("hello")),
+                             Allocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("partial failure") {  // issue #2081
+    allocator.setCountdown(2);
+
+    bool result = variant.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == false);
+    REQUIRE(doc2.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("hello")),
+                             AllocateFail(sizeofString("world")),
+                             Deallocate(sizeofString("hello")),
+                         });
+  }
+
+  SECTION("complete failure") {
+    allocator.setCountdown(0);
+
+    bool result = variant.add(doc1.as<JsonObject>());
+
+    REQUIRE(result == false);
+    REQUIRE(doc2.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofPool()),
+                         });
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/as.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/as.cpp
index 01e263e9c7ab3ada09c68374e96f2d77ec84aa0a..fa76b0e05597fde14aee3690724d3911a69b70b3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/as.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/as.cpp
@@ -1,11 +1,13 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <stdint.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 namespace my {
 using ArduinoJson::detail::isinf;
 }  // namespace my
@@ -15,7 +17,7 @@ enum MY_ENUM { ONE = 1, TWO = 2 };
 TEST_CASE("JsonVariant::as()") {
   static const char* null = 0;
 
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("not set") {
@@ -25,6 +27,8 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(0 == variant.as<const char*>());
     REQUIRE("null" == variant.as<std::string>());
     REQUIRE(variant.as<JsonString>().isNull());
+    REQUIRE(variant.as<MsgPackBinary>().data() == nullptr);
+    REQUIRE(variant.as<MsgPackExtension>().data() == nullptr);
   }
 
   SECTION("set(4.2)") {
@@ -36,6 +40,8 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(variant.as<long>() == 4L);
     REQUIRE(variant.as<unsigned>() == 4U);
     REQUIRE(variant.as<JsonString>().isNull());
+    REQUIRE(variant.as<MsgPackBinary>().data() == nullptr);
+    REQUIRE(variant.as<MsgPackExtension>().data() == nullptr);
   }
 
   SECTION("set(0.0)") {
@@ -44,6 +50,8 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(variant.as<bool>() == false);
     REQUIRE(variant.as<long>() == 0L);
     REQUIRE(variant.as<JsonString>().isNull());
+    REQUIRE(variant.as<MsgPackBinary>().data() == nullptr);
+    REQUIRE(variant.as<MsgPackExtension>().data() == nullptr);
   }
 
   SECTION("set(false)") {
@@ -54,6 +62,8 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(variant.as<long>() == 0L);
     REQUIRE(variant.as<std::string>() == "false");
     REQUIRE(variant.as<JsonString>().isNull());
+    REQUIRE(variant.as<MsgPackBinary>().data() == nullptr);
+    REQUIRE(variant.as<MsgPackExtension>().data() == nullptr);
   }
 
   SECTION("set(true)") {
@@ -64,6 +74,8 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(variant.as<long>() == 1L);
     REQUIRE(variant.as<std::string>() == "true");
     REQUIRE(variant.as<JsonString>().isNull());
+    REQUIRE(variant.as<MsgPackBinary>().data() == nullptr);
+    REQUIRE(variant.as<MsgPackExtension>().data() == nullptr);
   }
 
   SECTION("set(42)") {
@@ -75,6 +87,8 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(variant.as<unsigned int>() == 42U);  // issue #1601
     REQUIRE(variant.as<std::string>() == "42");
     REQUIRE(variant.as<JsonString>().isNull());
+    REQUIRE(variant.as<MsgPackBinary>().data() == nullptr);
+    REQUIRE(variant.as<MsgPackExtension>().data() == nullptr);
   }
 
   SECTION("set(42L)") {
@@ -144,20 +158,20 @@ TEST_CASE("JsonVariant::as()") {
 
     REQUIRE(variant.as<bool>() == true);
     REQUIRE(variant.as<long>() == 0L);
-    REQUIRE(variant.as<const char*>() == std::string("hello"));
-    REQUIRE(variant.as<const char*>() == std::string("hello"));
-    REQUIRE(variant.as<std::string>() == std::string("hello"));
+    REQUIRE(variant.as<const char*>() == "hello"_s);
+    REQUIRE(variant.as<const char*>() == "hello"_s);
+    REQUIRE(variant.as<std::string>() == "hello"_s);
     REQUIRE(variant.as<JsonString>() == "hello");
   }
 
   SECTION("set(std::string(\"4.2\"))") {
-    variant.set(std::string("4.2"));
+    variant.set("4.2"_s);
 
     REQUIRE(variant.as<bool>() == true);
     REQUIRE(variant.as<long>() == 4L);
     REQUIRE(variant.as<double>() == 4.2);
-    REQUIRE(variant.as<const char*>() == std::string("4.2"));
-    REQUIRE(variant.as<std::string>() == std::string("4.2"));
+    REQUIRE(variant.as<const char*>() == "4.2"_s);
+    REQUIRE(variant.as<std::string>() == "4.2"_s);
     REQUIRE(variant.as<JsonString>() == "4.2");
     REQUIRE(variant.as<JsonString>().isLinked() == false);
   }
@@ -199,6 +213,13 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(variant.as<JsonString>().isNull());
   }
 
+  SECTION("set(serialized(\"hello\"))") {
+    variant.set(serialized("hello"));
+
+    REQUIRE(variant.as<MsgPackBinary>().data() == nullptr);
+    REQUIRE(variant.as<MsgPackExtension>().data() == nullptr);
+  }
+
   SECTION("to<JsonObject>()") {
     JsonObject obj = variant.to<JsonObject>();
     obj["key"] = "value";
@@ -208,13 +229,13 @@ TEST_CASE("JsonVariant::as()") {
     }
 
     SECTION("as<std::string>()") {
-      REQUIRE(variant.as<std::string>() == std::string("{\"key\":\"value\"}"));
+      REQUIRE(variant.as<std::string>() == "{\"key\":\"value\"}"_s);
     }
 
     SECTION("ObjectAsJsonObject") {
       JsonObject o = variant.as<JsonObject>();
       REQUIRE(o.size() == 1);
-      REQUIRE(o["key"] == std::string("value"));
+      REQUIRE(o["key"] == "value"_s);
     }
   }
 
@@ -228,7 +249,7 @@ TEST_CASE("JsonVariant::as()") {
     }
 
     SECTION("as<std::string>()") {
-      REQUIRE(variant.as<std::string>() == std::string("[4,2]"));
+      REQUIRE(variant.as<std::string>() == "[4,2]"_s);
     }
 
     SECTION("as<JsonArray>()") {
@@ -245,23 +266,12 @@ TEST_CASE("JsonVariant::as()") {
     REQUIRE(variant.as<long long>() == -9223372036854775807 - 1);
   }
 
-  SECTION("Biggerst int64 positive") {
+  SECTION("Biggest int64 positive") {
     variant.set("9223372036854775807");
     REQUIRE(variant.as<long long>() == 9223372036854775807);
   }
 #endif
 
-  SECTION("should work on JsonVariantConst") {
-    variant.set("hello");
-
-    JsonVariantConst cvar = variant;
-
-    REQUIRE(cvar.as<bool>() == true);
-    REQUIRE(cvar.as<long>() == 0L);
-    REQUIRE(cvar.as<const char*>() == std::string("hello"));
-    REQUIRE(cvar.as<std::string>() == std::string("hello"));
-  }
-
   SECTION("as<enum>()") {
     variant.set(1);
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/clear.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/clear.cpp
index d516ca6c17e135c7e749ed47c4bd22a6e9a50edc..8e292d26f08e177e4c55f8fc2921767fc27befaa 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/clear.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/clear.cpp
@@ -1,13 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <stdint.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
 TEST_CASE("JsonVariant::clear()") {
-  DynamicJsonDocument doc(4096);
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
   JsonVariant var = doc.to<JsonVariant>();
 
   SECTION("size goes back to zero") {
@@ -23,4 +27,14 @@ TEST_CASE("JsonVariant::clear()") {
 
     REQUIRE(var.isNull() == true);
   }
+
+  SECTION("releases owned string") {
+    var.set("hello"_s);
+    var.clear();
+
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("hello")),
+                             Deallocate(sizeofString("hello")),
+                         });
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/compare.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/compare.cpp
index 1922df034ab241c9654b4178c42cf2c309b12148..de7ced715313ac3c6a7d9140d8547978f0b68e63 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/compare.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/compare.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -9,8 +9,8 @@
 // Here, we're just filling the holes
 
 TEST_CASE("Compare JsonVariant with value") {
-  StaticJsonDocument<256> doc;
-  JsonVariant a = doc.add();
+  JsonDocument doc;
+  JsonVariant a = doc.add<JsonVariant>();
 
   SECTION("null vs (char*)0") {
     char* b = 0;
@@ -37,9 +37,9 @@ TEST_CASE("Compare JsonVariant with value") {
 }
 
 TEST_CASE("Compare JsonVariant with JsonVariant") {
-  StaticJsonDocument<256> doc;
-  JsonVariant a = doc.add();
-  JsonVariant b = doc.add();
+  JsonDocument doc;
+  JsonVariant a = doc.add<JsonVariant>();
+  JsonVariant b = doc.add<JsonVariant>();
 
   SECTION("'abc' vs 'abc'") {
     a.set("abc");
@@ -113,6 +113,42 @@ TEST_CASE("Compare JsonVariant with JsonVariant") {
     CHECK_FALSE(a == b);
   }
 
+  SECTION("MsgPackBinary('abc') vs MsgPackBinary('abc')") {
+    a.set(MsgPackBinary("abc", 4));
+    b.set(MsgPackBinary("abc", 4));
+
+    CHECK(a == b);
+    CHECK(a <= b);
+    CHECK(a >= b);
+    CHECK_FALSE(a != b);
+    CHECK_FALSE(a < b);
+    CHECK_FALSE(a > b);
+  }
+
+  SECTION("MsgPackBinary('abc') vs MsgPackBinary('bcd')") {
+    a.set(MsgPackBinary("abc", 4));
+    b.set(MsgPackBinary("bcd", 4));
+
+    CHECK(a != b);
+    CHECK(a < b);
+    CHECK(a <= b);
+    CHECK_FALSE(a == b);
+    CHECK_FALSE(a > b);
+    CHECK_FALSE(a >= b);
+  }
+
+  SECTION("MsgPackBinary('bcd') vs MsgPackBinary('abc')") {
+    a.set(MsgPackBinary("bcd", 4));
+    b.set(MsgPackBinary("abc", 4));
+
+    CHECK(a != b);
+    CHECK(a > b);
+    CHECK(a >= b);
+    CHECK_FALSE(a < b);
+    CHECK_FALSE(a <= b);
+    CHECK_FALSE(a == b);
+  }
+
   SECTION("false vs true") {
     a.set(false);
     b.set(true);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp
index d003d11df7e4a0b4c6a244202017046c2141a8f5..a406fe14e5d035878a3a6063ed61ba83f9105fdb 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp
@@ -1,13 +1,15 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <stdint.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("JsonVariant::containsKey()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant var = doc.to<JsonVariant>();
 
   SECTION("containsKey(const char*)") {
@@ -20,23 +22,15 @@ TEST_CASE("JsonVariant::containsKey()") {
   SECTION("containsKey(std::string)") {
     var["hello"] = "world";
 
-    REQUIRE(var.containsKey(std::string("hello")) == true);
-    REQUIRE(var.containsKey(std::string("world")) == false);
+    REQUIRE(var.containsKey("hello"_s) == true);
+    REQUIRE(var.containsKey("world"_s) == false);
   }
-}
-
-TEST_CASE("JsonVariantConst::containsKey()") {
-  DynamicJsonDocument doc(4096);
-  doc["hello"] = "world";
-  JsonVariantConst cvar = doc.as<JsonVariant>();
 
-  SECTION("containsKey(const char*) returns true") {
-    REQUIRE(cvar.containsKey("hello") == true);
-    REQUIRE(cvar.containsKey("world") == false);
-  }
+  SECTION("containsKey(JsonVariant)") {
+    var["hello"] = "world";
+    var["key"] = "hello";
 
-  SECTION("containsKey(std::string) returns true") {
-    REQUIRE(cvar.containsKey(std::string("hello")) == true);
-    REQUIRE(cvar.containsKey(std::string("world")) == false);
+    REQUIRE(var.containsKey(doc["key"]) == true);
+    REQUIRE(var.containsKey(doc["foo"]) == false);
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/converters.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/converters.cpp
index 89afc9b701a8c3e07e7b574ff34ae4a3cc8ecf2d..545c4beb3bfebb11e6897df2fdac03df9795936c 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/converters.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/converters.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -32,7 +32,7 @@ bool canConvertFromJson(JsonVariantConst src, const Date&) {
 }  // namespace
 
 TEST_CASE("Custom converter with overloading") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("convert JSON to Date") {
     doc["date"]["day"] = 2;
@@ -107,7 +107,7 @@ struct Converter<Complex> {
 }  // namespace ArduinoJson
 
 TEST_CASE("Custom converter with specialization") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("convert JSON to Complex") {
     doc["value"]["real"] = 2;
@@ -140,39 +140,3 @@ TEST_CASE("Custom converter with specialization") {
     REQUIRE(doc["value"]["imag"] == 3);
   }
 }
-
-TEST_CASE("ConverterNeedsWriteableRef") {
-  using namespace ArduinoJson::detail;
-  CHECK(ConverterNeedsWriteableRef<int>::value == false);
-  CHECK(ConverterNeedsWriteableRef<float>::value == false);
-  CHECK(ConverterNeedsWriteableRef<JsonVariant>::value == true);
-  CHECK(ConverterNeedsWriteableRef<JsonVariantConst>::value == false);
-  CHECK(ConverterNeedsWriteableRef<JsonObject>::value == true);
-  CHECK(ConverterNeedsWriteableRef<JsonObjectConst>::value == false);
-  CHECK(ConverterNeedsWriteableRef<JsonArray>::value == true);
-  CHECK(ConverterNeedsWriteableRef<JsonArrayConst>::value == false);
-}
-
-namespace ArduinoJson {
-void convertToJson(char c, JsonVariant var) {
-  char buf[] = {c, 0};
-  var.set(buf);
-}
-
-void convertFromJson(JsonVariantConst src, char& dst) {
-  auto p = src.as<const char*>();
-  dst = p ? p[0] : 0;
-}
-}  // namespace ArduinoJson
-
-TEST_CASE("Convert char to string") {  // issue #1922
-  StaticJsonDocument<64> doc;
-  doc.set('a');
-  REQUIRE(doc.as<std::string>() == "a");
-}
-
-TEST_CASE("Convert string to char") {  // issue #1963
-  StaticJsonDocument<64> doc;
-  doc.set("a");
-  REQUIRE(doc.as<char>() == 'a');
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/copy.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/copy.cpp
index 0d96bf9fa604573e0caf39af2beb8cf2bfe0ae9a..7a52c2e058f7ba12ac6f7730ee6ec8676ebb9f0f 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/copy.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/copy.cpp
@@ -1,19 +1,24 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
+#include "Allocators.hpp"
+
+#include "Literals.hpp"
 
 TEST_CASE("JsonVariant::set(JsonVariant)") {
-  DynamicJsonDocument doc1(4096);
-  DynamicJsonDocument doc2(4096);
+  KillswitchAllocator killswitch;
+  SpyingAllocator spyingAllocator(&killswitch);
+  JsonDocument doc1(&spyingAllocator);
+  JsonDocument doc2(&spyingAllocator);
   JsonVariant var1 = doc1.to<JsonVariant>();
   JsonVariant var2 = doc2.to<JsonVariant>();
 
   SECTION("stores JsonArray by copy") {
     JsonArray arr = doc2.to<JsonArray>();
-    JsonObject obj = arr.createNestedObject();
+    JsonObject obj = arr.add<JsonObject>();
     obj["hello"] = "world";
 
     var1.set(arr);
@@ -24,7 +29,7 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
 
   SECTION("stores JsonObject by copy") {
     JsonObject obj = doc2.to<JsonObject>();
-    JsonArray arr = obj.createNestedArray("value");
+    JsonArray arr = obj["value"].to<JsonArray>();
     arr.add(42);
 
     var1.set(obj);
@@ -35,53 +40,95 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
 
   SECTION("stores const char* by reference") {
     var1.set("hello!!");
+    spyingAllocator.clearLog();
+
     var2.set(var1);
 
-    REQUIRE(doc1.memoryUsage() == 0);
-    REQUIRE(doc2.memoryUsage() == 0);
+    REQUIRE(spyingAllocator.log() == AllocatorLog{});
   }
 
   SECTION("stores char* by copy") {
     char str[] = "hello!!";
+    var1.set(str);
+    spyingAllocator.clearLog();
+
+    var2.set(var1);
 
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello!!")),
+                                     });
+  }
+
+  SECTION("fails gracefully if string allocation fails") {
+    char str[] = "hello!!";
     var1.set(str);
+    killswitch.on();
+    spyingAllocator.clearLog();
+
     var2.set(var1);
 
-    REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7));
-    REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7));
+    REQUIRE(doc2.overflowed() == true);
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         AllocateFail(sizeofString("hello!!")),
+                                     });
   }
 
   SECTION("stores std::string by copy") {
-    var1.set(std::string("hello!!"));
+    var1.set("hello!!"_s);
+    spyingAllocator.clearLog();
+
     var2.set(var1);
 
-    REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7));
-    REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7));
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello!!")),
+                                     });
   }
 
-  SECTION("stores Serialized<const char*> by reference") {
-    var1.set(serialized("hello!!", 8));
+  SECTION("stores Serialized<const char*> by copy") {
+    var1.set(serialized("hello!!", 7));
+    spyingAllocator.clearLog();
+
     var2.set(var1);
 
-    REQUIRE(doc1.memoryUsage() == 0);
-    REQUIRE(doc2.memoryUsage() == 0);
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello!!")),
+                                     });
   }
 
   SECTION("stores Serialized<char*> by copy") {
     char str[] = "hello!!";
     var1.set(serialized(str, 7));
+    spyingAllocator.clearLog();
+
     var2.set(var1);
 
-    REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7));
-    REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7));
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello!!")),
+                                     });
   }
 
   SECTION("stores Serialized<std::string> by copy") {
-    var1.set(serialized(std::string("hello!!")));
+    var1.set(serialized("hello!!"_s));
+    spyingAllocator.clearLog();
+
+    var2.set(var1);
+
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofString("hello!!")),
+                                     });
+  }
+
+  SECTION("fails gracefully if raw string allocation fails") {
+    var1.set(serialized("hello!!"_s));
+    killswitch.on();
+    spyingAllocator.clearLog();
+
     var2.set(var1);
 
-    REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7));
-    REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7));
+    REQUIRE(doc2.overflowed() == true);
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         AllocateFail(sizeofString("hello!!")),
+                                     });
   }
 
   SECTION("destination is unbound") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/createNested.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/createNested.cpp
deleted file mode 100644
index e3958231e622178bb9be870734ad07bc368d6d99..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/createNested.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <stdint.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonVariant::createNestedObject()") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant variant = doc.to<JsonVariant>();
-
-  SECTION("promotes to array") {
-    JsonObject obj = variant.createNestedObject();
-    obj["value"] = 42;
-
-    REQUIRE(variant.is<JsonArray>() == true);
-    REQUIRE(variant[0]["value"] == 42);
-    REQUIRE(obj.isNull() == false);
-  }
-}
-
-TEST_CASE("JsonVariant::createNestedArray()") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant variant = doc.to<JsonVariant>();
-
-  SECTION("promotes to array") {
-    JsonArray arr = variant.createNestedArray();
-
-    REQUIRE(variant.is<JsonArray>() == true);
-    REQUIRE(arr.isNull() == false);
-  }
-}
-
-TEST_CASE("JsonVariant::createNestedObject(key)") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant variant = doc.to<JsonVariant>();
-
-  SECTION("promotes to object") {
-    JsonObject obj = variant.createNestedObject("weather");
-    obj["temp"] = 42;
-
-    REQUIRE(variant.is<JsonObject>() == true);
-    REQUIRE(variant["weather"]["temp"] == 42);
-  }
-}
-
-TEST_CASE("JsonVariant::createNestedArray(key)") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant variant = doc.to<JsonVariant>();
-
-  SECTION("promotes to object") {
-    JsonArray arr = variant.createNestedArray("items");
-
-    REQUIRE(variant.is<JsonObject>() == true);
-    REQUIRE(arr.isNull() == false);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/is.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/is.cpp
index 6a414a42fda5084a6f37a1301b0a859adacd8a78..4d070346c395e53ddbfc82550d75c63fae00727d 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/is.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/is.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -8,7 +8,7 @@
 enum MYENUM2 { ONE = 1, TWO = 2 };
 
 TEST_CASE("JsonVariant::is<T>()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("unbound") {
@@ -162,158 +162,3 @@ TEST_CASE("JsonVariant::is<T>()") {
     CHECK(variant.is<JsonVariantConst>() == true);
   }
 }
-
-TEST_CASE("JsonVariantConst::is<T>()") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant variant = doc.to<JsonVariant>();
-  JsonVariantConst cvariant = variant;
-
-  SECTION("unbound") {
-    cvariant = JsonVariantConst();
-
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<JsonArrayConst>() == false);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonObjectConst>() == false);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<JsonVariantConst>() == false);
-    CHECK(cvariant.is<bool>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<std::string>() == false);
-    CHECK(cvariant.is<JsonString>() == false);
-    CHECK(cvariant.is<float>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-
-  SECTION("null") {
-    CHECK(cvariant.is<JsonVariantConst>() == true);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<bool>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<std::string>() == false);
-    CHECK(cvariant.is<JsonString>() == false);
-    CHECK(cvariant.is<float>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-
-  SECTION("true") {
-    variant.set(true);
-
-    CHECK(cvariant.is<bool>() == true);
-    CHECK(cvariant.is<JsonVariantConst>() == true);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<std::string>() == false);
-    CHECK(cvariant.is<JsonString>() == false);
-    CHECK(cvariant.is<float>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-
-  SECTION("false") {
-    variant.set(false);
-
-    CHECK(cvariant.is<bool>() == true);
-    CHECK(cvariant.is<JsonVariantConst>() == true);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<std::string>() == false);
-    CHECK(cvariant.is<JsonString>() == false);
-    CHECK(cvariant.is<float>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-
-  SECTION("int") {
-    variant.set(42);
-
-    CHECK(cvariant.is<int>() == true);
-    CHECK(cvariant.is<short>() == true);
-    CHECK(cvariant.is<long>() == true);
-    CHECK(cvariant.is<double>() == true);
-    CHECK(cvariant.is<float>() == true);
-    CHECK(cvariant.is<MYENUM2>() == true);
-    CHECK(cvariant.is<JsonVariantConst>() == true);
-    CHECK(cvariant.is<bool>() == false);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<std::string>() == false);
-    CHECK(cvariant.is<JsonString>() == false);
-  }
-
-  SECTION("double") {
-    variant.set(4.2);
-
-    CHECK(cvariant.is<double>() == true);
-    CHECK(cvariant.is<float>() == true);
-    CHECK(cvariant.is<JsonVariantConst>() == true);
-    CHECK(cvariant.is<bool>() == false);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<std::string>() == false);
-    CHECK(cvariant.is<JsonString>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-
-  SECTION("const char*") {
-    variant.set("4.2");
-
-    CHECK(cvariant.is<const char*>() == true);
-    CHECK(cvariant.is<const char*>() == true);
-    CHECK(cvariant.is<std::string>() == true);
-    CHECK(cvariant.is<JsonString>() == true);
-    CHECK(cvariant.is<double>() == false);
-    CHECK(cvariant.is<float>() == false);
-    CHECK(cvariant.is<bool>() == false);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-
-  SECTION("JsonArray") {
-    variant.to<JsonArray>();
-
-    CHECK(cvariant.is<JsonArrayConst>() == true);
-    CHECK(cvariant.is<JsonVariantConst>() == true);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonObjectConst>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<float>() == false);
-    CHECK(cvariant.is<bool>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-
-  SECTION("JsonObject") {
-    variant.to<JsonObject>();
-
-    CHECK(cvariant.is<JsonObjectConst>() == true);
-    CHECK(cvariant.is<JsonVariantConst>() == true);
-    CHECK(cvariant.is<JsonObject>() == false);
-    CHECK(cvariant.is<JsonVariant>() == false);
-    CHECK(cvariant.is<JsonArray>() == false);
-    CHECK(cvariant.is<JsonArrayConst>() == false);
-    CHECK(cvariant.is<int>() == false);
-    CHECK(cvariant.is<float>() == false);
-    CHECK(cvariant.is<bool>() == false);
-    CHECK(cvariant.is<const char*>() == false);
-    CHECK(cvariant.is<MYENUM2>() == false);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/isnull.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/isnull.cpp
index bb9de714be5832f90a7aa3b55b61f63af687f8c5..75617e97ca5501d5a023849d1bc09d1de9547dc3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/isnull.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/isnull.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonVariant::isNull()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("returns true when Undefined") {
@@ -20,7 +20,7 @@ TEST_CASE("JsonVariant::isNull()") {
   }
 
   SECTION("returns false when EmptyArray") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonArray array = doc2.to<JsonArray>();
 
     variant.set(array);
@@ -28,7 +28,7 @@ TEST_CASE("JsonVariant::isNull()") {
   }
 
   SECTION("returns false when EmptyObject") {
-    DynamicJsonDocument doc2(4096);
+    JsonDocument doc2;
     JsonObject obj = doc2.to<JsonObject>();
 
     variant.set(obj);
@@ -69,25 +69,4 @@ TEST_CASE("JsonVariant::isNull()") {
     variant.set(serialized(static_cast<const char*>(0)));
     REQUIRE(variant.isNull() == true);
   }
-
-  SECTION("returns true for a shallow null copy") {
-    StaticJsonDocument<128> doc2;
-    variant.shallowCopy(doc2);
-    CHECK(variant.isNull() == true);
-  }
-
-  SECTION("returns false for a shallow array copy") {
-    StaticJsonDocument<128> doc2;
-    doc2[0] = 42;
-    variant.shallowCopy(doc2);
-    CHECK(variant.isNull() == false);
-  }
-
-  SECTION("works with JsonVariantConst") {
-    variant.set(42);
-
-    JsonVariantConst cvar = variant;
-
-    REQUIRE(cvar.isNull() == false);
-  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/memoryUsage.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/memoryUsage.cpp
deleted file mode 100644
index 808acb3a20c90098e56c7c2ed546fc98a775111a..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/memoryUsage.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-#include <string>
-
-TEST_CASE("JsonVariant::memoryUsage()") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant var = doc.to<JsonVariant>();
-
-  SECTION("returns 0 if uninitialized") {
-    JsonVariant unitialized;
-    REQUIRE(unitialized.memoryUsage() == 0);
-  }
-
-  SECTION("returns size of object") {
-    JsonObject obj = var.to<JsonObject>();
-    obj["hello"] = 42;
-    REQUIRE(var.memoryUsage() == JSON_OBJECT_SIZE(1));
-  }
-
-  SECTION("returns size of array") {
-    JsonArray arr = var.to<JsonArray>();
-    arr.add(42);
-    REQUIRE(var.memoryUsage() == JSON_ARRAY_SIZE(1));
-  }
-
-  SECTION("returns size of owned string") {
-    var.set(std::string("hello"));
-    REQUIRE(var.memoryUsage() == 6);
-    REQUIRE(var.memoryUsage() == doc.memoryUsage());
-  }
-
-  SECTION("returns size of owned raw") {
-    var.set(serialized(std::string("hello")));
-    REQUIRE(var.memoryUsage() == 6);
-    REQUIRE(var.memoryUsage() == doc.memoryUsage());
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/misc.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/misc.cpp
index 7089f64050510761c87d33a97b90c9a08a519e12..4b39cf86e6ae5deee8af8bb79faf9264cf8d8544 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/misc.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/misc.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -10,6 +10,11 @@ TEST_CASE("VariantData") {
           true);
 }
 
+TEST_CASE("StringNode") {
+  REQUIRE(std::is_standard_layout<ArduinoJson::detail::StringNode>::value ==
+          true);
+}
+
 TEST_CASE("JsonVariant from JsonArray") {
   SECTION("JsonArray is null") {
     JsonArray arr;
@@ -18,7 +23,7 @@ TEST_CASE("JsonVariant from JsonArray") {
   }
 
   SECTION("JsonArray is not null") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonArray arr = doc.to<JsonArray>();
     arr.add(12);
     arr.add(34);
@@ -40,7 +45,7 @@ TEST_CASE("JsonVariant from JsonObject") {
   }
 
   SECTION("JsonObject is not null") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonObject obj = doc.to<JsonObject>();
     obj["a"] = 12;
     obj["b"] = 34;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nesting.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nesting.cpp
index 1028a2838129a642b95bd5ee0ce282eb0efe7c60..f2a703f667e727b5ab8f6647c4fe41cbbb69794a 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nesting.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nesting.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonVariant::nesting()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant var = doc.to<JsonVariant>();
 
   SECTION("return 0 if uninitialized") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nullptr.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nullptr.cpp
index e26d9306d84d444bce40de9ddb19e54e759f9a62..434ba5c86a0655bfd39076eae7d0ed7e673fb180 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nullptr.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/nullptr.cpp
@@ -3,7 +3,7 @@
 #include <catch.hpp>
 
 TEST_CASE("nullptr") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("JsonVariant == nullptr") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/or.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/or.cpp
index 01ec4701c8ae3aec8e62590bdebacd93d444ab67..937a0cff6b76a42eb9683df2848dc5c57f897bda 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/or.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/or.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonVariant::operator|()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc["value"].to<JsonVariant>();
 
   SECTION("null") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/overflow.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/overflow.cpp
index 1123e98b80de946973b5177f681d838fc6fcbc83..744805fdd905607e4a1abaa13bff46166150a1d1 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/overflow.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/overflow.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,7 +7,7 @@
 
 template <typename TOut, typename TIn>
 void shouldBeOk(TIn value) {
-  StaticJsonDocument<1> doc;
+  JsonDocument doc;
   JsonVariant var = doc.to<JsonVariant>();
   var.set(value);
   REQUIRE(var.as<TOut>() == TOut(value));
@@ -15,7 +15,7 @@ void shouldBeOk(TIn value) {
 
 template <typename TOut, typename TIn>
 void shouldOverflow(TIn value) {
-  StaticJsonDocument<1> doc;
+  JsonDocument doc;
   JsonVariant var = doc.to<JsonVariant>();
   var.set(value);
   REQUIRE(var.as<TOut>() == 0);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/remove.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/remove.cpp
index 0835bf0ee593503834f4c8701dbf09a1ef6206f4..b8dfc0e671bfcd496589f1d79955e9d596c00e8a 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/remove.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/remove.cpp
@@ -1,40 +1,111 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <stdint.h>
 #include <catch.hpp>
 
-TEST_CASE("JsonVariant::remove()") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant var = doc.to<JsonVariant>();
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+
+TEST_CASE("JsonVariant::remove(int)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+
+  SECTION("release top level strings") {
+    doc.add("hello"_s);
+    doc.add("hello"_s);
+    doc.add("world"_s);
 
-  SECTION("remove(int)") {
-    var.add(1);
-    var.add(2);
-    var.add(3);
+    JsonVariant var = doc.as<JsonVariant>();
+    REQUIRE(var.as<std::string>() == "[\"hello\",\"hello\",\"world\"]");
 
+    spy.clearLog();
     var.remove(1);
+    REQUIRE(var.as<std::string>() == "[\"hello\",\"world\"]");
+    REQUIRE(spy.log() == AllocatorLog{});
 
-    REQUIRE(var.as<std::string>() == "[1,3]");
+    spy.clearLog();
+    var.remove(1);
+    REQUIRE(var.as<std::string>() == "[\"hello\"]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                         });
+
+    spy.clearLog();
+    var.remove(0);
+    REQUIRE(var.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("hello")),
+                         });
   }
 
-  SECTION("remove(const char *)") {
-    var["a"] = 1;
-    var["b"] = 2;
+  SECTION("release strings in nested array") {
+    doc[0][0] = "hello"_s;
 
-    var.remove("a");
+    JsonVariant var = doc.as<JsonVariant>();
+    REQUIRE(var.as<std::string>() == "[[\"hello\"]]");
 
-    REQUIRE(var.as<std::string>() == "{\"b\":2}");
+    spy.clearLog();
+    var.remove(0);
+
+    REQUIRE(var.as<std::string>() == "[]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("hello")),
+                         });
   }
+}
 
-  SECTION("remove(std::string)") {
-    var["a"] = 1;
-    var["b"] = 2;
+TEST_CASE("JsonVariant::remove(const char *)") {
+  JsonDocument doc;
+  JsonVariant var = doc.to<JsonVariant>();
 
-    var.remove(std::string("b"));
+  var["a"] = 1;
+  var["b"] = 2;
 
-    REQUIRE(var.as<std::string>() == "{\"a\":1}");
-  }
+  var.remove("a");
+
+  REQUIRE(var.as<std::string>() == "{\"b\":2}");
+}
+
+TEST_CASE("JsonVariant::remove(std::string)") {
+  JsonDocument doc;
+  JsonVariant var = doc.to<JsonVariant>();
+
+  var["a"] = 1;
+  var["b"] = 2;
+
+  var.remove("b"_s);
+
+  REQUIRE(var.as<std::string>() == "{\"a\":1}");
+}
+
+TEST_CASE("JsonVariant::remove(JsonVariant) from object") {
+  JsonDocument doc;
+  JsonVariant var = doc.to<JsonVariant>();
+
+  var["a"] = "a";
+  var["b"] = 2;
+  var["c"] = "b";
+
+  var.remove(var["c"]);
+
+  REQUIRE(var.as<std::string>() == "{\"a\":\"a\",\"c\":\"b\"}");
+}
+
+TEST_CASE("JsonVariant::remove(JsonVariant) from array") {
+  JsonDocument doc;
+  JsonVariant var = doc.to<JsonVariant>();
+
+  var[0] = 3;
+  var[1] = 2;
+  var[2] = 1;
+
+  var.remove(var[2]);
+  var.remove(var[3]);  // noop
+
+  REQUIRE(var.as<std::string>() == "[3,1]");
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/set.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/set.cpp
index 4743afb970e3e02e1d9e4e51454a89dacbbf4e62..e47c2b4ab29f5a41555003db674d6dbbd5a87336 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/set.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/set.cpp
@@ -1,14 +1,19 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofObject;
+
 enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 };
 
 TEST_CASE("JsonVariant::set() when there is enough memory") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("const char*") {
@@ -128,19 +133,19 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
 }
 
 TEST_CASE("JsonVariant::set() with not enough memory") {
-  StaticJsonDocument<1> doc;
+  JsonDocument doc(FailingAllocator::instance());
 
   JsonVariant v = doc.to<JsonVariant>();
 
   SECTION("std::string") {
-    bool result = v.set(std::string("hello world!!"));
+    bool result = v.set("hello world!!"_s);
 
     REQUIRE(result == false);
     REQUIRE(v.isNull());
   }
 
   SECTION("Serialized<std::string>") {
-    bool result = v.set(serialized(std::string("hello world!!")));
+    bool result = v.set(serialized("hello world!!"_s));
 
     REQUIRE(result == false);
     REQUIRE(v.isNull());
@@ -155,11 +160,11 @@ TEST_CASE("JsonVariant::set() with not enough memory") {
   }
 }
 
-TEST_CASE("JsonVariant::set(DynamicJsonDocument)") {
-  DynamicJsonDocument doc1(1024);
+TEST_CASE("JsonVariant::set(JsonDocument)") {
+  JsonDocument doc1;
   doc1["hello"] = "world";
 
-  DynamicJsonDocument doc2(1024);
+  JsonDocument doc2;
   JsonVariant v = doc2.to<JsonVariant>();
 
   // Should copy the doc
@@ -170,3 +175,48 @@ TEST_CASE("JsonVariant::set(DynamicJsonDocument)") {
   serializeJson(doc2, json);
   REQUIRE(json == "{\"hello\":\"world\"}");
 }
+
+TEST_CASE("JsonVariant::set() releases the previous value") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  doc["hello"] = "world"_s;
+  spy.clearLog();
+
+  JsonVariant v = doc["hello"];
+
+  SECTION("int") {
+    v.set(42);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("bool") {
+    v.set(false);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("const char*") {
+    v.set("hello");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("float") {
+    v.set(1.2);
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("Serialized<const char*>") {
+    v.set(serialized("[]"));
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                             Allocate(sizeofString("[]")),
+                         });
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/shallowCopy.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/shallowCopy.cpp
deleted file mode 100644
index 28aa38c60c9fe673432be20cf21266af82f25af1..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/shallowCopy.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-TEST_CASE("JsonVariant::shallowCopy()") {
-  StaticJsonDocument<1024> doc1, doc2;
-  JsonVariant variant = doc1.to<JsonVariant>();
-
-  SECTION("JsonVariant::shallowCopy(JsonDocument&)") {
-    doc2["hello"] = "world";
-
-    variant.shallowCopy(doc2);
-
-    CHECK(variant.as<std::string>() == "{\"hello\":\"world\"}");
-
-    // altering the linked document should change the result
-    doc2["hello"] = "WORLD!";
-
-    CHECK(variant.as<std::string>() == "{\"hello\":\"WORLD!\"}");
-  }
-
-  SECTION("JsonVariant::shallowCopy(MemberProxy)") {
-    doc2["obj"]["hello"] = "world";
-
-    variant.shallowCopy(doc2["obj"]);
-
-    CHECK(variant.as<std::string>() == "{\"hello\":\"world\"}");
-
-    // altering the linked document should change the result
-    doc2["obj"]["hello"] = "WORLD!";
-
-    CHECK(variant.as<std::string>() == "{\"hello\":\"WORLD!\"}");
-  }
-
-  SECTION("JsonVariant::shallowCopy(ElementProxy)") {
-    doc2[0]["hello"] = "world";
-
-    variant.shallowCopy(doc2[0]);
-
-    CHECK(variant.as<std::string>() == "{\"hello\":\"world\"}");
-
-    // altering the linked document should change the result
-    doc2[0]["hello"] = "WORLD!";
-
-    CHECK(variant.as<std::string>() == "{\"hello\":\"WORLD!\"}");
-  }
-
-  SECTION("target is unbound") {
-    JsonVariant unbound;
-    variant["hello"] = "world";
-
-    variant.shallowCopy(unbound);
-
-    CHECK(variant.isUnbound() == false);
-    CHECK(variant.isNull() == true);
-    CHECK(variant.memoryUsage() == 0);
-    CHECK(variant.size() == 0);
-  }
-
-  SECTION("variant is unbound") {
-    JsonVariant unbound;
-    doc2["hello"] = "world";
-
-    unbound.shallowCopy(doc2);
-
-    CHECK(unbound.isUnbound() == true);
-    CHECK(unbound.isNull() == true);
-    CHECK(unbound.memoryUsage() == 0);
-    CHECK(unbound.size() == 0);
-  }
-
-  SECTION("preserves owned key bit") {
-    doc2.set(42);
-
-    doc1["a"].shallowCopy(doc2);
-    doc1[std::string("b")].shallowCopy(doc2);
-
-    JsonObject::iterator it = doc1.as<JsonObject>().begin();
-
-    CHECK(it->key().isLinked() == true);
-    ++it;
-    CHECK(it->key().isLinked() == false);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/size.cpp
index 3a4ec4676444fc7dc7768c96e688f084db36aba1..2f8e8a731a5cff2c0d3432c09f0840ff5801d7f1 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/size.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/size.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("JsonVariant::size()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("unbound reference") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/stl_containers.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/stl_containers.cpp
index d6b9b60bc1d373094df822955d01f08cc9ae34b0..05d10209a3f893da97db039f472f91e19d0d4c08 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/stl_containers.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/stl_containers.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -69,13 +69,13 @@ TEST_CASE("vector<int>") {
   SECTION("toJson") {
     std::vector<int> v = {1, 2};
 
-    StaticJsonDocument<128> doc;
+    JsonDocument doc;
     doc.set(v);
     REQUIRE(doc.as<std::string>() == "[1,2]");
   }
 
   SECTION("fromJson") {
-    StaticJsonDocument<128> doc;
+    JsonDocument doc;
     doc.add(1);
     doc.add(2);
 
@@ -86,7 +86,7 @@ TEST_CASE("vector<int>") {
   }
 
   SECTION("checkJson") {
-    StaticJsonDocument<128> doc;
+    JsonDocument doc;
     CHECK(doc.is<std::vector<int>>() == false);
 
     doc.add(1);
@@ -106,13 +106,13 @@ TEST_CASE("array<int, 2>") {
     v[0] = 1;
     v[1] = 2;
 
-    StaticJsonDocument<128> doc;
+    JsonDocument doc;
     doc.set(v);
     REQUIRE(doc.as<std::string>() == "[1,2]");
   }
 
   SECTION("fromJson") {
-    StaticJsonDocument<128> doc;
+    JsonDocument doc;
     doc.add(1);
     doc.add(2);
 
@@ -123,7 +123,7 @@ TEST_CASE("array<int, 2>") {
   }
 
   SECTION("checkJson") {
-    StaticJsonDocument<128> doc;
+    JsonDocument doc;
     CHECK(doc.is<array_type>() == false);
 
     doc.add(1);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/subscript.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/subscript.cpp
index 93f759b8b70e5ca34bc7dcb9f9b6e9f6e11b6d1c..946d84cabcddd18c4220a4347c65180c99225ed3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/subscript.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/subscript.cpp
@@ -1,12 +1,14 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("JsonVariant::operator[]") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant var = doc.to<JsonVariant>();
 
   SECTION("The JsonVariant is null") {
@@ -31,9 +33,9 @@ TEST_CASE("JsonVariant::operator[]") {
 
       REQUIRE(2 == var.size());
       var[0].as<std::string>();
-      // REQUIRE(std::string("element at index 0") == );
-      REQUIRE(std::string("element at index 1") == var[1]);
-      REQUIRE(std::string("element at index 0") ==
+      // REQUIRE("element at index 0"_s == );
+      REQUIRE("element at index 1"_s == var[1]);
+      REQUIRE("element at index 0"_s ==
               var[static_cast<unsigned char>(0)]);  // issue #381
       REQUIRE(var[666].isNull());
       REQUIRE(var[3].isNull());
@@ -46,17 +48,17 @@ TEST_CASE("JsonVariant::operator[]") {
       var[1] = "world";
 
       REQUIRE(var.size() == 2);
-      REQUIRE(std::string("world") == var[1]);
+      REQUIRE("world"_s == var[1]);
     }
 
     SECTION("set value in a nested object") {
-      array.createNestedObject();
+      array.add<JsonObject>();
 
       var[0]["hello"] = "world";
 
       REQUIRE(1 == var.size());
       REQUIRE(1 == var[0].size());
-      REQUIRE(std::string("world") == var[0]["hello"]);
+      REQUIRE("world"_s == var[0]["hello"]);
     }
 
     SECTION("variant[0] when variant contains an integer") {
@@ -67,6 +69,15 @@ TEST_CASE("JsonVariant::operator[]") {
       REQUIRE(var.is<int>());
       REQUIRE(var.as<int>() == 123);
     }
+
+    SECTION("use JsonVariant as index") {
+      array.add("A");
+      array.add("B");
+      array.add(1);
+
+      REQUIRE(var[var[2]] == "B");
+      REQUIRE(var[var[3]].isNull());
+    }
   }
 
   SECTION("The JsonVariant is a JsonObject") {
@@ -77,8 +88,8 @@ TEST_CASE("JsonVariant::operator[]") {
       object["b"] = "element at key \"b\"";
 
       REQUIRE(2 == var.size());
-      REQUIRE(std::string("element at key \"a\"") == var["a"]);
-      REQUIRE(std::string("element at key \"b\"") == var["b"]);
+      REQUIRE("element at key \"a\""_s == var["a"]);
+      REQUIRE("element at key \"b\""_s == var["b"]);
       REQUIRE(var["c"].isNull());
       REQUIRE(var[0].isNull());
     }
@@ -87,7 +98,7 @@ TEST_CASE("JsonVariant::operator[]") {
       var["hello"] = "world";
 
       REQUIRE(1 == var.size());
-      REQUIRE(std::string("world") == var["hello"]);
+      REQUIRE("world"_s == var["hello"]);
     }
 
     SECTION("set value, key is a char[]") {
@@ -96,13 +107,22 @@ TEST_CASE("JsonVariant::operator[]") {
       key[0] = '!';  // make sure the key is duplicated
 
       REQUIRE(1 == var.size());
-      REQUIRE(std::string("world") == var["hello"]);
+      REQUIRE("world"_s == var["hello"]);
     }
 
     SECTION("var[key].to<JsonArray>()") {
       JsonArray arr = var["hello"].to<JsonArray>();
       REQUIRE(arr.isNull() == false);
     }
+
+    SECTION("use JsonVariant as key") {
+      object["a"] = "a";
+      object["b"] = "b";
+      object["c"] = "b";
+
+      REQUIRE(var[var["c"]] == "b");
+      REQUIRE(var[var["d"]].isNull());
+    }
   }
 
 #if defined(HAS_VARIABLE_LENGTH_ARRAY) && \
@@ -115,7 +135,7 @@ TEST_CASE("JsonVariant::operator[]") {
     deserializeJson(doc, "{\"hello\":\"world\"}");
     JsonVariant variant = doc.as<JsonVariant>();
 
-    REQUIRE(std::string("world") == variant[vla]);
+    REQUIRE("world"_s == variant[vla]);
   }
 
   SECTION("key is a VLA, const JsonVariant") {
@@ -126,79 +146,7 @@ TEST_CASE("JsonVariant::operator[]") {
     deserializeJson(doc, "{\"hello\":\"world\"}");
     const JsonVariant variant = doc.as<JsonVariant>();
 
-    REQUIRE(std::string("world") == variant[vla]);
+    REQUIRE("world"_s == variant[vla]);
   }
 #endif
 }
-
-TEST_CASE("JsonVariantConst::operator[]") {
-  DynamicJsonDocument doc(4096);
-  JsonVariant var = doc.to<JsonVariant>();
-  JsonVariantConst cvar = var;
-
-  SECTION("The JsonVariant is null") {
-    REQUIRE(0 == cvar.size());
-    REQUIRE(cvar["0"].isNull());
-    REQUIRE(cvar[0].isNull());
-  }
-
-  SECTION("The JsonVariant is a string") {
-    var.set("hello world");
-    REQUIRE(0 == cvar.size());
-    REQUIRE(cvar["0"].isNull());
-    REQUIRE(cvar[0].isNull());
-  }
-
-  SECTION("The JsonVariant is a JsonArray") {
-    JsonArray array = var.to<JsonArray>();
-
-    SECTION("get value") {
-      array.add("element at index 0");
-      array.add("element at index 1");
-
-      REQUIRE(2 == cvar.size());
-      REQUIRE(std::string("element at index 0") == cvar[0]);
-      REQUIRE(std::string("element at index 1") == cvar[1]);
-      REQUIRE(std::string("element at index 0") ==
-              var[static_cast<unsigned char>(0)]);  // issue #381
-      REQUIRE(cvar[666].isNull());
-      REQUIRE(cvar[3].isNull());
-      REQUIRE(cvar["0"].isNull());
-    }
-  }
-
-  SECTION("The JsonVariant is a JsonObject") {
-    JsonObject object = var.to<JsonObject>();
-
-    SECTION("get value") {
-      object["a"] = "element at key \"a\"";
-      object["b"] = "element at key \"b\"";
-
-      REQUIRE(2 == cvar.size());
-      REQUIRE(std::string("element at key \"a\"") == cvar["a"]);
-      REQUIRE(std::string("element at key \"b\"") == cvar["b"]);
-      REQUIRE(cvar["c"].isNull());
-      REQUIRE(cvar[0].isNull());
-    }
-  }
-
-  SECTION("Auto promote null JsonVariant to JsonObject") {
-    var["hello"] = "world";
-
-    REQUIRE(var.is<JsonObject>() == true);
-  }
-
-  SECTION("Don't auto promote non-null JsonVariant to JsonObject") {
-    var.set(42);
-    var["hello"] = "world";
-
-    REQUIRE(var.is<JsonObject>() == false);
-  }
-
-  SECTION("Don't auto promote null JsonVariant to JsonObject when reading") {
-    const char* value = var["hello"];
-
-    REQUIRE(var.is<JsonObject>() == false);
-    REQUIRE(value == 0);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/types.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/types.cpp
index 91b4c1e2a647b2d3b24c82e0d21cae0f73adb2d0..eb0ee62892da66c9868a125cfda3a56e8e006ea0 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/types.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/types.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -9,7 +9,7 @@
 
 template <typename T>
 void checkValue(T expected) {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   variant.set(expected);
@@ -24,7 +24,7 @@ void checkReference(T& expected) {
 
 template <typename T>
 void checkNumericType() {
-  DynamicJsonDocument docMin(4096), docMax(4096);
+  JsonDocument docMin, docMax;
   JsonVariant variantMin = docMin.to<JsonVariant>();
   JsonVariant variantMax = docMax.to<JsonVariant>();
 
@@ -129,7 +129,7 @@ TEST_CASE("JsonVariant set()/get()") {
 #endif
 
   SECTION("CanStoreObject") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     JsonObject object = doc.to<JsonObject>();
 
     checkValue<JsonObject>(object);
@@ -137,7 +137,7 @@ TEST_CASE("JsonVariant set()/get()") {
 }
 
 TEST_CASE("volatile") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
 
   SECTION("volatile bool") {  // issue #2029
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/unbound.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/unbound.cpp
index 902032363fc84582c1276e7614d98722a6211508..ff936258f9a083b2cc46eecce3e28311b0e000e9 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/unbound.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariant/unbound.cpp
@@ -1,10 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("Unbound JsonVariant") {
   JsonVariant variant;
 
@@ -21,6 +23,10 @@ TEST_CASE("Unbound JsonVariant") {
     CHECK(variant.as<JsonObject>().isNull());
     CHECK(variant.as<JsonObjectConst>().isNull());
     CHECK(variant.as<JsonString>().isNull());
+    CHECK(variant.as<MsgPackBinary>().data() == nullptr);
+    CHECK(variant.as<MsgPackBinary>().size() == 0);
+    CHECK(variant.as<MsgPackExtension>().data() == nullptr);
+    CHECK(variant.as<MsgPackExtension>().size() == 0);
   }
 
   SECTION("is<T>()") {
@@ -44,7 +50,10 @@ TEST_CASE("Unbound JsonVariant") {
     CHECK_FALSE(variant.set(42L));
     CHECK_FALSE(variant.set(42U));
     CHECK_FALSE(variant.set(serialized("42")));
+    CHECK_FALSE(variant.set(serialized("42"_s)));
     CHECK_FALSE(variant.set(true));
+    CHECK_FALSE(variant.set(MsgPackBinary("hello", 5)));
+    CHECK_FALSE(variant.set(MsgPackExtension(1, "hello", 5)));
   }
 
   SECTION("add()") {
@@ -61,6 +70,15 @@ TEST_CASE("Unbound JsonVariant") {
     CHECK(variant["key"].isNull());
     CHECK_FALSE(variant[0].set(1));
     CHECK_FALSE(variant["key"].set(1));
-    CHECK_FALSE(variant[std::string("key")].set(1));
+    CHECK_FALSE(variant["key"_s].set(1));
+  }
+
+  SECTION("containsKey()") {
+    CHECK_FALSE(variant.containsKey("hello"));
+  }
+
+  SECTION("remove()") {
+    variant.remove(0);
+    variant.remove("hello");
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d0dc65e9f91ab23871153b774b9135f40ca6a264
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/CMakeLists.txt
@@ -0,0 +1,20 @@
+# ArduinoJson - https://arduinojson.org
+# Copyright © 2014-2024, Benoit BLANCHON
+# MIT License
+
+add_executable(JsonVariantConstTests
+	as.cpp
+	containsKey.cpp
+	is.cpp
+	isnull.cpp
+	nesting.cpp
+	size.cpp
+	subscript.cpp
+)
+
+add_test(JsonVariantConst JsonVariantConstTests)
+
+set_tests_properties(JsonVariantConst
+	PROPERTIES
+		LABELS "Catch"
+)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/as.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/as.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5ef1563fa7b50a715928021f5b18adb816b6a157
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/as.cpp
@@ -0,0 +1,42 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <stdint.h>
+#include <catch.hpp>
+
+#include "Literals.hpp"
+
+TEST_CASE("JsonVariantConst::as<T>()") {
+  JsonDocument doc;
+  JsonVariantConst var = doc.to<JsonVariant>();
+
+  doc.set("hello");
+
+  REQUIRE(var.as<bool>() == true);
+  REQUIRE(var.as<long>() == 0L);
+  REQUIRE(var.as<const char*>() == "hello"_s);
+  REQUIRE(var.as<std::string>() == "hello"_s);
+}
+
+TEST_CASE("Invalid conversions") {
+  using namespace ArduinoJson::detail;
+
+  JsonVariantConst variant;
+
+  CHECK(is_same<decltype(variant.as<int>()), int>::value);
+  CHECK(is_same<decltype(variant.as<float>()), float>::value);
+  CHECK(is_same<decltype(variant.as<JsonVariantConst>()),
+                JsonVariantConst>::value);
+  CHECK(
+      is_same<decltype(variant.as<JsonObjectConst>()), JsonObjectConst>::value);
+  CHECK(is_same<decltype(variant.as<JsonArrayConst>()), JsonArrayConst>::value);
+
+  CHECK(is_same<decltype(variant.as<JsonVariant>()),
+                InvalidConversion<JsonVariantConst, JsonVariant>>::value);
+  CHECK(is_same<decltype(variant.as<JsonObject>()),
+                InvalidConversion<JsonVariantConst, JsonObject>>::value);
+  CHECK(is_same<decltype(variant.as<JsonArray>()),
+                InvalidConversion<JsonVariantConst, JsonArray>>::value);
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/containsKey.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/containsKey.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dfa1fc806d57b23c7bc2365823d4a754eab2418b
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/containsKey.cpp
@@ -0,0 +1,41 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <stdint.h>
+#include <catch.hpp>
+
+#include "Literals.hpp"
+
+TEST_CASE("JsonVariantConst::containsKey()") {
+  JsonDocument doc;
+  doc["hello"] = "world";
+  JsonVariantConst var = doc.as<JsonVariant>();
+
+  SECTION("support const char*") {
+    REQUIRE(var.containsKey("hello") == true);
+    REQUIRE(var.containsKey("world") == false);
+  }
+
+  SECTION("support std::string") {
+    REQUIRE(var.containsKey("hello"_s) == true);
+    REQUIRE(var.containsKey("world"_s) == false);
+  }
+
+#ifdef HAS_VARIABLE_LENGTH_ARRAY
+  SECTION("supports VLA") {
+    size_t i = 16;
+    char vla[i];
+    strcpy(vla, "hello");
+
+    REQUIRE(true == var.containsKey(vla));
+  }
+#endif
+
+  SECTION("support JsonVariant") {
+    doc["key"] = "hello";
+    REQUIRE(var.containsKey(var["key"]) == true);
+    REQUIRE(var.containsKey(var["foo"]) == false);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/is.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/is.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..24f932612b5b0db3c06dd6f9c3676a45cb26af48
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/is.cpp
@@ -0,0 +1,162 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+enum MYENUM2 { ONE = 1, TWO = 2 };
+
+TEST_CASE("JsonVariantConst::is<T>()") {
+  JsonDocument doc;
+  JsonVariantConst var = doc.to<JsonVariant>();
+
+  SECTION("unbound") {
+    var = JsonVariantConst();
+
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<JsonArrayConst>() == false);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonObjectConst>() == false);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<JsonVariantConst>() == false);
+    CHECK(var.is<bool>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<std::string>() == false);
+    CHECK(var.is<JsonString>() == false);
+    CHECK(var.is<float>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+
+  SECTION("null") {
+    CHECK(var.is<JsonVariantConst>() == true);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<bool>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<std::string>() == false);
+    CHECK(var.is<JsonString>() == false);
+    CHECK(var.is<float>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+
+  SECTION("true") {
+    doc.set(true);
+
+    CHECK(var.is<bool>() == true);
+    CHECK(var.is<JsonVariantConst>() == true);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<std::string>() == false);
+    CHECK(var.is<JsonString>() == false);
+    CHECK(var.is<float>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+
+  SECTION("false") {
+    doc.set(false);
+
+    CHECK(var.is<bool>() == true);
+    CHECK(var.is<JsonVariantConst>() == true);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<std::string>() == false);
+    CHECK(var.is<JsonString>() == false);
+    CHECK(var.is<float>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+
+  SECTION("int") {
+    doc.set(42);
+
+    CHECK(var.is<int>() == true);
+    CHECK(var.is<short>() == true);
+    CHECK(var.is<long>() == true);
+    CHECK(var.is<double>() == true);
+    CHECK(var.is<float>() == true);
+    CHECK(var.is<MYENUM2>() == true);
+    CHECK(var.is<JsonVariantConst>() == true);
+    CHECK(var.is<bool>() == false);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<std::string>() == false);
+    CHECK(var.is<JsonString>() == false);
+  }
+
+  SECTION("double") {
+    doc.set(4.2);
+
+    CHECK(var.is<double>() == true);
+    CHECK(var.is<float>() == true);
+    CHECK(var.is<JsonVariantConst>() == true);
+    CHECK(var.is<bool>() == false);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<std::string>() == false);
+    CHECK(var.is<JsonString>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+
+  SECTION("const char*") {
+    doc.set("4.2");
+
+    CHECK(var.is<const char*>() == true);
+    CHECK(var.is<const char*>() == true);
+    CHECK(var.is<std::string>() == true);
+    CHECK(var.is<JsonString>() == true);
+    CHECK(var.is<double>() == false);
+    CHECK(var.is<float>() == false);
+    CHECK(var.is<bool>() == false);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+
+  SECTION("JsonArray") {
+    doc.to<JsonArray>();
+
+    CHECK(var.is<JsonArrayConst>() == true);
+    CHECK(var.is<JsonVariantConst>() == true);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonObjectConst>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<float>() == false);
+    CHECK(var.is<bool>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+
+  SECTION("JsonObject") {
+    doc.to<JsonObject>();
+
+    CHECK(var.is<JsonObjectConst>() == true);
+    CHECK(var.is<JsonVariantConst>() == true);
+    CHECK(var.is<JsonObject>() == false);
+    CHECK(var.is<JsonVariant>() == false);
+    CHECK(var.is<JsonArray>() == false);
+    CHECK(var.is<JsonArrayConst>() == false);
+    CHECK(var.is<int>() == false);
+    CHECK(var.is<float>() == false);
+    CHECK(var.is<bool>() == false);
+    CHECK(var.is<const char*>() == false);
+    CHECK(var.is<MYENUM2>() == false);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/isnull.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/isnull.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..14572c7a2012770e152d78118b73ddcbb5128d5b
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/isnull.cpp
@@ -0,0 +1,21 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonVariantConst::isNull()") {
+  JsonDocument doc;
+  JsonVariantConst variant = doc.to<JsonVariant>();
+
+  SECTION("returns true when undefined") {
+    REQUIRE(variant.isNull() == true);
+  }
+
+  SECTION("returns false if value is integer") {
+    doc.set(42);
+
+    REQUIRE(variant.isNull() == false);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/nesting.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/nesting.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..827f766bcc4e17943c56ed6e125768fef54b6751
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/nesting.cpp
@@ -0,0 +1,31 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonVariantConst::nesting()") {
+  JsonDocument doc;
+  JsonVariantConst var = doc.to<JsonVariant>();
+
+  SECTION("return 0 if unbound") {
+    JsonVariantConst unbound;
+    REQUIRE(unbound.nesting() == 0);
+  }
+
+  SECTION("returns 0 for string") {
+    doc.set("hello");
+    REQUIRE(var.nesting() == 0);
+  }
+
+  SECTION("returns 1 for empty object") {
+    doc.to<JsonObject>();
+    REQUIRE(var.nesting() == 1);
+  }
+
+  SECTION("returns 1 for empty array") {
+    doc.to<JsonArray>();
+    REQUIRE(var.nesting() == 1);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/size.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c047951ba3b4bc545118fae64e369fac028ff3fe
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/size.cpp
@@ -0,0 +1,36 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonVariantConst::size()") {
+  JsonDocument doc;
+  JsonVariantConst variant = doc.to<JsonVariant>();
+
+  SECTION("unbound reference") {
+    JsonVariantConst unbound;
+
+    CHECK(unbound.size() == 0);
+  }
+
+  SECTION("int") {
+    doc.set(42);
+
+    CHECK(variant.size() == 0);
+  }
+
+  SECTION("string") {
+    doc.set("hello");
+
+    CHECK(variant.size() == 0);
+  }
+
+  SECTION("object") {
+    doc["a"] = 1;
+    doc["b"] = 2;
+
+    CHECK(variant.size() == 2);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/subscript.cpp b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/subscript.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..67ac98ead5d5c894593902502dc3aa16ffc5b6a0
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/JsonVariantConst/subscript.cpp
@@ -0,0 +1,86 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include "Literals.hpp"
+
+TEST_CASE("JsonVariantConst::operator[]") {
+  JsonDocument doc;
+  JsonVariantConst var = doc.to<JsonVariant>();
+
+  SECTION("null") {
+    REQUIRE(0 == var.size());
+    REQUIRE(var["0"].isNull());
+    REQUIRE(var[0].isNull());
+  }
+
+  SECTION("string") {
+    doc.set("hello world");
+    REQUIRE(0 == var.size());
+    REQUIRE(var["0"].isNull());
+    REQUIRE(var[0].isNull());
+  }
+
+  SECTION("array") {
+    JsonArray array = doc.to<JsonArray>();
+    array.add("A");
+    array.add("B");
+
+    SECTION("int") {
+      REQUIRE("A"_s == var[0]);
+      REQUIRE("B"_s == var[1]);
+      REQUIRE("A"_s == var[static_cast<unsigned char>(0)]);  // issue #381
+      REQUIRE(var[666].isNull());
+      REQUIRE(var[3].isNull());
+    }
+
+    SECTION("const char*") {
+      REQUIRE(var["0"].isNull());
+    }
+
+    SECTION("JsonVariant") {
+      array.add(1);
+      REQUIRE(var[var[2]] == "B"_s);
+      REQUIRE(var[var[3]].isNull());
+    }
+  }
+
+  SECTION("object") {
+    JsonObject object = doc.to<JsonObject>();
+    object["a"] = "A";
+    object["b"] = "B";
+
+    SECTION("supports const char*") {
+      REQUIRE("A"_s == var["a"]);
+      REQUIRE("B"_s == var["b"]);
+      REQUIRE(var["c"].isNull());
+      REQUIRE(var[0].isNull());
+    }
+
+    SECTION("supports std::string") {
+      REQUIRE("A"_s == var["a"_s]);
+      REQUIRE("B"_s == var["b"_s]);
+      REQUIRE(var["c"_s].isNull());
+    }
+
+#if defined(HAS_VARIABLE_LENGTH_ARRAY) && \
+    !defined(SUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR)
+    SECTION("supports VLA") {
+      size_t i = 16;
+      char vla[i];
+      strcpy(vla, "a");
+
+      REQUIRE("A"_s == var[vla]);
+    }
+#endif
+
+    SECTION("supports JsonVariant") {
+      object["c"] = "b";
+      REQUIRE(var[var["c"]] == "B");
+      REQUIRE(var[var["d"]].isNull());
+    }
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/CMakeLists.txt
deleted file mode 100644
index 2f809cc26d5fa5d7e2677ac5aff3c06f46f95960..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
-# MIT License
-
-add_executable(MemoryPoolTests
-	allocVariant.cpp
-	clear.cpp
-	saveString.cpp
-	size.cpp
-	StringCopier.cpp
-)
-
-add_test(MemoryPool MemoryPoolTests)
-
-set_tests_properties(MemoryPool
-	PROPERTIES
-		LABELS "Catch"
-)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/StringCopier.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/StringCopier.cpp
deleted file mode 100644
index 0342bc4fc4af31c8f16068ea6ca4002bf35aff89..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/StringCopier.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson/StringStorage/StringCopier.hpp>
-#include <catch.hpp>
-
-using namespace ArduinoJson::detail;
-
-TEST_CASE("StringCopier") {
-  char buffer[4096];
-
-  SECTION("Works when buffer is big enough") {
-    MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(5)));
-    StringCopier str(&pool);
-
-    str.startString();
-    str.append("hello");
-
-    REQUIRE(str.isValid() == true);
-    REQUIRE(str.str() == "hello");
-    REQUIRE(pool.overflowed() == false);
-  }
-
-  SECTION("Returns null when too small") {
-    MemoryPool pool(buffer, sizeof(void*));
-    StringCopier str(&pool);
-
-    str.startString();
-    str.append("hello world!");
-
-    REQUIRE(str.isValid() == false);
-    REQUIRE(pool.overflowed() == true);
-  }
-
-  SECTION("Increases size of memory pool") {
-    MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6)));
-    StringCopier str(&pool);
-
-    str.startString();
-    str.save();
-
-    REQUIRE(1 == pool.size());
-    REQUIRE(pool.overflowed() == false);
-  }
-
-  SECTION("Works when memory pool is 0 bytes") {
-    MemoryPool pool(buffer, 0);
-    StringCopier str(&pool);
-
-    str.startString();
-    REQUIRE(str.isValid() == false);
-    REQUIRE(pool.overflowed() == true);
-  }
-}
-
-static const char* addStringToPool(MemoryPool& pool, const char* s) {
-  StringCopier str(&pool);
-  str.startString();
-  str.append(s);
-  return str.save().c_str();
-}
-
-TEST_CASE("StringCopier::save() deduplicates strings") {
-  char buffer[4096];
-  MemoryPool pool(buffer, 4096);
-
-  SECTION("Basic") {
-    const char* s1 = addStringToPool(pool, "hello");
-    const char* s2 = addStringToPool(pool, "world");
-    const char* s3 = addStringToPool(pool, "hello");
-
-    REQUIRE(s1 == s3);
-    REQUIRE(s2 != s3);
-    REQUIRE(pool.size() == 12);
-  }
-
-  SECTION("Requires terminator") {
-    const char* s1 = addStringToPool(pool, "hello world");
-    const char* s2 = addStringToPool(pool, "hello");
-
-    REQUIRE(s2 != s1);
-    REQUIRE(pool.size() == 12 + 6);
-  }
-
-  SECTION("Don't overrun") {
-    const char* s1 = addStringToPool(pool, "hello world");
-    const char* s2 = addStringToPool(pool, "wor");
-
-    REQUIRE(s2 != s1);
-    REQUIRE(pool.size() == 12 + 4);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/allocVariant.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/allocVariant.cpp
deleted file mode 100644
index 5123a4e25eb53711e917efc3401d1769d6fec1b3..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/allocVariant.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson/Memory/MemoryPool.hpp>
-#include <catch.hpp>
-
-using namespace ArduinoJson::detail;
-
-TEST_CASE("MemoryPool::allocVariant()") {
-  char buffer[4096];
-
-  SECTION("Returns different pointer") {
-    MemoryPool pool(buffer, sizeof(buffer));
-
-    VariantSlot* s1 = pool.allocVariant();
-    REQUIRE(s1 != 0);
-    VariantSlot* s2 = pool.allocVariant();
-    REQUIRE(s2 != 0);
-
-    REQUIRE(s1 != s2);
-  }
-
-  SECTION("Returns aligned pointers") {
-    MemoryPool pool(buffer, sizeof(buffer));
-
-    REQUIRE(isAligned(pool.allocVariant()));
-    REQUIRE(isAligned(pool.allocVariant()));
-  }
-
-  SECTION("Returns zero if capacity is 0") {
-    MemoryPool pool(buffer, 0);
-
-    REQUIRE(pool.allocVariant() == 0);
-  }
-
-  SECTION("Returns zero if buffer is null") {
-    MemoryPool pool(0, sizeof(buffer));
-
-    REQUIRE(pool.allocVariant() == 0);
-  }
-
-  SECTION("Returns zero if capacity is insufficient") {
-    MemoryPool pool(buffer, sizeof(VariantSlot));
-
-    pool.allocVariant();
-
-    REQUIRE(pool.allocVariant() == 0);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/clear.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/clear.cpp
deleted file mode 100644
index 9e33b4718c8fdce2589080bd8b95e151e0747a2d..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/clear.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson/Memory/MemoryPool.hpp>
-#include <ArduinoJson/Strings/StringAdapters.hpp>
-#include <catch.hpp>
-
-using namespace ArduinoJson::detail;
-
-static const size_t poolCapacity = 512;
-
-TEST_CASE("MemoryPool::clear()") {
-  char buffer[poolCapacity];
-  MemoryPool pool(buffer, sizeof(buffer));
-
-  SECTION("Discards allocated variants") {
-    pool.allocVariant();
-
-    pool.clear();
-    REQUIRE(pool.size() == 0);
-  }
-
-  SECTION("Discards allocated strings") {
-    pool.saveString(adaptString(const_cast<char*>("123456789")));
-    REQUIRE(pool.size() == 10);
-
-    pool.clear();
-
-    REQUIRE(pool.size() == 0);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/saveString.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/saveString.cpp
deleted file mode 100644
index ba8d5b91ba53759750088079a76fa2bf3a3e9519..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/saveString.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson/Memory/MemoryPool.hpp>
-#include <ArduinoJson/Strings/StringAdapters.hpp>
-#include <catch.hpp>
-
-using namespace ArduinoJson::detail;
-
-static const char* saveString(MemoryPool& pool, const char* s) {
-  return pool.saveString(adaptString(const_cast<char*>(s)));
-}
-
-static const char* saveString(MemoryPool& pool, const char* s, size_t n) {
-  return pool.saveString(adaptString(s, n));
-}
-
-TEST_CASE("MemoryPool::saveString()") {
-  char buffer[32];
-  MemoryPool pool(buffer, 32);
-
-  SECTION("Duplicates different strings") {
-    const char* a = saveString(pool, "hello");
-    const char* b = saveString(pool, "world");
-    REQUIRE(a != b);
-    REQUIRE(pool.size() == 6 + 6);
-  }
-
-  SECTION("Deduplicates identical strings") {
-    const char* a = saveString(pool, "hello");
-    const char* b = saveString(pool, "hello");
-    REQUIRE(a == b);
-    REQUIRE(pool.size() == 6);
-  }
-
-  SECTION("Deduplicates identical strings that contain NUL") {
-    const char* a = saveString(pool, "hello\0world", 11);
-    const char* b = saveString(pool, "hello\0world", 11);
-    REQUIRE(a == b);
-    REQUIRE(pool.size() == 12);
-  }
-
-  SECTION("Reuse part of a string if it ends with NUL") {
-    const char* a = saveString(pool, "hello\0world", 11);
-    const char* b = saveString(pool, "hello");
-    REQUIRE(a == b);
-    REQUIRE(pool.size() == 12);
-  }
-
-  SECTION("Don't stop on first NUL") {
-    const char* a = saveString(pool, "hello");
-    const char* b = saveString(pool, "hello\0world", 11);
-    REQUIRE(a != b);
-    REQUIRE(pool.size() == 18);
-  }
-
-  SECTION("Returns NULL when full") {
-    REQUIRE(pool.capacity() == 32);
-
-    const void* p1 = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-    REQUIRE(p1 != 0);
-    REQUIRE(pool.size() == 32);
-
-    const void* p2 = saveString(pool, "b");
-    REQUIRE(p2 == 0);
-  }
-
-  SECTION("Returns NULL when pool is too small") {
-    const void* p = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-    REQUIRE(0 == p);
-  }
-
-  SECTION("Returns NULL when buffer is NULL") {
-    MemoryPool pool2(0, 32);
-    REQUIRE(0 == saveString(pool2, "a"));
-  }
-
-  SECTION("Returns NULL when capacity is 0") {
-    MemoryPool pool2(buffer, 0);
-    REQUIRE(0 == saveString(pool2, "a"));
-  }
-
-  SECTION("Returns same address after clear()") {
-    const void* a = saveString(pool, "hello");
-    pool.clear();
-    const void* b = saveString(pool, "world");
-
-    REQUIRE(a == b);
-  }
-
-  SECTION("Can use full capacity when fresh") {
-    const void* a = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-
-    REQUIRE(a != 0);
-  }
-
-  SECTION("Can use full capacity after clear") {
-    saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-    pool.clear();
-
-    const void* a = saveString(pool, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
-
-    REQUIRE(a != 0);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/size.cpp
deleted file mode 100644
index 057f4ab30fa3a5b84791fbe561664b143788feea..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MemoryPool/size.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson/Memory/MemoryPool.hpp>
-#include <catch.hpp>
-
-using namespace ArduinoJson::detail;
-
-TEST_CASE("MemoryPool::capacity()") {
-  char buffer[4096];
-  const size_t capacity = 64;
-  MemoryPool pool(buffer, capacity);
-  REQUIRE(capacity == pool.capacity());
-}
-
-TEST_CASE("MemoryPool::size()") {
-  char buffer[4096];
-  MemoryPool pool(buffer, sizeof(buffer));
-
-  SECTION("Initial size is 0") {
-    REQUIRE(0 == pool.size());
-  }
-
-  SECTION("Doesn't grow when memory pool is full") {
-    const size_t variantCount = sizeof(buffer) / sizeof(VariantSlot);
-
-    for (size_t i = 0; i < variantCount; i++)
-      pool.allocVariant();
-    size_t size = pool.size();
-
-    pool.allocVariant();
-
-    REQUIRE(size == pool.size());
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/Misc/CMakeLists.txt
index 3a1eb898e4143c77ce7191db2f0b661e08147c7b..2fdc6138cfeea938dc073bcde6075da2144b21e0 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(MiscTests
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/FloatParts.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/FloatParts.cpp
index 8e3d82e15413d683273dfe95431053adfe7c4593..e75a88db37c5e11d365247443800e6ce33f3bdfb 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/FloatParts.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/FloatParts.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson/Numbers/FloatParts.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/JsonString.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/JsonString.cpp
index c3dca1728dc2ec4c1bde1a71fe1a014f69d932a5..6b9ac5e1051acdde8cff76a33a3af9906815f592 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/JsonString.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/JsonString.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp
index 4b0218bff50c2dd203abf8d5403fad653f373291..b5891270b3f1ce4efa18e6660eb26bcf31ff5140 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINO 1
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/Readers.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/Readers.cpp
index 0b8f4d5025ce5a49fc38297759dd76d4363a6050..ffb6355074bc1b430b7f8aaf5b26f674d947d81f 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/Readers.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/Readers.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <Arduino.h>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringAdapters.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringAdapters.cpp
index 1c77e08bf76f08fed9c84f4efb991c825eeb1cd4..3e5c2b6d061e9bd3ba822cbd2b7ed9acb5b8c601 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringAdapters.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringAdapters.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <Arduino.h>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringWriter.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringWriter.cpp
index 81cb88e924b2fc4a3fb8cabf6edf3a7f0a76bf7f..3bf60ece848e4f66c7ca6e71eb212e14d8b126fa 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringWriter.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/StringWriter.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <Arduino.h>
@@ -9,6 +9,7 @@
 
 #include <catch.hpp>
 
+#include "Literals.hpp"
 #include "custom_string.hpp"
 
 using namespace ArduinoJson::detail;
@@ -36,13 +37,13 @@ void common_tests(StringWriter& writer, const String& output) {
 
   SECTION("OneString") {
     REQUIRE(4 == print(writer, "ABCD"));
-    REQUIRE(std::string("ABCD") == output);
+    REQUIRE("ABCD"_s == output);
   }
 
   SECTION("TwoStrings") {
     REQUIRE(4 == print(writer, "ABCD"));
     REQUIRE(4 == print(writer, "EFGH"));
-    REQUIRE(std::string("ABCDEFGH") == output);
+    REQUIRE("ABCDEFGH"_s == output);
   }
 }
 
@@ -139,9 +140,9 @@ TEST_CASE("Writer<custom_string>") {
 }
 
 TEST_CASE("serializeJson(doc, String)") {
-  StaticJsonDocument<1024> doc;
+  JsonDocument doc;
   doc["hello"] = "world";
-  ::String output;
+  ::String output = "erase me";
 
   SECTION("sufficient capacity") {
     serializeJson(doc, output);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/TypeTraits.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/TypeTraits.cpp
index 10b6168b598294fb7466fe1d21ee200bc9e4b047..7beb46585fb7585ef4d6353bea98aeca9e45f357 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/TypeTraits.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/TypeTraits.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -193,9 +193,7 @@ TEST_CASE("Polyfills/type_traits") {
     CHECK(is_convertible<MemberProxy<JsonObject, const char*>,
                          JsonVariantConst>::value == true);
     CHECK(is_convertible<JsonObjectConst, JsonVariantConst>::value == true);
-    CHECK(is_convertible<DynamicJsonDocument, JsonVariantConst>::value == true);
-    CHECK(is_convertible<StaticJsonDocument<10>, JsonVariantConst>::value ==
-          true);
+    CHECK(is_convertible<JsonDocument, JsonVariantConst>::value == true);
   }
 
   SECTION("is_class") {
@@ -214,3 +212,8 @@ TEST_CASE("Polyfills/type_traits") {
     CHECK(is_enum<double>::value == false);
   }
 }
+
+TEST_CASE("is_std_string") {
+  REQUIRE(is_std_string<std::string>::value == true);
+  REQUIRE(is_std_string<EmptyClass>::value == false);
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf16.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf16.cpp
index 3a595910522547207d8e0f6c63b50fcfea0171d4..4b19fdb0ba124461a582fb422ae61ce8eb8f78b2 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf16.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf16.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson/Json/Utf16.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf8.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf8.cpp
index 0c62033d29744d1e0994dd5530b234d4d590cf66..3a602816d08b8ded7912af68b54a5f2a51c76403 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf8.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/Utf8.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -10,9 +10,8 @@
 using namespace ArduinoJson::detail;
 
 static void testCodepoint(uint32_t codepoint, std::string expected) {
-  char buffer[4096];
-  MemoryPool pool(buffer, 4096);
-  StringCopier str(&pool);
+  ResourceManager resources;
+  StringBuilder str(&resources);
   str.startString();
 
   CAPTURE(codepoint);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp
index 2d51754a151ceaee640b5ac8a586f18de1d485a4..a87950033f968b5f9afc4f7366cea017dfa95c33 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson/Numbers/arithmeticCompare.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/conflicts.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/conflicts.cpp
index 75c6c11df8aafed5c6f49cb9832778108c86033e..5ce6823c6f5b34c909eae24c008f383d28c8bdf5 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/conflicts.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/conflicts.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 // Include any header that might use the conflicting macros
@@ -56,7 +56,12 @@
 #define _current
 
 // issue #1914
-#define V6 6
+#define V7 7
+
+// STM32, Mbed, Particle
+#define A0 16
+#define A1 17
+#define A2 18
 
 // catch.hpp mutes several warnings, this file also allows to detect them
 #include "ArduinoJson.h"
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/custom_string.hpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/custom_string.hpp
index 4a6a5a73523988afeeeebab4163bb24b560431f0..acc67521dc3582250bbe0b80a7260df62b65d7e9 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/custom_string.hpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/custom_string.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -7,6 +7,5 @@
 #include <string>
 
 struct custom_char_traits : std::char_traits<char> {};
-struct custom_allocator : std::allocator<char> {};
-typedef std::basic_string<char, custom_char_traits, custom_allocator>
-    custom_string;
+
+typedef std::basic_string<char, custom_char_traits> custom_string;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/issue1967.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/issue1967.cpp
index a8d6b948d7ff7e7e06c3e1cad470f5140c10ff19..833fcf07eecafa19a8b8c3861b29cbccd2398ae9 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/issue1967.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/issue1967.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 // we expect ArduinoJson.h to include <string>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/printable.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/printable.cpp
index 09c23ad18a06a9edf0736829228e5280605045a0..6062cce696abf414d6af5148ff72bac9430659ce 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/printable.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/printable.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <Arduino.h>
@@ -8,6 +8,10 @@
 #define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1
 #include <ArduinoJson.h>
 
+#include "Allocators.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+
 struct PrintOneCharacterAtATime {
   static size_t printStringTo(const std::string& s, Print& p) {
     size_t result = 0;
@@ -48,8 +52,9 @@ struct PrintableString : public Printable {
 
 TEST_CASE("Printable") {
   SECTION("Doesn't overflow") {
-    StaticJsonDocument<8> doc;
-    const char* value = "example";  // == 7 chars
+    SpyingAllocator spy;
+    JsonDocument doc(&spy);
+    const char* value = "example";
 
     doc.set(666);  // to make sure we override the value
 
@@ -59,8 +64,11 @@ TEST_CASE("Printable") {
       CHECK(doc.as<std::string>() == value);
       CHECK(printable.totalBytesWritten() == 7);
       CHECK(doc.overflowed() == false);
-      CHECK(doc.memoryUsage() == 8);
-      CHECK(doc.as<JsonVariant>().memoryUsage() == 8);
+      CHECK(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                Reallocate(sizeofStringBuffer(), sizeofString("example")),
+            });
     }
 
     SECTION("Via Print::write(const char* size_t)") {
@@ -69,58 +77,90 @@ TEST_CASE("Printable") {
       CHECK(doc.as<std::string>() == value);
       CHECK(printable.totalBytesWritten() == 7);
       CHECK(doc.overflowed() == false);
-      CHECK(doc.memoryUsage() == 8);
-      CHECK(doc.as<JsonVariant>().memoryUsage() == 8);
+      CHECK(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                Reallocate(sizeofStringBuffer(), sizeofString("example")),
+            });
     }
   }
 
-  SECTION("Overflows early") {
-    StaticJsonDocument<8> doc;
-    const char* value = "hello world";  // > 8 chars
+  SECTION("First allocation fails") {
+    SpyingAllocator spy(FailingAllocator::instance());
+    JsonDocument doc(&spy);
+    const char* value = "hello world";
 
     doc.set(666);  // to make sure we override the value
 
     SECTION("Via Print::write(char)") {
       PrintableString<PrintOneCharacterAtATime> printable(value);
-      CHECK(doc.set(printable) == false);
+
+      bool success = doc.set(printable);
+
+      CHECK(success == false);
       CHECK(doc.isNull());
-      CHECK(printable.totalBytesWritten() == 8);
+      CHECK(printable.totalBytesWritten() == 0);
       CHECK(doc.overflowed() == true);
-      CHECK(doc.memoryUsage() == 0);
+      CHECK(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofStringBuffer()),
+                         });
     }
 
     SECTION("Via Print::write(const char*, size_t)") {
       PrintableString<PrintAllAtOnce> printable(value);
-      CHECK(doc.set(printable) == false);
+
+      bool success = doc.set(printable);
+
+      CHECK(success == false);
       CHECK(doc.isNull());
       CHECK(printable.totalBytesWritten() == 0);
       CHECK(doc.overflowed() == true);
-      CHECK(doc.memoryUsage() == 0);
+      CHECK(spy.log() == AllocatorLog{
+                             AllocateFail(sizeofStringBuffer()),
+                         });
     }
   }
 
-  SECTION("Overflows adding terminator") {
-    StaticJsonDocument<8> doc;
-    const char* value = "overflow";  // == 8 chars
+  SECTION("Reallocation fails") {
+    TimebombAllocator timebomb(1);
+    SpyingAllocator spy(&timebomb);
+    JsonDocument doc(&spy);
+    const char* value = "Lorem ipsum dolor sit amet, cons";  // > 31 chars
 
     doc.set(666);  // to make sure we override the value
 
     SECTION("Via Print::write(char)") {
       PrintableString<PrintOneCharacterAtATime> printable(value);
-      CHECK(doc.set(printable) == false);
+
+      bool success = doc.set(printable);
+
+      CHECK(success == false);
       CHECK(doc.isNull());
-      CHECK(printable.totalBytesWritten() == 8);
+      CHECK(printable.totalBytesWritten() == 31);
       CHECK(doc.overflowed() == true);
-      CHECK(doc.memoryUsage() == 0);
+      CHECK(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                ReallocateFail(sizeofStringBuffer(), sizeofStringBuffer(2)),
+                Deallocate(sizeofStringBuffer()),
+            });
     }
 
     SECTION("Via Print::write(const char*, size_t)") {
       PrintableString<PrintAllAtOnce> printable(value);
-      CHECK(doc.set(printable) == false);
+
+      bool success = doc.set(printable);
+
+      CHECK(success == false);
       CHECK(doc.isNull());
-      CHECK(printable.totalBytesWritten() == 0);
+      CHECK(printable.totalBytesWritten() == 31);
       CHECK(doc.overflowed() == true);
-      CHECK(doc.memoryUsage() == 0);
+      CHECK(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                ReallocateFail(sizeofStringBuffer(), sizeofStringBuffer(2)),
+                Deallocate(sizeofStringBuffer()),
+            });
     }
   }
 
@@ -133,12 +173,20 @@ TEST_CASE("Printable") {
   }
 
   SECTION("String deduplication") {
-    StaticJsonDocument<128> doc;
+    SpyingAllocator spy;
+    JsonDocument doc(&spy);
     doc.add(PrintableString<PrintOneCharacterAtATime>("Hello World!"));
     doc.add(PrintableString<PrintAllAtOnce>("Hello World!"));
     REQUIRE(doc.size() == 2);
     CHECK(doc[0] == "Hello World!");
     CHECK(doc[1] == "Hello World!");
-    CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 13);
+    CHECK(spy.log() ==
+          AllocatorLog{
+              Allocate(sizeofPool()),
+              Allocate(sizeofStringBuffer()),
+              Reallocate(sizeofStringBuffer(), sizeofString("Hello World!")),
+              Allocate(sizeofStringBuffer()),
+              Deallocate(sizeofStringBuffer()),
+          });
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/unsigned_char.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/unsigned_char.cpp
index d3ee492a3decf25589b307246b86904ba5ac2783..fd780d62a17214b2466cb419deac0f268690f76e 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/unsigned_char.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/unsigned_char.cpp
@@ -1,10 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 #if defined(__clang__)
 #  define CONFLICTS_WITH_BUILTIN_OPERATOR
 #endif
@@ -13,7 +15,7 @@ TEST_CASE("unsigned char[]") {
   SECTION("deserializeJson()") {
     unsigned char input[] = "{\"a\":42}";
 
-    StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc;
+    JsonDocument doc;
     DeserializationError err = deserializeJson(doc, input);
 
     REQUIRE(err == DeserializationError::Ok);
@@ -22,7 +24,7 @@ TEST_CASE("unsigned char[]") {
   SECTION("deserializeMsgPack()") {
     unsigned char input[] = "\xDE\x00\x01\xA5Hello\xA5world";
 
-    StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+    JsonDocument doc;
     DeserializationError err = deserializeMsgPack(doc, input);
 
     REQUIRE(err == DeserializationError::Ok);
@@ -30,7 +32,7 @@ TEST_CASE("unsigned char[]") {
 
   SECTION("serializeMsgPack(unsigned char[])") {
     unsigned char buffer[32];
-    StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+    JsonDocument doc;
     doc["hello"] = "world";
 
     size_t n = serializeMsgPack(doc, buffer);
@@ -41,7 +43,7 @@ TEST_CASE("unsigned char[]") {
 
   SECTION("serializeMsgPack(unsigned char*)") {
     unsigned char buffer[32];
-    StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+    JsonDocument doc;
     doc["hello"] = "world";
 
     size_t n = serializeMsgPack(doc, buffer, sizeof(buffer));
@@ -52,7 +54,7 @@ TEST_CASE("unsigned char[]") {
 
   SECTION("serializeJson(unsigned char[])") {
     unsigned char buffer[32];
-    StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+    JsonDocument doc;
     doc["hello"] = "world";
 
     size_t n = serializeJson(doc, buffer);
@@ -63,7 +65,7 @@ TEST_CASE("unsigned char[]") {
 
   SECTION("serializeJson(unsigned char*)") {
     unsigned char buffer[32];
-    StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+    JsonDocument doc;
     doc["hello"] = "world";
 
     size_t n = serializeJson(doc, buffer, sizeof(buffer));
@@ -74,7 +76,7 @@ TEST_CASE("unsigned char[]") {
 
   SECTION("serializeJsonPretty(unsigned char[])") {
     unsigned char buffer[32];
-    StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+    JsonDocument doc;
     doc["hello"] = "world";
 
     size_t n = serializeJsonPretty(doc, buffer);
@@ -84,7 +86,7 @@ TEST_CASE("unsigned char[]") {
 
   SECTION("serializeJsonPretty(unsigned char*)") {
     unsigned char buffer[32];
-    StaticJsonDocument<JSON_OBJECT_SIZE(2)> doc;
+    JsonDocument doc;
     doc["hello"] = "world";
 
     size_t n = serializeJsonPretty(doc, buffer, sizeof(buffer));
@@ -93,7 +95,7 @@ TEST_CASE("unsigned char[]") {
   }
 
   SECTION("JsonVariant") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
 
     SECTION("set") {
       unsigned char value[] = "42";
@@ -111,7 +113,7 @@ TEST_CASE("unsigned char[]") {
       deserializeJson(doc, "{\"hello\":\"world\"}");
       JsonVariant variant = doc.as<JsonVariant>();
 
-      REQUIRE(std::string("world") == variant[key]);
+      REQUIRE("world"_s == variant[key]);
     }
 #endif
 
@@ -122,7 +124,7 @@ TEST_CASE("unsigned char[]") {
       deserializeJson(doc, "{\"hello\":\"world\"}");
       const JsonVariant variant = doc.as<JsonVariant>();
 
-      REQUIRE(std::string("world") == variant[key]);
+      REQUIRE("world"_s == variant[key]);
     }
 #endif
 
@@ -156,28 +158,28 @@ TEST_CASE("unsigned char[]") {
     SECTION("operator[]") {
       unsigned char key[] = "hello";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       JsonObject obj = doc.to<JsonObject>();
       obj[key] = "world";
 
-      REQUIRE(std::string("world") == obj["hello"]);
+      REQUIRE("world"_s == obj["hello"]);
     }
 
     SECTION("JsonObject::operator[] const") {
       unsigned char key[] = "hello";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       deserializeJson(doc, "{\"hello\":\"world\"}");
 
       JsonObject obj = doc.as<JsonObject>();
-      REQUIRE(std::string("world") == obj[key]);
+      REQUIRE("world"_s == obj[key]);
     }
 #endif
 
     SECTION("containsKey()") {
       unsigned char key[] = "hello";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       deserializeJson(doc, "{\"hello\":\"world\"}");
       JsonObject obj = doc.as<JsonObject>();
       REQUIRE(true == obj.containsKey(key));
@@ -186,50 +188,34 @@ TEST_CASE("unsigned char[]") {
     SECTION("remove()") {
       unsigned char key[] = "hello";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       deserializeJson(doc, "{\"hello\":\"world\"}");
       JsonObject obj = doc.as<JsonObject>();
       obj.remove(key);
 
       REQUIRE(0 == obj.size());
     }
-
-    SECTION("createNestedArray()") {
-      unsigned char key[] = "hello";
-
-      DynamicJsonDocument doc(4096);
-      JsonObject obj = doc.to<JsonObject>();
-      obj.createNestedArray(key);
-    }
-
-    SECTION("createNestedObject()") {
-      unsigned char key[] = "hello";
-
-      DynamicJsonDocument doc(4096);
-      JsonObject obj = doc.to<JsonObject>();
-      obj.createNestedObject(key);
-    }
   }
 
   SECTION("MemberProxy") {
     SECTION("operator=") {  // issue #416
       unsigned char value[] = "world";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       JsonObject obj = doc.to<JsonObject>();
       obj["hello"] = value;
 
-      REQUIRE(std::string("world") == obj["hello"]);
+      REQUIRE("world"_s == obj["hello"]);
     }
 
     SECTION("set()") {
       unsigned char value[] = "world";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       JsonObject obj = doc.to<JsonObject>();
       obj["hello"].set(value);
 
-      REQUIRE(std::string("world") == obj["hello"]);
+      REQUIRE("world"_s == obj["hello"]);
     }
   }
 
@@ -237,11 +223,11 @@ TEST_CASE("unsigned char[]") {
     SECTION("add()") {
       unsigned char value[] = "world";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       JsonArray arr = doc.to<JsonArray>();
       arr.add(value);
 
-      REQUIRE(std::string("world") == arr[0]);
+      REQUIRE("world"_s == arr[0]);
     }
   }
 
@@ -249,23 +235,23 @@ TEST_CASE("unsigned char[]") {
     SECTION("set()") {
       unsigned char value[] = "world";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       JsonArray arr = doc.to<JsonArray>();
       arr.add("hello");
       arr[0].set(value);
 
-      REQUIRE(std::string("world") == arr[0]);
+      REQUIRE("world"_s == arr[0]);
     }
 
     SECTION("operator=") {
       unsigned char value[] = "world";
 
-      DynamicJsonDocument doc(4096);
+      JsonDocument doc;
       JsonArray arr = doc.to<JsonArray>();
       arr.add("hello");
       arr[0] = value;
 
-      REQUIRE(std::string("world") == arr[0]);
+      REQUIRE("world"_s == arr[0]);
     }
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/version.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/version.cpp
index a91f159a1a61e5a404ed783bf7359b4374c9ec7d..2256fd2845ca587e30e41342e495f9ee4b59ef11 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/version.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/version.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson/version.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp b/neosensor/libraries/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp
index 5463d7ec21b913a6fe7d86c4d7c340848457f717..c0e5c6c5bbf566d198ebcf15d7d096346a257432 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson/Namespace.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt
index c2c9dc5caa506354238e5a463265e22ab305075f..b8cad8e598eeb8024643627be2ecef0c00538645 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(MixedConfigurationTests
@@ -14,9 +14,10 @@ add_executable(MixedConfigurationTests
 	enable_nan_0.cpp
 	enable_nan_1.cpp
 	enable_progmem_1.cpp
-	enable_string_deduplication_0.cpp
-	enable_string_deduplication_1.cpp
 	issue1707.cpp
+	string_length_size_1.cpp
+	string_length_size_2.cpp
+	string_length_size_4.cpp
 	use_double_0.cpp
 	use_double_1.cpp
 	use_long_long_0.cpp
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp
index b5dc1f179b7b1a2725ecdab0d9ade44f88ef3a46..91b03bb877d1dc0df5bd6a6522dd2b860cb59d35 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp
@@ -4,7 +4,7 @@
 #include <catch.hpp>
 
 TEST_CASE("ARDUINOJSON_DECODE_UNICODE == 0") {
-  DynamicJsonDocument doc(2048);
+  JsonDocument doc;
   DeserializationError err = deserializeJson(doc, "\"\\uD834\\uDD1E\"");
 
   REQUIRE(err == DeserializationError::Ok);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp
index 2b5b65236b1bab89d0c7034805a043cc1e5d2f8b..0568ab53feee321f20476a626c0bcc70791d2ac8 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp
@@ -4,7 +4,7 @@
 #include <catch.hpp>
 
 TEST_CASE("ARDUINOJSON_DECODE_UNICODE == 1") {
-  DynamicJsonDocument doc(2048);
+  JsonDocument doc;
   DeserializationError err = deserializeJson(doc, "\"\\uD834\\uDD1E\"");
 
   REQUIRE(err == DeserializationError::Ok);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp
index a3ada5ea162e460b4bc5769919547c589a69f550..bffbe7a3ebd828ab3ca512c9f25d382d72abe460 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_ENABLE_COMMENTS 0
@@ -8,7 +8,7 @@
 #include <catch.hpp>
 
 TEST_CASE("Comments should produce InvalidInput") {
-  DynamicJsonDocument doc(2048);
+  JsonDocument doc;
 
   const char* testCases[] = {
       "/*COMMENT*/  [\"hello\"]",
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp
index 5e0831c5b4c0a4271f2deaf91d6b89df79407226..db9c2636161c772fe4dc86118a72dbfdc899254e 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_ENABLE_COMMENTS 1
@@ -8,7 +8,7 @@
 #include <catch.hpp>
 
 TEST_CASE("Comments in arrays") {
-  DynamicJsonDocument doc(2048);
+  JsonDocument doc;
 
   SECTION("Block comments") {
     SECTION("Before opening bracket") {
@@ -161,7 +161,7 @@ TEST_CASE("Comments in arrays") {
 }
 
 TEST_CASE("Comments in objects") {
-  DynamicJsonDocument doc(2048);
+  JsonDocument doc;
 
   SECTION("Block comments") {
     SECTION("Before opening brace") {
@@ -371,7 +371,7 @@ TEST_CASE("Comments in objects") {
 }
 
 TEST_CASE("Comments alone") {
-  DynamicJsonDocument doc(2048);
+  JsonDocument doc;
 
   SECTION("Just a trailing comment with no line break") {
     DeserializationError err = deserializeJson(doc, "// comment");
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp
index 521fb847f0f79b9b0e0064e309dbd0ff602bb506..41566e247e0d40b8ffabe6de5dbe40dfcb75e086 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp
@@ -5,7 +5,7 @@
 #include <limits>
 
 static void assertParseFails(const char* json) {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   DeserializationError err = deserializeJson(doc, json);
 
   REQUIRE(err == DeserializationError::InvalidInput);
@@ -20,7 +20,7 @@ static void assertJsonEquals(const JsonDocument& doc,
 
 TEST_CASE("ARDUINOJSON_ENABLE_INFINITY == 0") {
   SECTION("serializeJson()") {
-    DynamicJsonDocument doc(4096);
+    JsonDocument doc;
     doc.add(std::numeric_limits<double>::infinity());
     doc.add(-std::numeric_limits<double>::infinity());
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp
index 28a3461fb275f5358009ab51cf2966ec1f621f66..99ab3fe7122907328b71562229f806e7df2c56ec 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp
@@ -9,7 +9,7 @@ using ArduinoJson::detail::isinf;
 }  // namespace my
 
 TEST_CASE("ARDUINOJSON_ENABLE_INFINITY == 1") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("serializeJson()") {
     doc.add(std::numeric_limits<double>::infinity());
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp
index e015a0dbad4afe9713a2cdfe4164bbbab902fa00..7491e4a3dd30f916b232247606324fd9145360b9 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp
@@ -5,7 +5,7 @@
 #include <limits>
 
 TEST_CASE("ARDUINOJSON_ENABLE_NAN == 0") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject root = doc.to<JsonObject>();
 
   SECTION("serializeJson()") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp
index f9ae04a6b950d88fae04a7ebc3065b23501485a4..20d0874ec2e49ac56404abcfc9e1d818015e5b8d 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp
@@ -9,7 +9,7 @@ using ArduinoJson::detail::isnan;
 }  // namespace my
 
 TEST_CASE("ARDUINOJSON_ENABLE_NAN == 1") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject root = doc.to<JsonObject>();
 
   SECTION("serializeJson()") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp
index 2cc683d7c8cfd765d6464cfcf1e15f9b95cffedf..3dc869c5e25c20865293994290cc953142c805b3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_ENABLE_PROGMEM 1
@@ -8,7 +8,7 @@
 #include <catch.hpp>
 
 TEST_CASE("Flash strings") {
-  DynamicJsonDocument doc(2048);
+  JsonDocument doc;
 
   SECTION("deserializeJson()") {
     DeserializationError err = deserializeJson(doc, F("{'hello':'world'}"));
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_0.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_0.cpp
deleted file mode 100644
index 7277af945d2ca557e5c8655e1988abdfe09507ef..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_0.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
-#define ARDUINOJSON_ENABLE_PROGMEM 1
-#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 0
-#include <ArduinoJson.h>
-
-#include <catch.hpp>
-
-TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") {
-  StaticJsonDocument<1024> doc;
-
-  SECTION("deserializeJson()") {
-    SECTION("Deduplicate values") {
-      deserializeJson(doc, "[\"example\",\"example\"]");
-
-      CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
-      CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
-    }
-
-    SECTION("Deduplicate keys") {
-      deserializeJson(doc, "[{\"example\":1},{\"example\":2}]");
-
-      CHECK(doc.memoryUsage() ==
-            2 * JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2) + 16);
-
-      const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-      const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-
-      CHECK(key1 != key2);
-    }
-  }
-
-  SECTION("JsonDocument") {
-    SECTION("values") {
-      SECTION("std::string") {
-        doc.add(std::string("example"));
-        doc.add(std::string("example"));
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
-        CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
-      }
-
-      SECTION("char*") {
-        char value[] = "example";
-        doc.add(value);
-        doc.add(value);
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
-        CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
-      }
-
-      SECTION("Arduino String") {
-        doc.add(String("example"));
-        doc.add(String("example"));
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
-        CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
-      }
-
-      SECTION("Flash string") {
-        doc.add(F("example"));
-        doc.add(F("example"));
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
-        CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
-      }
-    }
-
-    SECTION("keys") {
-      SECTION("std::string") {
-        doc[0][std::string("example")] = 1;
-        doc[1][std::string("example")] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 != key2);
-      }
-
-      SECTION("char*") {
-        char key[] = "example";
-        doc[0][key] = 1;
-        doc[1][key] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 != key2);
-      }
-
-      SECTION("Arduino String") {
-        doc[0][String("example")] = 1;
-        doc[1][String("example")] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 != key2);
-      }
-
-      SECTION("Flash string") {
-        doc[0][F("example")] = 1;
-        doc[1][F("example")] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 != key2);
-      }
-    }
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_1.cpp
deleted file mode 100644
index d3b2285e3866ba00ea290cc42ebfe5d6a220b825..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_1.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
-#define ARDUINOJSON_ENABLE_PROGMEM 1
-#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1
-#include <ArduinoJson.h>
-
-#include <catch.hpp>
-
-TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") {
-  StaticJsonDocument<1024> doc;
-
-  SECTION("deserializeJson()") {
-    SECTION("Deduplicate values") {
-      deserializeJson(doc, "[\"example\",\"example\"]");
-
-      CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
-      CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
-    }
-
-    SECTION("Deduplicate keys") {
-      deserializeJson(doc, "[{\"example\":1},{\"example\":2}]");
-
-      CHECK(doc.memoryUsage() ==
-            2 * JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2) + 8);
-
-      const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-      const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-      CHECK(key1 == key2);
-    }
-  }
-
-  SECTION("JsonDocument") {
-    SECTION("values") {
-      SECTION("std::string") {
-        doc.add(std::string("example"));
-        doc.add(std::string("example"));
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
-        CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
-      }
-
-      SECTION("char*") {
-        char value[] = "example";
-        doc.add(value);
-        doc.add(value);
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
-        CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
-      }
-
-      SECTION("Arduino String") {
-        doc.add(String("example"));
-        doc.add(String("example"));
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
-        CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
-      }
-
-      SECTION("Flash string") {
-        doc.add(F("example"));
-        doc.add(F("example"));
-
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
-        CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
-      }
-    }
-
-    SECTION("keys") {
-      SECTION("std::string") {
-        doc[0][std::string("example")] = 1;
-        doc[1][std::string("example")] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 == key2);
-      }
-
-      SECTION("char*") {
-        char key[] = "example";
-        doc[0][key] = 1;
-        doc[1][key] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 == key2);
-      }
-
-      SECTION("Arduino String") {
-        doc[0][String("example")] = 1;
-        doc[1][String("example")] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 == key2);
-      }
-
-      SECTION("Flash string") {
-        doc[0][F("example")] = 1;
-        doc[1][F("example")] = 2;
-
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
-
-        const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
-        const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
-        CHECK(key1 == key2);
-      }
-    }
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp
index f8ca566b57227dc8493ae748a5f9c47cc719d308..5dc3c613c1b2f5a91e5ac5962981028bf3043c23 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINO
@@ -10,7 +10,7 @@
 #include <catch.hpp>
 
 TEST_CASE("Issue1707") {
-  StaticJsonDocument<128> doc;
+  JsonDocument doc;
 
   DeserializationError err = deserializeJson(doc, F("{\"hello\":12}"));
   REQUIRE(err == DeserializationError::Ok);
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_1.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..32636867e4e2bc55042c96b53333d050e4f9f351
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_1.cpp
@@ -0,0 +1,131 @@
+#define ARDUINOJSON_STRING_LENGTH_SIZE 1
+#include <ArduinoJson.h>
+
+#include <catch.hpp>
+#include <string>
+
+#include "Literals.hpp"
+
+TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 1") {
+  JsonDocument doc;
+
+  SECTION("set(std::string)") {
+    SECTION("returns true if len <= 255") {
+      auto result = doc.set(std::string(255, '?'));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+
+    SECTION("returns false if len >= 256") {
+      auto result = doc.set(std::string(256, '?'));
+
+      REQUIRE(result == false);
+      REQUIRE(doc.overflowed() == true);
+    }
+  }
+
+  SECTION("set(MsgPackBinary)") {
+    SECTION("returns true if size <= 253") {
+      auto str = std::string(253, '?');
+      auto result = doc.set(MsgPackBinary(str.data(), str.size()));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+
+    SECTION("returns false if size >= 254") {
+      auto str = std::string(254, '?');
+      auto result = doc.set(MsgPackBinary(str.data(), str.size()));
+
+      REQUIRE(result == false);
+      REQUIRE(doc.overflowed() == true);
+    }
+  }
+
+  SECTION("set(MsgPackExtension)") {
+    SECTION("returns true if size <= 252") {
+      auto str = std::string(252, '?');
+      auto result = doc.set(MsgPackExtension(1, str.data(), str.size()));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+
+    SECTION("returns false if size >= 253") {
+      auto str = std::string(253, '?');
+      auto result = doc.set(MsgPackExtension(1, str.data(), str.size()));
+
+      REQUIRE(result == false);
+      REQUIRE(doc.overflowed() == true);
+    }
+  }
+
+  SECTION("deserializeJson()") {
+    SECTION("returns Ok if string length <= 255") {
+      auto input = "\"" + std::string(255, '?') + "\"";
+
+      auto err = deserializeJson(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if string length >= 256") {
+      auto input = "\"" + std::string(256, '?') + "\"";
+
+      auto err = deserializeJson(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+  }
+
+  SECTION("deserializeMsgPack()") {
+    SECTION("returns Ok if string length <= 255") {
+      auto input = "\xd9\xff" + std::string(255, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if string length >= 256") {
+      auto input = "\xda\x01\x00"_s + std::string(256, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+
+    SECTION("returns Ok if binary size <= 253") {
+      auto input = "\xc4\xfd" + std::string(253, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if binary size >= 254") {
+      auto input = "\xc4\xfe" + std::string(254, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+
+    SECTION("returns Ok if extension size <= 252") {
+      auto input = "\xc7\xfc\x01" + std::string(252, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if binary size >= 253") {
+      auto input = "\xc7\xfd\x01" + std::string(253, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_2.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2c1315b08bf8867907e08759761cb3b91ebf406e
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_2.cpp
@@ -0,0 +1,140 @@
+#define ARDUINOJSON_STRING_LENGTH_SIZE 2
+#include <ArduinoJson.h>
+
+#include <catch.hpp>
+#include <string>
+
+#include "Literals.hpp"
+
+TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 2") {
+  JsonDocument doc;
+
+  SECTION("set(std::string)") {
+    SECTION("returns true if len <= 65535") {
+      auto result = doc.set(std::string(65535, '?'));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+
+    SECTION("returns false if len >= 65536") {
+      auto result = doc.set(std::string(65536, '?'));
+
+      REQUIRE(result == false);
+      REQUIRE(doc.overflowed() == true);
+    }
+  }
+
+  SECTION("set(MsgPackBinary)") {
+    SECTION("returns true if size <= 65532") {
+      auto str = std::string(65532, '?');
+      auto result = doc.set(MsgPackBinary(str.data(), str.size()));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+
+    SECTION("returns false if size >= 65533") {
+      auto str = std::string(65533, '?');
+      auto result = doc.set(MsgPackBinary(str.data(), str.size()));
+
+      REQUIRE(result == false);
+      REQUIRE(doc.overflowed() == true);
+    }
+  }
+
+  SECTION("set(MsgPackExtension)") {
+    SECTION("returns true if size <= 65531") {
+      auto str = std::string(65531, '?');
+      auto result = doc.set(MsgPackExtension(1, str.data(), str.size()));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+
+    SECTION("returns false if size >= 65532") {
+      auto str = std::string(65532, '?');
+      auto result = doc.set(MsgPackExtension(1, str.data(), str.size()));
+
+      REQUIRE(result == false);
+      REQUIRE(doc.overflowed() == true);
+    }
+  }
+
+  SECTION("deserializeJson()") {
+    SECTION("returns Ok if string length <= 65535") {
+      auto input = "\"" + std::string(65535, '?') + "\"";
+
+      auto err = deserializeJson(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if string length >= 65536") {
+      auto input = "\"" + std::string(65536, '?') + "\"";
+
+      auto err = deserializeJson(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+  }
+
+  SECTION("deserializeMsgPack()") {
+    SECTION("returns Ok if string length <= 65535") {
+      auto input = "\xda\xff\xff" + std::string(65535, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if string length >= 65536") {
+      auto input = "\xdb\x00\x01\x00\x00"_s + std::string(65536, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+
+    SECTION("returns Ok if binary size <= 65532") {
+      auto input = "\xc5\xff\xfc" + std::string(65532, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if binary size >= 65534") {
+      auto input = "\xc5\xff\xfd" + std::string(65534, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+
+    // https://oss-fuzz.com/testcase?key=5354792971993088
+    SECTION("doesn't overflow if binary size == 0xFFFF") {
+      auto input = "\xc5\xff\xff"_s;
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+
+    SECTION("returns Ok if extension size <= 65531") {
+      auto input = "\xc8\xff\xfb\x01" + std::string(65531, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns NoMemory if extension size >= 65532") {
+      auto input = "\xc8\xff\xfc\x01" + std::string(65532, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_4.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_4.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4059c3d483be9dfca38eae5d75a0b6d9d426a25c
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/string_length_size_4.cpp
@@ -0,0 +1,146 @@
+#define ARDUINOJSON_STRING_LENGTH_SIZE 4
+#include <ArduinoJson.h>
+
+#include <catch.hpp>
+#include <string>
+
+#include "Literals.hpp"
+
+TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 4") {
+  JsonDocument doc;
+
+  SECTION("set(std::string)") {
+    SECTION("returns true if string length >= 65536") {
+      auto result = doc.set(std::string(65536, '?'));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+  }
+
+  SECTION("set(MsgPackBinary)") {
+    SECTION("returns true if size >= 65536") {
+      auto str = std::string(65536, '?');
+      auto result = doc.set(MsgPackBinary(str.data(), str.size()));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+  }
+
+  SECTION("set(MsgPackExtension)") {
+    SECTION("returns true if size >= 65532") {
+      auto str = std::string(65532, '?');
+      auto result = doc.set(MsgPackExtension(1, str.data(), str.size()));
+
+      REQUIRE(result == true);
+      REQUIRE(doc.overflowed() == false);
+    }
+  }
+
+  SECTION("deserializeJson()") {
+    SECTION("returns Ok if string length >= 65536") {
+      auto input = "\"" + std::string(65536, '?') + "\"";
+
+      auto err = deserializeJson(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+  }
+
+  SECTION("deserializeMsgPack()") {
+    SECTION("returns Ok if string size >= 65536") {
+      auto input = "\xda\xff\xff" + std::string(65536, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns Ok if binary size >= 65536") {
+      auto input = "\xc5\xff\xff" + std::string(65536, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    SECTION("returns Ok if extension size >= 65532") {
+      auto input = "\xc8\xff\xfb\x01" + std::string(65532, '?');
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::Ok);
+    }
+
+    // https://oss-fuzz.com/testcase?key=5354792971993088
+    SECTION("doesn't overflow if binary size == 0xFFFFFFFF") {
+      auto input = "\xc6\xff\xff\xff\xff"_s;
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err == DeserializationError::NoMemory);
+    }
+
+    SECTION("doesn't overflow if string size == 0xFFFFFFFF") {
+      auto input = "\xdb\xff\xff\xff\xff???????????????????"_s;
+
+      auto err = deserializeMsgPack(doc, input);
+
+      REQUIRE(err != DeserializationError::Ok);
+    }
+  }
+
+  SECTION("bin 32 deserialization") {
+    auto str = std::string(65536, '?');
+    auto input = "\xc6\x00\x01\x00\x00"_s + str;
+
+    auto err = deserializeMsgPack(doc, input);
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackBinary>());
+    auto binary = doc.as<MsgPackBinary>();
+    REQUIRE(binary.size() == 65536);
+    REQUIRE(binary.data() != nullptr);
+    REQUIRE(std::string(reinterpret_cast<const char*>(binary.data()),
+                        binary.size()) == str);
+  }
+
+  SECTION("bin 32 serialization") {
+    auto str = std::string(65536, '?');
+    doc.set(MsgPackBinary(str.data(), str.size()));
+
+    std::string output;
+    auto result = serializeMsgPack(doc, output);
+
+    REQUIRE(result == 5 + str.size());
+    REQUIRE(output == "\xc6\x00\x01\x00\x00"_s + str);
+  }
+
+  SECTION("ext 32 deserialization") {
+    auto str = std::string(65536, '?');
+    auto input = "\xc9\x00\x01\x00\x00\x2a"_s + str;
+
+    auto err = deserializeMsgPack(doc, input);
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto value = doc.as<MsgPackExtension>();
+    REQUIRE(value.type() == 42);
+    REQUIRE(value.size() == 65536);
+    REQUIRE(value.data() != nullptr);
+    REQUIRE(std::string(reinterpret_cast<const char*>(value.data()),
+                        value.size()) == str);
+  }
+
+  SECTION("ext 32 serialization") {
+    auto str = std::string(65536, '?');
+    doc.set(MsgPackExtension(42, str.data(), str.size()));
+
+    std::string output;
+    auto result = serializeMsgPack(doc, output);
+
+    REQUIRE(result == 6 + str.size());
+    REQUIRE(output == "\xc9\x00\x01\x00\x00\x2a"_s + str);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp
index f38ee1f539e16c70e913904c5195d6677af23e79..8752890dbfb79c75bbbd6e6952d6f530f0978b33 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp
@@ -4,7 +4,7 @@
 #include <catch.hpp>
 
 TEST_CASE("ARDUINOJSON_USE_DOUBLE == 0") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject root = doc.to<JsonObject>();
 
   root["pi"] = 3.14;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp
index ff4a0e353fcf3ace13e5123daad6bc2135f5bb42..15c4849c8d6f036f031ab42116ae049cc24f5921 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp
@@ -4,7 +4,7 @@
 #include <catch.hpp>
 
 TEST_CASE("ARDUINOJSON_USE_DOUBLE == 1") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject root = doc.to<JsonObject>();
 
   root["pi"] = 3.14;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_0.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_0.cpp
index f467aabdd6f0c9aef6868c30c003134a1a69348e..2781a76fc7a6ce732ccbc78e99df85f2eb3e75d3 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_0.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_0.cpp
@@ -3,14 +3,40 @@
 
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 TEST_CASE("ARDUINOJSON_USE_LONG_LONG == 0") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
+
+  SECTION("smoke test") {
+    doc["A"] = 42;
+    doc["B"] = 84;
+
+    std::string json;
+    serializeJson(doc, json);
+
+    REQUIRE(json == "{\"A\":42,\"B\":84}");
+  }
+
+  SECTION("deserializeMsgPack()") {
+    SECTION("cf 00 00 00 00 ff ff ff ff") {
+      auto err =
+          deserializeMsgPack(doc, "\xcf\x00\x00\x00\x00\xff\xff\xff\xff"_s);
 
-  doc["A"] = 42;
-  doc["B"] = 84;
+      REQUIRE(err == DeserializationError::Ok);
+      REQUIRE(doc.as<uint32_t>() == 0xFFFFFFFF);
+    }
 
-  std::string json;
-  serializeJson(doc, json);
+    SECTION("cf 00 00 00 01 00 00 00 00") {
+      auto err =
+          deserializeMsgPack(doc, "\xcf\x00\x00\x00\x01\x00\x00\x00\x00"_s);
 
-  REQUIRE(json == "{\"A\":42,\"B\":84}");
+      REQUIRE(err == DeserializationError::Ok);
+#if defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ >= 8
+      REQUIRE(doc.as<JsonInteger>() == 0x100000000);
+#else
+      REQUIRE(doc.isNull());
+#endif
+    }
+  }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_1.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_1.cpp
index 5127a55a72315a1336c5571f08a6bd0eaf5f354c..a2c4fd8e21f9ac13dacadec569dc6f60b1642bd9 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_1.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MixedConfiguration/use_long_long_1.cpp
@@ -4,7 +4,7 @@
 #include <catch.hpp>
 
 TEST_CASE("ARDUINOJSON_USE_LONG_LONG == 1") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject root = doc.to<JsonObject>();
 
   root["A"] = 123456789123456789;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt
index a5442076a17aeedfbc8291d24a0036e7a00b15d6..61dc8b9339a60b3f9a9627bb5317b009903051fc 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt
@@ -1,19 +1,17 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(MsgPackDeserializerTests
 	deserializeArray.cpp
 	deserializeObject.cpp
-	deserializeStaticVariant.cpp
 	deserializeVariant.cpp
+	destination_types.cpp
 	doubleToFloat.cpp
+	errors.cpp
 	filter.cpp
-	incompleteInput.cpp
 	input_types.cpp
-	misc.cpp
 	nestingLimit.cpp
-	notSupported.cpp
 )
 
 add_test(MsgPackDeserializer MsgPackDeserializerTests)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp
index 356a9fe47d5747588c373dbe93628e18dd595206..053c765e8b401fe86a403ee4059b2cda787f5302 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("deserialize MsgPack array") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("fixarray") {
     SECTION("empty") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp
index 4aeb5532a253d468bab5b81232e9aaca39df407f..675329e82f22e3276c6ab8ceaa73e10ff4ee7524 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("deserialize MsgPack object") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("fixmap") {
     SECTION("empty") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeStaticVariant.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeStaticVariant.cpp
deleted file mode 100644
index 0e126ff29a8500c98cbc75ccbb6572f6070fbbf2..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeStaticVariant.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-template <size_t Capacity>
-static void check(const char* input, DeserializationError expected) {
-  StaticJsonDocument<Capacity> doc;
-
-  DeserializationError error = deserializeMsgPack(doc, input);
-
-  CAPTURE(input);
-  REQUIRE(error == expected);
-}
-
-template <size_t Size>
-static void checkString(const char* input, DeserializationError expected) {
-  check<Size>(input, expected);
-}
-
-TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") {
-  SECTION("single values always fit") {
-    check<0>("\xc0", DeserializationError::Ok);                  // nil
-    check<0>("\xc2", DeserializationError::Ok);                  // false
-    check<0>("\xc3", DeserializationError::Ok);                  // true
-    check<0>("\xcc\x00", DeserializationError::Ok);              // uint 8
-    check<0>("\xcd\x30\x39", DeserializationError::Ok);          // uint 16
-    check<0>("\xCE\x12\x34\x56\x78", DeserializationError::Ok);  // uint 32
-  }
-
-  SECTION("fixstr") {
-    checkString<8>("\xA0", DeserializationError::Ok);
-    checkString<8>("\xA7ZZZZZZZ", DeserializationError::Ok);
-    checkString<8>("\xA8ZZZZZZZZ", DeserializationError::NoMemory);
-    checkString<16>("\xAFZZZZZZZZZZZZZZZ", DeserializationError::Ok);
-    checkString<16>("\xB0ZZZZZZZZZZZZZZZZ", DeserializationError::NoMemory);
-  }
-
-  SECTION("str 8") {
-    checkString<8>("\xD9\x00", DeserializationError::Ok);
-    checkString<8>("\xD9\x07ZZZZZZZ", DeserializationError::Ok);
-    checkString<8>("\xD9\x08ZZZZZZZZ", DeserializationError::NoMemory);
-    checkString<16>("\xD9\x0FZZZZZZZZZZZZZZZ", DeserializationError::Ok);
-    checkString<16>("\xD9\x10ZZZZZZZZZZZZZZZZ", DeserializationError::NoMemory);
-  }
-
-  SECTION("str 16") {
-    checkString<8>("\xDA\x00\x00", DeserializationError::Ok);
-    checkString<8>("\xDA\x00\x07ZZZZZZZ", DeserializationError::Ok);
-    checkString<8>("\xDA\x00\x08ZZZZZZZZ", DeserializationError::NoMemory);
-    checkString<16>("\xDA\x00\x0FZZZZZZZZZZZZZZZ", DeserializationError::Ok);
-    checkString<16>("\xDA\x00\x10ZZZZZZZZZZZZZZZZ",
-                    DeserializationError::NoMemory);
-  }
-
-  SECTION("str 32") {
-    checkString<8>("\xDB\x00\x00\x00\x00", DeserializationError::Ok);
-    checkString<8>("\xDB\x00\x00\x00\x07ZZZZZZZ", DeserializationError::Ok);
-    checkString<8>("\xDB\x00\x00\x00\x08ZZZZZZZZ",
-                   DeserializationError::NoMemory);
-    checkString<16>("\xDB\x00\x00\x00\x0FZZZZZZZZZZZZZZZ",
-                    DeserializationError::Ok);
-    checkString<16>("\xDB\x00\x00\x00\x10ZZZZZZZZZZZZZZZZ",
-                    DeserializationError::NoMemory);
-  }
-
-  SECTION("fixarray") {
-    check<JSON_ARRAY_SIZE(0)>("\x90", DeserializationError::Ok);  // []
-    check<JSON_ARRAY_SIZE(0)>("\x91\x01",
-                              DeserializationError::NoMemory);        // [1]
-    check<JSON_ARRAY_SIZE(1)>("\x91\x01", DeserializationError::Ok);  // [1]
-    check<JSON_ARRAY_SIZE(1)>("\x92\x01\x02",
-                              DeserializationError::NoMemory);  // [1,2]
-  }
-
-  SECTION("array 16") {
-    check<JSON_ARRAY_SIZE(0)>("\xDC\x00\x00", DeserializationError::Ok);
-    check<JSON_ARRAY_SIZE(0)>("\xDC\x00\x01\x01",
-                              DeserializationError::NoMemory);
-    check<JSON_ARRAY_SIZE(1)>("\xDC\x00\x01\x01", DeserializationError::Ok);
-    check<JSON_ARRAY_SIZE(1)>("\xDC\x00\x02\x01\x02",
-                              DeserializationError::NoMemory);
-  }
-
-  SECTION("array 32") {
-    check<JSON_ARRAY_SIZE(0)>("\xDD\x00\x00\x00\x00", DeserializationError::Ok);
-    check<JSON_ARRAY_SIZE(0)>("\xDD\x00\x00\x00\x01\x01",
-                              DeserializationError::NoMemory);
-    check<JSON_ARRAY_SIZE(1)>("\xDD\x00\x00\x00\x01\x01",
-                              DeserializationError::Ok);
-    check<JSON_ARRAY_SIZE(1)>("\xDD\x00\x00\x00\x02\x01\x02",
-                              DeserializationError::NoMemory);
-  }
-
-  SECTION("fixmap") {
-    SECTION("{}") {
-      check<JSON_OBJECT_SIZE(0)>("\x80", DeserializationError::Ok);
-    }
-    SECTION("{H:1}") {
-      check<JSON_OBJECT_SIZE(0)>("\x81\xA1H\x01",
-                                 DeserializationError::NoMemory);
-      check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
-          "\x81\xA1H\x01", DeserializationError::Ok);
-    }
-    SECTION("{H:1,W:2}") {
-      check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
-          "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
-      check<JSON_OBJECT_SIZE(2) + 2 * JSON_STRING_SIZE(2)>(
-          "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
-    }
-  }
-
-  SECTION("map 16") {
-    SECTION("{}") {
-      check<JSON_OBJECT_SIZE(0)>("\xDE\x00\x00", DeserializationError::Ok);
-    }
-    SECTION("{H:1}") {
-      check<JSON_OBJECT_SIZE(0)>("\xDE\x00\x01\xA1H\x01",
-                                 DeserializationError::NoMemory);
-      check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
-          "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok);
-    }
-    SECTION("{H:1,W:2}") {
-      check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
-          "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
-      check<JSON_OBJECT_SIZE(2) + 2 * JSON_OBJECT_SIZE(1)>(
-          "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
-    }
-  }
-
-  SECTION("map 32") {
-    SECTION("{}") {
-      check<JSON_OBJECT_SIZE(0)>("\xDF\x00\x00\x00\x00",
-                                 DeserializationError::Ok);
-    }
-    SECTION("{H:1}") {
-      check<JSON_OBJECT_SIZE(0)>("\xDF\x00\x00\x00\x01\xA1H\x01",
-                                 DeserializationError::NoMemory);
-      check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
-          "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
-    }
-    SECTION("{H:1,W:2}") {
-      check<JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(2)>(
-          "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
-          DeserializationError::NoMemory);
-      check<JSON_OBJECT_SIZE(2) + 2 * JSON_OBJECT_SIZE(1)>(
-          "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
-    }
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp
index 87f38d6e2573d2b8e68505767353e00d92e28646..412d541e4d4b481e66bc461de7fc082043d0cab4 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp
@@ -1,13 +1,16 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
-template <typename T, typename U>
-static void check(const char* input, U expected) {
-  DynamicJsonDocument doc(4096);
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+template <typename T>
+static void checkValue(const char* input, T expected) {
+  JsonDocument doc;
 
   DeserializationError error = deserializeMsgPack(doc, input);
 
@@ -16,132 +19,374 @@ static void check(const char* input, U expected) {
   REQUIRE(doc.as<T>() == expected);
 }
 
-#if ARDUINOJSON_USE_LONG_LONG == 0
-static void checkNotSupported(const char* input) {
-  DynamicJsonDocument doc(4096);
-  DeserializationError error = deserializeMsgPack(doc, input);
-  REQUIRE(error == DeserializationError::Ok);
-  REQUIRE(doc.isNull());
-}
-#endif
-
-static void checkIsNull(const char* input) {
-  DynamicJsonDocument doc(4096);
+static void checkError(size_t timebombCountDown, const char* input,
+                       DeserializationError expected) {
+  TimebombAllocator timebomb(timebombCountDown);
+  JsonDocument doc(&timebomb);
 
   DeserializationError error = deserializeMsgPack(doc, input);
 
-  REQUIRE(error == DeserializationError::Ok);
-  REQUIRE(doc.as<JsonVariant>().isNull());
+  CAPTURE(input);
+  REQUIRE(error == expected);
 }
 
 TEST_CASE("deserialize MsgPack value") {
   SECTION("nil") {
-    checkIsNull("\xc0");
+    checkValue("\xc0", nullptr);
   }
 
   SECTION("bool") {
-    check<bool>("\xc2", false);
-    check<bool>("\xc3", true);
+    checkValue<bool>("\xc2", false);
+    checkValue<bool>("\xc3", true);
   }
 
   SECTION("positive fixint") {
-    check<int>("\x00", 0);
-    check<int>("\x7F", 127);
+    checkValue<int>("\x00", 0);
+    checkValue<int>("\x7F", 127);
   }
 
   SECTION("negative fixint") {
-    check<int>("\xe0", -32);
-    check<int>("\xff", -1);
+    checkValue<int>("\xe0", -32);
+    checkValue<int>("\xff", -1);
   }
 
   SECTION("uint 8") {
-    check<int>("\xcc\x00", 0);
-    check<int>("\xcc\xff", 255);
+    checkValue<int>("\xcc\x00", 0);
+    checkValue<int>("\xcc\xff", 255);
   }
 
   SECTION("uint 16") {
-    check<int>("\xcd\x00\x00", 0);
-    check<int>("\xcd\xFF\xFF", 65535);
-    check<int>("\xcd\x30\x39", 12345);
+    checkValue<int>("\xcd\x00\x00", 0);
+    checkValue<int>("\xcd\xFF\xFF", 65535);
+    checkValue<int>("\xcd\x30\x39", 12345);
   }
 
   SECTION("uint 32") {
-    check<uint32_t>("\xCE\x00\x00\x00\x00", 0x00000000U);
-    check<uint32_t>("\xCE\xFF\xFF\xFF\xFF", 0xFFFFFFFFU);
-    check<uint32_t>("\xCE\x12\x34\x56\x78", 0x12345678U);
+    checkValue<uint32_t>("\xCE\x00\x00\x00\x00", 0x00000000U);
+    checkValue<uint32_t>("\xCE\xFF\xFF\xFF\xFF", 0xFFFFFFFFU);
+    checkValue<uint32_t>("\xCE\x12\x34\x56\x78", 0x12345678U);
   }
 
   SECTION("uint 64") {
 #if ARDUINOJSON_USE_LONG_LONG
-    check<uint64_t>("\xCF\x00\x00\x00\x00\x00\x00\x00\x00", 0U);
-    check<uint64_t>("\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
-                    0xFFFFFFFFFFFFFFFFU);
-    check<uint64_t>("\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0",
-                    0x123456789ABCDEF0U);
+    checkValue<uint64_t>("\xCF\x00\x00\x00\x00\x00\x00\x00\x00", 0U);
+    checkValue<uint64_t>("\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+                         0xFFFFFFFFFFFFFFFFU);
+    checkValue<uint64_t>("\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0",
+                         0x123456789ABCDEF0U);
 #else
-    checkNotSupported("\xCF\x00\x00\x00\x00\x00\x00\x00\x00");
-    checkNotSupported("\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF");
-    checkNotSupported("\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0");
+    checkValue("\xCF\x00\x00\x00\x00\x00\x00\x00\x00", nullptr);
+    checkValue("\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", nullptr);
+    checkValue("\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0", nullptr);
 #endif
   }
 
   SECTION("int 8") {
-    check<int>("\xd0\x00", 0);
-    check<int>("\xd0\xff", -1);
+    checkValue<int>("\xd0\x00", 0);
+    checkValue<int>("\xd0\xff", -1);
   }
 
   SECTION("int 16") {
-    check<int>("\xD1\x00\x00", 0);
-    check<int>("\xD1\xFF\xFF", -1);
-    check<int>("\xD1\xCF\xC7", -12345);
+    checkValue<int>("\xD1\x00\x00", 0);
+    checkValue<int>("\xD1\xFF\xFF", -1);
+    checkValue<int>("\xD1\xCF\xC7", -12345);
   }
 
   SECTION("int 32") {
-    check<int>("\xD2\x00\x00\x00\x00", 0);
-    check<int>("\xD2\xFF\xFF\xFF\xFF", -1);
-    check<int>("\xD2\xB6\x69\xFD\x2E", -1234567890);
+    checkValue<int>("\xD2\x00\x00\x00\x00", 0);
+    checkValue<int>("\xD2\xFF\xFF\xFF\xFF", -1);
+    checkValue<int>("\xD2\xB6\x69\xFD\x2E", -1234567890);
   }
 
   SECTION("int 64") {
 #if ARDUINOJSON_USE_LONG_LONG
-    check<int64_t>("\xD3\x00\x00\x00\x00\x00\x00\x00\x00", int64_t(0U));
-    check<int64_t>("\xD3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
-                   int64_t(0xFFFFFFFFFFFFFFFFU));
-    check<int64_t>("\xD3\x12\x34\x56\x78\x9A\xBC\xDE\xF0",
-                   int64_t(0x123456789ABCDEF0));
+    checkValue<int64_t>("\xD3\x00\x00\x00\x00\x00\x00\x00\x00", int64_t(0U));
+    checkValue<int64_t>("\xD3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+                        int64_t(0xFFFFFFFFFFFFFFFFU));
+    checkValue<int64_t>("\xD3\x12\x34\x56\x78\x9A\xBC\xDE\xF0",
+                        int64_t(0x123456789ABCDEF0));
 #else
-    checkNotSupported("\xD3\x00\x00\x00\x00\x00\x00\x00\x00");
-    checkNotSupported("\xD3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF");
-    checkNotSupported("\xD3\x12\x34\x56\x78\x9A\xBC\xDE\xF0");
+    checkValue("\xD3\x00\x00\x00\x00\x00\x00\x00\x00", nullptr);
+    checkValue("\xD3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", nullptr);
+    checkValue("\xD3\x12\x34\x56\x78\x9A\xBC\xDE\xF0", nullptr);
 #endif
   }
 
   SECTION("float 32") {
-    check<double>("\xCA\x00\x00\x00\x00", 0.0f);
-    check<double>("\xCA\x40\x48\xF5\xC3", 3.14f);
+    checkValue<double>("\xCA\x00\x00\x00\x00", 0.0f);
+    checkValue<double>("\xCA\x40\x48\xF5\xC3", 3.14f);
   }
 
   SECTION("float 64") {
-    check<double>("\xCB\x00\x00\x00\x00\x00\x00\x00\x00", 0.0);
-    check<double>("\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F", 3.1415);
+    checkValue<double>("\xCB\x00\x00\x00\x00\x00\x00\x00\x00", 0.0);
+    checkValue<double>("\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F", 3.1415);
+  }
+
+  SECTION("fixstr") {
+    checkValue<std::string>("\xA0", std::string(""));
+    checkValue<std::string>("\xABhello world", "hello world"_s);
+    checkValue<std::string>("\xBFhello world hello world hello !",
+                            "hello world hello world hello !"_s);
+  }
+
+  SECTION("str 8") {
+    checkValue<std::string>("\xd9\x05hello", "hello"_s);
+  }
+
+  SECTION("str 16") {
+    checkValue<std::string>("\xda\x00\x05hello", "hello"_s);
+  }
+
+  SECTION("str 32") {
+    checkValue<std::string>("\xdb\x00\x00\x00\x05hello", "hello"_s);
+  }
+
+  SECTION("bin 8") {
+    JsonDocument doc;
+
+    DeserializationError error = deserializeMsgPack(doc, "\xc4\x01?");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackBinary>());
+    auto binary = doc.as<MsgPackBinary>();
+    REQUIRE(binary.size() == 1);
+    REQUIRE(binary.data() != nullptr);
+    REQUIRE(reinterpret_cast<const char*>(binary.data())[0] == '?');
+  }
+
+  SECTION("bin 16") {
+    JsonDocument doc;
+    auto str = std::string(256, '?');
+    auto input = "\xc5\x01\x00"_s + str;
+
+    DeserializationError error = deserializeMsgPack(doc, input);
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackBinary>());
+    auto binary = doc.as<MsgPackBinary>();
+    REQUIRE(binary.size() == 0x100);
+    REQUIRE(binary.data() != nullptr);
+    REQUIRE(std::string(reinterpret_cast<const char*>(binary.data()),
+                        binary.size()) == str);
+  }
+
+  SECTION("fixext 1") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xd4\x01\x02");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 1);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == 2);
+  }
+
+  SECTION("fixext 2") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xd5\x01\x02\x03");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 2);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == 2);
+    REQUIRE(data[1] == 3);
+  }
+
+  SECTION("fixext 4") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xd6\x01\x02\x03\x04\x05");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 4);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == 2);
+    REQUIRE(data[1] == 3);
+    REQUIRE(data[2] == 4);
+    REQUIRE(data[3] == 5);
+  }
+
+  SECTION("fixext 8") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xd7\x01????????");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 8);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == '?');
+    REQUIRE(data[7] == '?');
+  }
+
+  SECTION("fixext 16") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xd8\x01?????????????????");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 16);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == '?');
+    REQUIRE(data[15] == '?');
+  }
+
+  SECTION("ext 8") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xc7\x02\x01\x03\x04");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 2);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == 3);
+    REQUIRE(data[1] == 4);
+  }
+
+  SECTION("ext 16") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xc8\x00\x02\x01\x03\x04");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 2);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == 3);
+    REQUIRE(data[1] == 4);
+  }
+
+  SECTION("ext 32") {
+    JsonDocument doc;
+
+    auto error = deserializeMsgPack(doc, "\xc9\x00\x00\x00\x02\x01\x03\x04");
+
+    REQUIRE(error == DeserializationError::Ok);
+    REQUIRE(doc.is<MsgPackExtension>());
+    auto ext = doc.as<MsgPackExtension>();
+    REQUIRE(ext.type() == 1);
+    REQUIRE(ext.size() == 2);
+    auto data = reinterpret_cast<const uint8_t*>(ext.data());
+    REQUIRE(data[0] == 3);
+    REQUIRE(data[1] == 4);
+  }
+}
+
+TEST_CASE("deserializeMsgPack() under memory constaints") {
+  SECTION("single values always fit") {
+    checkError(0, "\xc0", DeserializationError::Ok);          // nil
+    checkError(0, "\xc2", DeserializationError::Ok);          // false
+    checkError(0, "\xc3", DeserializationError::Ok);          // true
+    checkError(0, "\xcc\x00", DeserializationError::Ok);      // uint 8
+    checkError(0, "\xcd\x30\x39", DeserializationError::Ok);  // uint 16
+    checkError(0, "\xCE\x12\x34\x56\x78",
+               DeserializationError::Ok);  // uint 32
   }
 
   SECTION("fixstr") {
-    check<const char*>("\xA0", std::string(""));
-    check<const char*>("\xABhello world", std::string("hello world"));
-    check<const char*>("\xBFhello world hello world hello !",
-                       std::string("hello world hello world hello !"));
+    checkError(2, "\xA7ZZZZZZZ", DeserializationError::Ok);
+    checkError(0, "\xA7ZZZZZZZ", DeserializationError::NoMemory);
   }
 
   SECTION("str 8") {
-    check<const char*>("\xd9\x05hello", std::string("hello"));
+    checkError(2, "\xD9\x07ZZZZZZZ", DeserializationError::Ok);
+    checkError(0, "\xD9\x07ZZZZZZZ", DeserializationError::NoMemory);
   }
 
   SECTION("str 16") {
-    check<const char*>("\xda\x00\x05hello", std::string("hello"));
+    checkError(2, "\xDA\x00\x07ZZZZZZZ", DeserializationError::Ok);
+    checkError(0, "\xDA\x00\x07ZZZZZZZ", DeserializationError::NoMemory);
   }
 
   SECTION("str 32") {
-    check<const char*>("\xdb\x00\x00\x00\x05hello", std::string("hello"));
+    checkError(2, "\xDB\x00\x00\x00\x07ZZZZZZZ", DeserializationError::Ok);
+    checkError(0, "\xDB\x00\x00\x00\x07ZZZZZZZ",
+               DeserializationError::NoMemory);
+  }
+
+  SECTION("fixarray") {
+    checkError(0, "\x90", DeserializationError::Ok);  // []
+    checkError(0, "\x91\x01",
+               DeserializationError::NoMemory);  // [1]
+    checkError(1, "\x91\x01",
+               DeserializationError::Ok);  // [1]
+  }
+
+  SECTION("array 16") {
+    checkError(0, "\xDC\x00\x00", DeserializationError::Ok);
+    checkError(0, "\xDC\x00\x01\x01", DeserializationError::NoMemory);
+    checkError(1, "\xDC\x00\x01\x01", DeserializationError::Ok);
+  }
+
+  SECTION("array 32") {
+    checkError(0, "\xDD\x00\x00\x00\x00", DeserializationError::Ok);
+    checkError(0, "\xDD\x00\x00\x00\x01\x01", DeserializationError::NoMemory);
+    checkError(1, "\xDD\x00\x00\x00\x01\x01", DeserializationError::Ok);
+  }
+
+  SECTION("fixmap") {
+    SECTION("{}") {
+      checkError(0, "\x80", DeserializationError::Ok);
+    }
+    SECTION("{H:1}") {
+      checkError(1, "\x81\xA1H\x01", DeserializationError::NoMemory);
+      checkError(2, "\x81\xA1H\x01", DeserializationError::Ok);
+    }
+    SECTION("{H:1,W:2}") {
+      checkError(2, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
+      checkError(3, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
+    }
+  }
+
+  SECTION("map 16") {
+    SECTION("{}") {
+      checkError(0, "\xDE\x00\x00", DeserializationError::Ok);
+    }
+    SECTION("{H:1}") {
+      checkError(1, "\xDE\x00\x01\xA1H\x01", DeserializationError::NoMemory);
+      checkError(2, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok);
+    }
+    SECTION("{H:1,W:2}") {
+      checkError(2, "\xDE\x00\x02\xA1H\x01\xA1W\x02",
+                 DeserializationError::NoMemory);
+      checkError(3, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
+    }
+  }
+
+  SECTION("map 32") {
+    SECTION("{}") {
+      checkError(0, "\xDF\x00\x00\x00\x00", DeserializationError::Ok);
+    }
+    SECTION("{H:1}") {
+      checkError(1, "\xDF\x00\x00\x00\x01\xA1H\x01",
+                 DeserializationError::NoMemory);
+      checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
+    }
+    SECTION("{H:1,W:2}") {
+      checkError(2, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
+                 DeserializationError::NoMemory);
+      checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
+                 DeserializationError::Ok);
+    }
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/destination_types.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/destination_types.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f0d04d67c01d83ccbd29439ceff43bf19104503a
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/destination_types.cpp
@@ -0,0 +1,109 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+
+#include <catch.hpp>
+#include <string>
+
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofArray;
+using ArduinoJson::detail::sizeofObject;
+
+TEST_CASE("deserializeMsgPack(JsonDocument&)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  doc.add("hello"_s);
+  spy.clearLog();
+
+  auto err = deserializeMsgPack(doc, "\x91\x2A");
+
+  REQUIRE(err == DeserializationError::Ok);
+  REQUIRE(doc.as<std::string>() == "[42]");
+  REQUIRE(spy.log() == AllocatorLog{
+                           Deallocate(sizeofPool()),
+                           Deallocate(sizeofString("hello")),
+                           Allocate(sizeofPool()),
+                           Reallocate(sizeofPool(), sizeofArray(1)),
+                       });
+}
+
+TEST_CASE("deserializeMsgPack(JsonVariant)") {
+  SECTION("variant is bound") {
+    SpyingAllocator spy;
+    JsonDocument doc(&spy);
+    doc.add("hello"_s);
+    spy.clearLog();
+
+    JsonVariant variant = doc[0];
+
+    auto err = deserializeMsgPack(variant, "\x91\x2A");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "[[42]]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("hello")),
+                         });
+  }
+
+  SECTION("variant is unbound") {
+    JsonVariant variant;
+
+    auto err = deserializeMsgPack(variant, "\x91\x2A");
+
+    REQUIRE(err == DeserializationError::NoMemory);
+  }
+}
+
+TEST_CASE("deserializeMsgPack(ElementProxy)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  doc.add("hello"_s);
+  spy.clearLog();
+
+  SECTION("element already exists") {
+    auto err = deserializeMsgPack(doc[0], "\x91\x2A");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "[[42]]");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("hello")),
+                         });
+  }
+
+  SECTION("element must be created exists") {
+    auto err = deserializeMsgPack(doc[1], "\x91\x2A");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "[\"hello\",[42]]");
+    REQUIRE(spy.log() == AllocatorLog{});
+  }
+}
+
+TEST_CASE("deserializeMsgPack(MemberProxy)") {
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
+  doc["hello"_s] = "world"_s;
+  spy.clearLog();
+
+  SECTION("member already exists") {
+    auto err = deserializeMsgPack(doc["hello"], "\x91\x2A");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "{\"hello\":[42]}");
+    REQUIRE(spy.log() == AllocatorLog{
+                             Deallocate(sizeofString("world")),
+                         });
+  }
+
+  SECTION("member must be created") {
+    auto err = deserializeMsgPack(doc["value"], "\x91\x2A");
+
+    REQUIRE(err == DeserializationError::Ok);
+    REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}");
+    REQUIRE(spy.log() == AllocatorLog{});
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp
index fb2c25d090ab58c87cc4b6c3ff674f9e59114e7e..167261fee956f85ce045716e0a24afe69690debd 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -13,7 +13,7 @@ static void check(const char* input, T expected) {
   uint8_t* f = reinterpret_cast<uint8_t*>(&actual);
   const uint8_t* d = reinterpret_cast<const uint8_t*>(input);
   doubleToFloat(d, f);
-  fixEndianess(actual);
+  fixEndianness(actual);
   CHECK(actual == expected);
 }
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/errors.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/errors.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..01e761af2432d7cf9117a5ebf5bebe5d832127f2
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/errors.cpp
@@ -0,0 +1,201 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+#include <sstream>
+
+#include "Allocators.hpp"
+
+TEST_CASE("deserializeMsgPack() returns InvalidInput") {
+  JsonDocument doc;
+
+  SECTION("integer as key") {
+    auto err = deserializeMsgPack(doc, "\x81\x01\xA1H", 3);
+    REQUIRE(err == DeserializationError::InvalidInput);
+  }
+}
+
+TEST_CASE("deserializeMsgPack() returns EmptyInput") {
+  JsonDocument doc;
+
+  SECTION("from sized buffer") {
+    auto err = deserializeMsgPack(doc, "", 0);
+
+    REQUIRE(err == DeserializationError::EmptyInput);
+  }
+
+  SECTION("from stream") {
+    std::istringstream input("");
+
+    auto err = deserializeMsgPack(doc, input);
+
+    REQUIRE(err == DeserializationError::EmptyInput);
+  }
+}
+
+static void testIncompleteInput(const char* input, size_t len) {
+  JsonDocument doc;
+  REQUIRE(deserializeMsgPack(doc, input, len) == DeserializationError::Ok);
+
+  while (--len) {
+    REQUIRE(deserializeMsgPack(doc, input, len) ==
+            DeserializationError::IncompleteInput);
+  }
+}
+
+TEST_CASE("deserializeMsgPack() returns IncompleteInput") {
+  SECTION("empty input") {
+    testIncompleteInput("\x00", 1);
+  }
+
+  SECTION("fixarray") {
+    testIncompleteInput("\x91\x01", 2);
+  }
+
+  SECTION("array 16") {
+    testIncompleteInput("\xDC\x00\x01\x01", 4);
+  }
+
+  SECTION("array 32") {
+    testIncompleteInput("\xDD\x00\x00\x00\x01\x01", 6);
+  }
+
+  SECTION("fixmap") {
+    testIncompleteInput("\x81\xA3one\x01", 6);
+  }
+
+  SECTION("map 16") {
+    testIncompleteInput("\xDE\x00\x01\xA3one\x01", 8);
+  }
+
+  SECTION("map 32") {
+    testIncompleteInput("\xDF\x00\x00\x00\x01\xA3one\x01", 10);
+    testIncompleteInput("\xDF\x00\x00\x00\x01\xd9\x03one\x01", 11);
+  }
+
+  SECTION("uint 8") {
+    testIncompleteInput("\xcc\x01", 2);
+  }
+
+  SECTION("uint 16") {
+    testIncompleteInput("\xcd\x00\x01", 3);
+  }
+
+  SECTION("uint 32") {
+    testIncompleteInput("\xCE\x00\x00\x00\x01", 5);
+  }
+
+#if ARDUINOJSON_USE_LONG_LONG
+  SECTION("uint 64") {
+    testIncompleteInput("\xCF\x00\x00\x00\x00\x00\x00\x00\x00", 9);
+  }
+#endif
+
+  SECTION("int 8") {
+    testIncompleteInput("\xD0\x01", 2);
+  }
+
+  SECTION("int 16") {
+    testIncompleteInput("\xD1\x00\x01", 3);
+  }
+
+  SECTION("int 32") {
+    testIncompleteInput("\xD2\x00\x00\x00\x01", 5);
+  }
+
+#if ARDUINOJSON_USE_LONG_LONG
+  SECTION("int 64") {
+    testIncompleteInput("\xD3\x00\x00\x00\x00\x00\x00\x00\x00", 9);
+  }
+#endif
+
+  SECTION("float 32") {
+    testIncompleteInput("\xCA\x40\x48\xF5\xC3", 5);
+  }
+
+  SECTION("float 64") {
+    testIncompleteInput("\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F", 9);
+  }
+
+  SECTION("fixstr") {
+    testIncompleteInput("\xABhello world", 12);
+  }
+
+  SECTION("str 8") {
+    testIncompleteInput("\xd9\x05hello", 7);
+  }
+
+  SECTION("str 16") {
+    testIncompleteInput("\xda\x00\x05hello", 8);
+  }
+
+  SECTION("str 32") {
+    testIncompleteInput("\xdb\x00\x00\x00\x05hello", 10);
+  }
+
+  SECTION("bin 8") {
+    testIncompleteInput("\xc4\x01X", 3);
+  }
+
+  SECTION("bin 16") {
+    testIncompleteInput("\xc5\x00\x01X", 4);
+  }
+
+  SECTION("bin 32") {
+    testIncompleteInput("\xc6\x00\x00\x00\x01X", 6);
+  }
+
+  SECTION("ext 8") {
+    testIncompleteInput("\xc7\x01\x01\x01", 4);
+  }
+
+  SECTION("ext 16") {
+    testIncompleteInput("\xc8\x00\x01\x01\x01", 5);
+  }
+
+  SECTION("ext 32") {
+    testIncompleteInput("\xc9\x00\x00\x00\x01\x01\x01", 7);
+  }
+
+  SECTION("fixext 1") {
+    testIncompleteInput("\xd4\x01\x01", 3);
+  }
+
+  SECTION("fixext 2") {
+    testIncompleteInput("\xd5\x01\x01\x02", 4);
+  }
+
+  SECTION("fixext 4") {
+    testIncompleteInput("\xd6\x01\x01\x02\x03\x04", 6);
+  }
+
+  SECTION("fixext 8") {
+    testIncompleteInput("\xd7\x01\x01\x02\x03\x04\x05\x06\x07\x08", 10);
+  }
+
+  SECTION("fixext 16") {
+    testIncompleteInput(
+        "\xd8\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E"
+        "\x0F\x10",
+        18);
+  }
+}
+
+TEST_CASE(
+    "deserializeMsgPack() returns NoMemory when string allocation fails") {
+  TimebombAllocator allocator(0);
+  JsonDocument doc(&allocator);
+
+  SECTION("fixstr") {
+    DeserializationError err = deserializeMsgPack(doc, "\xA5hello", 9);
+    REQUIRE(err == DeserializationError::NoMemory);
+  }
+
+  SECTION("bin 8") {
+    DeserializationError err = deserializeMsgPack(doc, "\xC4\x01X", 3);
+    REQUIRE(err == DeserializationError::NoMemory);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp
index 78700db64c73e8dca5dce22b767defefb8c9941a..dcd2d54309366d0ed1a9fcc285b8370c2a26620c 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -7,13 +7,17 @@
 
 #include <sstream>
 
+#include "Allocators.hpp"
+#include "Literals.hpp"
+
 using namespace ArduinoJson::detail;
 
 TEST_CASE("deserializeMsgPack() filter") {
-  StaticJsonDocument<4096> doc;
+  SpyingAllocator spy;
+  JsonDocument doc(&spy);
   DeserializationError error;
 
-  StaticJsonDocument<200> filter;
+  JsonDocument filter;
   DeserializationOption::Filter filterOpt(filter);
 
   SECTION("root is fixmap") {
@@ -26,7 +30,10 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::IncompleteInput);
         CHECK(doc.as<std::string>() == "{}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                           });
       }
 
       SECTION("input truncated after inside skipped uint 8") {
@@ -35,7 +42,10 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::IncompleteInput);
         CHECK(doc.as<std::string>() == "{}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                           });
       }
 
       SECTION("input truncated after before skipped string size") {
@@ -43,7 +53,10 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::IncompleteInput);
         CHECK(doc.as<std::string>() == "{}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                           });
       }
 
       SECTION("input truncated after before skipped ext size") {
@@ -51,7 +64,10 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::IncompleteInput);
         CHECK(doc.as<std::string>() == "{}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0));
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                           });
       }
 
       SECTION("skip nil") {
@@ -60,7 +76,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("reject 0xc1") {
@@ -68,6 +90,10 @@ TEST_CASE("deserializeMsgPack() filter") {
                                    filterOpt);
 
         CHECK(error == DeserializationError::InvalidInput);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                           });
       }
 
       SECTION("skip false") {
@@ -76,7 +102,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip true") {
@@ -85,7 +117,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip positive fixint") {
@@ -94,7 +132,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip negative fixint") {
@@ -103,7 +147,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip uint 8") {
@@ -112,7 +162,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip int 8") {
@@ -121,7 +177,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip uint 16") {
@@ -130,7 +192,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip int 16") {
@@ -139,7 +207,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip uint 32") {
@@ -149,7 +223,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip int 32") {
@@ -159,7 +239,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip uint 64") {
@@ -170,7 +256,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip int 64") {
@@ -181,7 +273,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip float 32") {
@@ -191,7 +289,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip float 64") {
@@ -202,7 +306,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixstr") {
@@ -211,7 +321,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip str 8") {
@@ -220,7 +336,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip str 16") {
@@ -229,7 +351,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip str 32") {
@@ -239,7 +367,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip bin 8") {
@@ -248,7 +382,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip bin 16") {
@@ -257,7 +397,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip bin 32") {
@@ -267,7 +413,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixarray") {
@@ -276,7 +428,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip array 16") {
@@ -286,7 +444,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip array 32") {
@@ -299,7 +463,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixmap") {
@@ -309,7 +479,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip map 16") {
@@ -321,7 +497,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip map 32") {
@@ -335,7 +517,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixext 1") {
@@ -347,7 +535,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixext 2") {
@@ -359,7 +553,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixext 4") {
@@ -371,7 +571,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixext 8") {
@@ -383,7 +589,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip fixext 16") {
@@ -397,7 +609,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip ext 8") {
@@ -409,7 +627,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip ext 16") {
@@ -421,7 +645,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
 
       SECTION("skip ext 32") {
@@ -433,7 +663,13 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("ignore")),
+                               Deallocate(sizeofString("ignore")),
+                               Allocate(sizeofString("include")),
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofObject(1)),
+                           });
       }
     }
 
@@ -454,8 +690,17 @@ TEST_CASE("deserializeMsgPack() filter") {
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() ==
               "{\"onlyarr\":[{\"measure\":2},{\"measure\":4}],\"include\":42}");
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(2) + 24);
+        CHECK(spy.log() ==
+              AllocatorLog{
+                  Allocate(sizeofString("onlyarr")),
+                  Allocate(sizeofPool()),
+                  Allocate(sizeofString("location")),
+                  Reallocate(sizeofString("location"), sizeofString("measure")),
+                  Allocate(sizeofString("location")),
+                  Reallocate(sizeofString("location"), sizeofString("include")),
+                  Reallocate(sizeofPool(), sizeofObject(2) + sizeofArray(2) +
+                                               2 * sizeofObject(1)),
+              });
       }
 
       SECTION("include array 16") {
@@ -470,8 +715,17 @@ TEST_CASE("deserializeMsgPack() filter") {
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() ==
               "{\"onlyarr\":[{\"measure\":2},{\"measure\":4}],\"include\":42}");
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(2) + 24);
+        CHECK(spy.log() ==
+              AllocatorLog{
+                  Allocate(sizeofString("onlyarr")),
+                  Allocate(sizeofPool()),
+                  Allocate(sizeofString("location")),
+                  Reallocate(sizeofString("location"), sizeofString("measure")),
+                  Allocate(sizeofString("location")),
+                  Reallocate(sizeofString("location"), sizeofString("include")),
+                  Reallocate(sizeofPool(), sizeofObject(2) + sizeofArray(2) +
+                                               2 * sizeofObject(1)),
+              });
       }
 
       SECTION("include array 32") {
@@ -486,8 +740,17 @@ TEST_CASE("deserializeMsgPack() filter") {
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() ==
               "{\"onlyarr\":[{\"measure\":2},{\"measure\":4}],\"include\":42}");
-        CHECK(doc.memoryUsage() ==
-              JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(2) + 24);
+        CHECK(spy.log() ==
+              AllocatorLog{
+                  Allocate(sizeofString("onlyarr")),
+                  Allocate(sizeofPool()),
+                  Allocate(sizeofString("location")),
+                  Reallocate(sizeofString("location"), sizeofString("measure")),
+                  Allocate(sizeofString("location")),
+                  Reallocate(sizeofString("location"), sizeofString("include")),
+                  Reallocate(sizeofPool(), sizeofObject(2) + sizeofArray(2) +
+                                               2 * sizeofObject(1)),
+              });
       }
 
       SECTION("skip null") {
@@ -496,7 +759,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip false") {
@@ -505,7 +773,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip true") {
@@ -514,7 +787,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip positive fixint") {
@@ -523,7 +801,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip negative fixint") {
@@ -532,7 +815,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip uint 8") {
@@ -541,7 +829,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip uint 16") {
@@ -550,7 +843,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip uint 32") {
@@ -560,7 +858,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip uint 64") {
@@ -571,7 +874,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip int 8") {
@@ -580,7 +888,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip int 16") {
@@ -589,7 +902,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip int 32") {
@@ -599,7 +917,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip int 64") {
@@ -610,7 +933,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip float 32") {
@@ -620,7 +948,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip float 64") {
@@ -631,7 +964,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip fixstr") {
@@ -640,7 +978,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip str 8") {
@@ -662,7 +1005,12 @@ TEST_CASE("deserializeMsgPack() filter") {
             doc, "\x82\xA7onlyarr\xdb\x00\x00\x00\x05hello\xA7include\x2A",
             filterOpt);
 
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip fixmap") {
@@ -672,7 +1020,14 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("one")),
+                               Deallocate(sizeofString("one")),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip map 16") {
@@ -684,7 +1039,14 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("H")),
+                               Deallocate(sizeofString("H")),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
 
       SECTION("skip map 32") {
@@ -698,7 +1060,14 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "{\"onlyarr\":null,\"include\":42}");
-        CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofString("onlyarr")),
+                               Allocate(sizeofPool()),
+                               Allocate(sizeofString("zero")),
+                               Deallocate(sizeofString("zero")),
+                               Allocate(sizeofString("include")),
+                               Reallocate(sizeofPool(), sizeofObject(2)),
+                           });
       }
     }
   }
@@ -713,7 +1082,7 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "[]");
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(0));
+        CHECK(spy.log() == AllocatorLog());
       }
     }
 
@@ -726,7 +1095,10 @@ TEST_CASE("deserializeMsgPack() filter") {
 
         CHECK(error == DeserializationError::Ok);
         CHECK(doc.as<std::string>() == "[1,2,3]");
-        CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(3));
+        CHECK(spy.log() == AllocatorLog{
+                               Allocate(sizeofPool()),
+                               Reallocate(sizeofPool(), sizeofArray(3)),
+                           });
       }
     }
   }
@@ -747,8 +1119,15 @@ TEST_CASE("deserializeMsgPack() filter") {
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() ==
             "{\"onlyobj\":{\"measure\":2},\"include\":42}");
-      CHECK(doc.memoryUsage() ==
-            JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1) + 24);
+      CHECK(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofString("onlyobj")),
+                Allocate(sizeofPool()),
+                Allocate(sizeofString("location")),
+                Reallocate(sizeofString("location"), sizeofString("measure")),
+                Allocate(sizeofString("include")),
+                Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
+            });
     }
 
     SECTION("include map 16") {
@@ -761,8 +1140,15 @@ TEST_CASE("deserializeMsgPack() filter") {
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() ==
             "{\"onlyobj\":{\"measure\":2},\"include\":42}");
-      CHECK(doc.memoryUsage() ==
-            JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1) + 24);
+      CHECK(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofString("onlyobj")),
+                Allocate(sizeofPool()),
+                Allocate(sizeofString("location")),
+                Reallocate(sizeofString("location"), sizeofString("measure")),
+                Allocate(sizeofString("include")),
+                Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
+            });
     }
 
     SECTION("include map 32") {
@@ -776,8 +1162,15 @@ TEST_CASE("deserializeMsgPack() filter") {
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() ==
             "{\"onlyobj\":{\"measure\":2},\"include\":42}");
-      CHECK(doc.memoryUsage() ==
-            JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1) + 24);
+      CHECK(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofString("onlyobj")),
+                Allocate(sizeofPool()),
+                Allocate(sizeofString("location")),
+                Reallocate(sizeofString("location"), sizeofString("measure")),
+                Allocate(sizeofString("include")),
+                Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
+            });
     }
 
     SECTION("skip null") {
@@ -786,7 +1179,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip false") {
@@ -795,7 +1193,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip true") {
@@ -804,7 +1207,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip positive fixint") {
@@ -813,7 +1221,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip negative fixint") {
@@ -822,7 +1235,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip uint 8") {
@@ -831,7 +1249,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip uint 16") {
@@ -840,7 +1263,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip uint 32") {
@@ -849,7 +1277,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip uint 64") {
@@ -860,7 +1293,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip int 8") {
@@ -869,7 +1307,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip int 16") {
@@ -878,7 +1321,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip int 32") {
@@ -887,7 +1335,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip int 64") {
@@ -898,7 +1351,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip float 32") {
@@ -907,7 +1365,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip float 64") {
@@ -918,7 +1381,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip fixstr") {
@@ -927,7 +1395,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip str 8") {
@@ -949,7 +1422,12 @@ TEST_CASE("deserializeMsgPack() filter") {
           doc, "\x82\xA7onlyobj\xdb\x00\x00\x00\x05hello\xA7include\x2A",
           filterOpt);
 
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip fixarray") {
@@ -958,7 +1436,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip array 16") {
@@ -969,7 +1452,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
 
     SECTION("skip array 32") {
@@ -981,7 +1469,12 @@ TEST_CASE("deserializeMsgPack() filter") {
 
       CHECK(error == DeserializationError::Ok);
       CHECK(doc.as<std::string>() == "{\"onlyobj\":null,\"include\":42}");
-      CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
+      CHECK(spy.log() == AllocatorLog{
+                             Allocate(sizeofString("onlyarr")),
+                             Allocate(sizeofPool()),
+                             Allocate(sizeofString("include")),
+                             Reallocate(sizeofPool(), sizeofObject(2)),
+                         });
     }
   }
 
@@ -1032,10 +1525,10 @@ TEST_CASE("deserializeMsgPack() filter") {
 TEST_CASE("Zero-copy mode") {  // issue #1697
   char input[] = "\x82\xA7include\x01\xA6ignore\x02";
 
-  StaticJsonDocument<256> filter;
+  JsonDocument filter;
   filter["include"] = true;
 
-  StaticJsonDocument<256> doc;
+  JsonDocument doc;
   DeserializationError err =
       deserializeMsgPack(doc, input, 18, DeserializationOption::Filter(filter));
 
@@ -1044,8 +1537,8 @@ TEST_CASE("Zero-copy mode") {  // issue #1697
 }
 
 TEST_CASE("Overloads") {
-  StaticJsonDocument<256> doc;
-  StaticJsonDocument<256> filter;
+  JsonDocument doc;
+  JsonDocument filter;
 
   using namespace DeserializationOption;
 
@@ -1060,7 +1553,7 @@ TEST_CASE("Overloads") {
   }
 
   SECTION("const std::string&, Filter") {
-    deserializeMsgPack(doc, std::string("{}"), Filter(filter));
+    deserializeMsgPack(doc, "{}"_s, Filter(filter));
   }
 
   SECTION("std::istream&, Filter") {
@@ -1088,7 +1581,7 @@ TEST_CASE("Overloads") {
   }
 
   SECTION("const std::string&, Filter, NestingLimit") {
-    deserializeMsgPack(doc, std::string("{}"), Filter(filter), NestingLimit(5));
+    deserializeMsgPack(doc, "{}"_s, Filter(filter), NestingLimit(5));
   }
 
   SECTION("std::istream&, Filter, NestingLimit") {
@@ -1116,7 +1609,7 @@ TEST_CASE("Overloads") {
   }
 
   SECTION("const std::string&, NestingLimit, Filter") {
-    deserializeMsgPack(doc, std::string("{}"), NestingLimit(5), Filter(filter));
+    deserializeMsgPack(doc, "{}"_s, NestingLimit(5), Filter(filter));
   }
 
   SECTION("std::istream&, NestingLimit, Filter") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/incompleteInput.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/incompleteInput.cpp
deleted file mode 100644
index 6a563882533fe52253a70dbe1ee8eff902ef3bd7..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/incompleteInput.cpp
+++ /dev/null
@@ -1,158 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-DeserializationError deserialize(const char* input, size_t len) {
-  DynamicJsonDocument doc(4096);
-
-  return deserializeMsgPack(doc, input, len);
-}
-
-void checkAllSizes(const char* input, size_t len) {
-  REQUIRE(deserialize(input, len) == DeserializationError::Ok);
-
-  while (--len) {
-    REQUIRE(deserialize(input, len) == DeserializationError::IncompleteInput);
-  }
-}
-
-TEST_CASE("deserializeMsgPack() returns IncompleteInput") {
-  SECTION("empty input") {
-    checkAllSizes("\x00", 1);
-  }
-
-  SECTION("fixarray") {
-    checkAllSizes("\x91\x01", 2);
-  }
-
-  SECTION("array 16") {
-    checkAllSizes("\xDC\x00\x01\x01", 4);
-  }
-
-  SECTION("array 32") {
-    checkAllSizes("\xDD\x00\x00\x00\x01\x01", 6);
-  }
-
-  SECTION("fixmap") {
-    checkAllSizes("\x81\xA3one\x01", 6);
-  }
-
-  SECTION("map 16") {
-    checkAllSizes("\xDE\x00\x01\xA3one\x01", 8);
-  }
-
-  SECTION("map 32") {
-    checkAllSizes("\xDF\x00\x00\x00\x01\xA3one\x01", 10);
-    checkAllSizes("\xDF\x00\x00\x00\x01\xd9\x03one\x01", 11);
-  }
-
-  SECTION("uint 8") {
-    checkAllSizes("\xcc\x01", 2);
-  }
-
-  SECTION("uint 16") {
-    checkAllSizes("\xcd\x00\x01", 3);
-  }
-
-  SECTION("uint 32") {
-    checkAllSizes("\xCE\x00\x00\x00\x01", 5);
-  }
-
-#if ARDUINOJSON_USE_LONG_LONG
-  SECTION("uint 64") {
-    checkAllSizes("\xCF\x00\x00\x00\x00\x00\x00\x00\x00", 9);
-  }
-#endif
-
-  SECTION("int 8") {
-    checkAllSizes("\xD0\x01", 2);
-  }
-
-  SECTION("int 16") {
-    checkAllSizes("\xD1\x00\x01", 3);
-  }
-
-  SECTION("int 32") {
-    checkAllSizes("\xD2\x00\x00\x00\x01", 5);
-  }
-
-#if ARDUINOJSON_USE_LONG_LONG
-  SECTION("int 64") {
-    checkAllSizes("\xD3\x00\x00\x00\x00\x00\x00\x00\x00", 9);
-  }
-#endif
-
-  SECTION("float 32") {
-    checkAllSizes("\xCA\x40\x48\xF5\xC3", 5);
-  }
-
-  SECTION("float 64") {
-    checkAllSizes("\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F", 9);
-  }
-
-  SECTION("fixstr") {
-    checkAllSizes("\xABhello world", 12);
-  }
-
-  SECTION("str 8") {
-    checkAllSizes("\xd9\x05hello", 7);
-  }
-
-  SECTION("str 16") {
-    checkAllSizes("\xda\x00\x05hello", 8);
-  }
-
-  SECTION("str 32") {
-    checkAllSizes("\xdb\x00\x00\x00\x05hello", 10);
-  }
-
-  SECTION("bin 8") {
-    checkAllSizes("\xc4\x01X", 3);
-  }
-
-  SECTION("bin 16") {
-    checkAllSizes("\xc5\x00\x01X", 4);
-  }
-
-  SECTION("bin 32") {
-    checkAllSizes("\xc6\x00\x00\x00\x01X", 6);
-  }
-
-  SECTION("ext 8") {
-    checkAllSizes("\xc7\x01\x01\x01", 4);
-  }
-
-  SECTION("ext 16") {
-    checkAllSizes("\xc8\x00\x01\x01\x01", 5);
-  }
-
-  SECTION("ext 32") {
-    checkAllSizes("\xc9\x00\x00\x00\x01\x01\x01", 7);
-  }
-
-  SECTION("fixext 1") {
-    checkAllSizes("\xd4\x01\x01", 3);
-  }
-
-  SECTION("fixext 2") {
-    checkAllSizes("\xd5\x01\x01\x02", 4);
-  }
-
-  SECTION("fixext 4") {
-    checkAllSizes("\xd6\x01\x01\x02\x03\x04", 6);
-  }
-
-  SECTION("fixext 8") {
-    checkAllSizes("\xd7\x01\x01\x02\x03\x04\x05\x06\x07\x08", 10);
-  }
-
-  SECTION("fixext 16") {
-    checkAllSizes(
-        "\xd8\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E"
-        "\x0F\x10",
-        18);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp
index 783aaa2c1e5751b8b7ea7ce7927a3c7c9ae90b5b..b581f201cd017427f84d6934dcbe6f61931acc8b 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp
@@ -1,14 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 #include "CustomReader.hpp"
+#include "Literals.hpp"
+
+using ArduinoJson::detail::sizeofObject;
 
 TEST_CASE("deserializeMsgPack(const std::string&)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("should accept const string") {
     const std::string input("\x92\x01\x02");
@@ -19,8 +22,7 @@ TEST_CASE("deserializeMsgPack(const std::string&)") {
   }
 
   SECTION("should accept temporary string") {
-    DeserializationError err =
-        deserializeMsgPack(doc, std::string("\x92\x01\x02"));
+    DeserializationError err = deserializeMsgPack(doc, "\x92\x01\x02"_s);
 
     REQUIRE(err == DeserializationError::Ok);
   }
@@ -33,12 +35,11 @@ TEST_CASE("deserializeMsgPack(const std::string&)") {
 
     JsonArray array = doc.as<JsonArray>();
     REQUIRE(err == DeserializationError::Ok);
-    REQUIRE(std::string("hello") == array[0]);
+    REQUIRE("hello"_s == array[0]);
   }
 
   SECTION("should accept a zero in input") {
-    DeserializationError err =
-        deserializeMsgPack(doc, std::string("\x92\x00\x02", 3));
+    DeserializationError err = deserializeMsgPack(doc, "\x92\x00\x02"_s);
 
     REQUIRE(err == DeserializationError::Ok);
     JsonArray arr = doc.as<JsonArray>();
@@ -48,10 +49,10 @@ TEST_CASE("deserializeMsgPack(const std::string&)") {
 }
 
 TEST_CASE("deserializeMsgPack(std::istream&)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("should accept a zero in input") {
-    std::istringstream input(std::string("\x92\x00\x02", 3));
+    std::istringstream input("\x92\x00\x02"_s);
 
     DeserializationError err = deserializeMsgPack(doc, input);
 
@@ -76,7 +77,7 @@ TEST_CASE("deserializeMsgPack(VLA)") {
   char vla[i];
   memcpy(vla, "\xDE\x00\x01\xA5Hello\xA5world", 15);
 
-  StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc;
+  JsonDocument doc;
   DeserializationError err = deserializeMsgPack(doc, vla);
 
   REQUIRE(err == DeserializationError::Ok);
@@ -84,7 +85,7 @@ TEST_CASE("deserializeMsgPack(VLA)") {
 #endif
 
 TEST_CASE("deserializeMsgPack(CustomReader)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   CustomReader reader("\x92\xA5Hello\xA5world");
   DeserializationError err = deserializeMsgPack(doc, reader);
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/misc.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/misc.cpp
deleted file mode 100644
index 25137b35898dccdd3e648bafecec26be2f70b192..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/misc.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-#include <sstream>
-
-TEST_CASE("deserializeMsgPack() returns EmptyInput") {
-  StaticJsonDocument<100> doc;
-
-  SECTION("from sized buffer") {
-    DeserializationError err = deserializeMsgPack(doc, "", 0);
-
-    REQUIRE(err == DeserializationError::EmptyInput);
-  }
-
-  SECTION("from stream") {
-    std::istringstream input("");
-
-    DeserializationError err = deserializeMsgPack(doc, input);
-
-    REQUIRE(err == DeserializationError::EmptyInput);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp
index 2936415a55e416659f76c051a1cb2ddcd938bd64..206600cd3e89406ec664a693a0e5e04aeda70393 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
@@ -12,7 +12,7 @@
   REQUIRE(DeserializationError::TooDeep == expression);
 
 TEST_CASE("JsonDeserializer nesting") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
 
   SECTION("Input = const char*") {
     SECTION("limit = 0") {
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/notSupported.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/notSupported.cpp
deleted file mode 100644
index 2be0f1ec5f85f75ce8553a953c3175594ee0f6ec..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackDeserializer/notSupported.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#include <ArduinoJson.h>
-#include <catch.hpp>
-
-static void checkMsgPackDocument(const char* input, size_t inputSize,
-                                 const char* expectedJson) {
-  DynamicJsonDocument doc(4096);
-
-  DeserializationError error = deserializeMsgPack(doc, input, inputSize);
-
-  REQUIRE(error == DeserializationError::Ok);
-  std::string actualJson;
-  serializeJson(doc, actualJson);
-  REQUIRE(actualJson == expectedJson);
-}
-
-static void checkMsgPackError(const char* input, size_t inputSize,
-                              DeserializationError expectedError) {
-  DynamicJsonDocument doc(4096);
-
-  DeserializationError error = deserializeMsgPack(doc, input, inputSize);
-
-  REQUIRE(error == expectedError);
-}
-
-TEST_CASE("deserializeMsgPack() return NotSupported") {
-  SECTION("bin 8") {
-    checkMsgPackDocument("\x92\xc4\x01X\x2A", 5, "[null,42]");
-  }
-
-  SECTION("bin 16") {
-    checkMsgPackDocument("\x92\xc5\x00\x01X\x2A", 6, "[null,42]");
-  }
-
-  SECTION("bin 32") {
-    checkMsgPackDocument("\x92\xc6\x00\x00\x00\x01X\x2A", 8, "[null,42]");
-  }
-
-  SECTION("ext 8") {
-    checkMsgPackDocument("\x92\xc7\x01\x01\x01\x2A", 6, "[null,42]");
-  }
-
-  SECTION("ext 16") {
-    checkMsgPackDocument("\x92\xc8\x00\x01\x01\x01\x2A", 7, "[null,42]");
-  }
-
-  SECTION("ext 32") {
-    checkMsgPackDocument("\x92\xc9\x00\x00\x00\x01\x01\x01\x2A", 9,
-                         "[null,42]");
-  }
-
-  SECTION("fixext 1") {
-    checkMsgPackDocument("\x92\xd4\x01\x01\x2A", 5, "[null,42]");
-  }
-
-  SECTION("fixext 2") {
-    checkMsgPackDocument("\x92\xd5\x01\x01\x02\x2A", 6, "[null,42]");
-  }
-
-  SECTION("fixext 4") {
-    checkMsgPackDocument("\x92\xd6\x01\x01\x02\x03\x04\x2A", 8, "[null,42]");
-  }
-
-  SECTION("fixext 8") {
-    checkMsgPackDocument("\x92\xd7\x01\x01\x02\x03\x04\x05\x06\x07\x08\x2A", 12,
-                         "[null,42]");
-  }
-
-  SECTION("fixext 16") {
-    checkMsgPackDocument(
-        "\x92\xd8\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E"
-        "\x0F\x10\x2A",
-        20, "[null,42]");
-  }
-
-  SECTION("integer as key") {
-    checkMsgPackError("\x81\x01\xA1H", 3, DeserializationError::InvalidInput);
-  }
-}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt
index 2f3d8cc76daf494eecc89ebcd6c2ba330695737b..25b0384d5e698cb868d7696cad33771da22e50a4 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(MsgPackSerializerTests
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp
index 8b0dffa00cdcb11cc420af5c8a2e7e12848ef994..569d5f7d9b49536defebe83a8f063bebe6422640 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("serialize MsgPack to various destination types") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject object = doc.to<JsonObject>();
   object["hello"] = "world";
   const char* expected_result = "\x81\xA5hello\xA5world";
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp
index a6a4fc9dcec8767011d2790ecaa08b3eafa4bcce..e1480e2f76b94cf2dd90a4fd46da1ab8e6ff134a 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp
@@ -1,12 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
 TEST_CASE("measureMsgPack()") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject object = doc.to<JsonObject>();
   object["hello"] = "world";
 
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp
index 4753dbf4e9e5311ae1262794711f2cdbebf327cc..bea9b242a95916603394eb9f447db693e867bab6 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp
@@ -4,7 +4,7 @@
 
 template <typename T>
 void check(T value, const std::string& expected) {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   doc.to<JsonVariant>().set(value);
   char buffer[256] = "";
   size_t returnValue = serializeMsgPack(doc, buffer, sizeof(buffer));
@@ -13,7 +13,7 @@ void check(T value, const std::string& expected) {
 }
 
 TEST_CASE("serializeMsgPack(MemberProxy)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   deserializeJson(doc, "{\"hello\":42}");
   JsonObject obj = doc.as<JsonObject>();
   std::string result;
@@ -24,7 +24,7 @@ TEST_CASE("serializeMsgPack(MemberProxy)") {
 }
 
 TEST_CASE("serializeMsgPack(ElementProxy)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   deserializeJson(doc, "[42]");
   JsonArray arr = doc.as<JsonArray>();
   std::string result;
@@ -35,7 +35,7 @@ TEST_CASE("serializeMsgPack(ElementProxy)") {
 }
 
 TEST_CASE("serializeMsgPack(JsonVariantSubscript)") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   deserializeJson(doc, "[42]");
   JsonVariant var = doc.as<JsonVariant>();
   std::string result;
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp
index a8ec639c9e3d3b37e3e76faf320a177ce10d36ba..3e36b9f247a62ae7c1bb872a7f07094c0c04133b 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp
@@ -1,10 +1,14 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
+#define ARDUINOJSON_SLOT_ID_SIZE 4  // required to reach 65536 elements
+
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 static void check(const JsonArray array, const char* expected_data,
                   size_t expected_len) {
   std::string expected(expected_data, expected_data + expected_len);
@@ -26,7 +30,7 @@ static void check(const JsonArray array, const std::string& expected) {
 }
 
 TEST_CASE("serialize MsgPack array") {
-  DynamicJsonDocument doc(JSON_ARRAY_SIZE(65536));
+  JsonDocument doc;
   JsonArray array = doc.to<JsonArray>();
 
   SECTION("empty") {
@@ -53,8 +57,8 @@ TEST_CASE("serialize MsgPack array") {
     const char* nil = 0;
     for (int i = 0; i < 65536; i++)
       array.add(nil);
+    REQUIRE(array.size() == 65536);
 
-    check(array,
-          std::string("\xDD\x00\x01\x00\x00", 5) + std::string(65536, '\xc0'));
+    check(array, "\xDD\x00\x01\x00\x00"_s + std::string(65536, '\xc0'));
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp
index 88173074350232101c9a8184c7b40145b2b55a39..07774d03d8e23cc8af5adae92d50749e0b5739c8 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp
@@ -1,11 +1,13 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <stdio.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 static void check(const JsonObject object, const char* expected_data,
                   size_t expected_len) {
   std::string expected(expected_data, expected_data + expected_len);
@@ -28,7 +30,7 @@ static void check(const JsonObject object, const char (&expected_data)[N]) {
 //}
 
 TEST_CASE("serialize MsgPack object") {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonObject object = doc.to<JsonObject>();
 
   SECTION("empty") {
@@ -44,7 +46,7 @@ TEST_CASE("serialize MsgPack object") {
   SECTION("map 16") {
     for (int i = 0; i < 16; ++i) {
       char key[16];
-      sprintf(key, "i%X", i);
+      snprintf(key, sizeof(key), "i%X", i);
       object[key] = i;
     }
 
@@ -60,7 +62,7 @@ TEST_CASE("serialize MsgPack object") {
   //
   //   for (int i = 0; i < 65536; ++i) {
   //     char kv[16];
-  //     sprintf(kv, "%04x", i);
+  //     snprintf(kv, sizeof(kv), "%04x", i);
   //     object[kv] = kv;
   //     expected += '\xA4';
   //     expected += kv;
@@ -77,7 +79,7 @@ TEST_CASE("serialize MsgPack object") {
   }
 
   SECTION("serialized(std::string)") {
-    object["hello"] = serialized(std::string("\xDB\x00\x01\x00\x00", 5));
+    object["hello"] = serialized("\xDB\x00\x01\x00\x00"_s);
     check(object, "\x81\xA5hello\xDB\x00\x01\x00\x00");
   }
 }
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp
index b8a021f926749910168ef4c98f11de26481a064c..43ca3ff4369419281fd9f8eab33a2c81a0d4fb09 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp
@@ -1,14 +1,16 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.h>
 #include <catch.hpp>
 
+#include "Literals.hpp"
+
 template <typename T>
 static void checkVariant(T value, const char* expected_data,
                          size_t expected_len) {
-  DynamicJsonDocument doc(4096);
+  JsonDocument doc;
   JsonVariant variant = doc.to<JsonVariant>();
   variant.set(value);
   std::string expected(expected_data, expected_data + expected_len);
@@ -129,16 +131,15 @@ TEST_CASE("serialize MsgPack value") {
 
   SECTION("str 16") {
     std::string shortest(256, '?');
-    checkVariant(shortest.c_str(), std::string("\xDA\x01\x00", 3) + shortest);
+    checkVariant(shortest.c_str(), "\xDA\x01\x00"_s + shortest);
 
     std::string longest(65535, '?');
-    checkVariant(longest.c_str(), std::string("\xDA\xFF\xFF", 3) + longest);
+    checkVariant(longest.c_str(), "\xDA\xFF\xFF"_s + longest);
   }
 
   SECTION("str 32") {
     std::string shortest(65536, '?');
-    checkVariant(shortest.c_str(),
-                 std::string("\xDB\x00\x01\x00\x00", 5) + shortest);
+    checkVariant(shortest.c_str(), "\xDB\x00\x01\x00\x00"_s + shortest);
   }
 
   SECTION("serialized(const char*)") {
@@ -146,6 +147,56 @@ TEST_CASE("serialize MsgPack value") {
     checkVariant(serialized("\xDB\x00\x01\x00\x00", 5), "\xDB\x00\x01\x00\x00");
   }
 
+  SECTION("bin 8") {
+    checkVariant(MsgPackBinary("?", 1), "\xC4\x01?");
+  }
+
+  SECTION("bin 16") {
+    auto str = std::string(256, '?');
+    checkVariant(MsgPackBinary(str.data(), str.size()), "\xC5\x01\x00"_s + str);
+  }
+
+  // bin 32 is tested in string_length_size_4.cpp
+
+  SECTION("fixext 1") {
+    checkVariant(MsgPackExtension(1, "\x02", 1), "\xD4\x01\x02");
+  }
+
+  SECTION("fixext 2") {
+    checkVariant(MsgPackExtension(1, "\x03\x04", 2), "\xD5\x01\x03\x04");
+  }
+
+  SECTION("fixext 4") {
+    checkVariant(MsgPackExtension(1, "\x05\x06\x07\x08", 4),
+                 "\xD6\x01\x05\x06\x07\x08");
+  }
+
+  SECTION("fixext 8") {
+    checkVariant(MsgPackExtension(1, "????????", 8), "\xD7\x01????????");
+  }
+
+  SECTION("fixext 16") {
+    checkVariant(MsgPackExtension(1, "????????????????", 16),
+                 "\xD8\x01????????????????");
+  }
+
+  SECTION("ext 8") {
+    checkVariant(MsgPackExtension(2, "???", 3), "\xC7\x03\x02???");
+    checkVariant(MsgPackExtension(2, "?????", 5), "\xC7\x05\x02?????");
+    checkVariant(MsgPackExtension(2, "???????", 7), "\xC7\x07\x02???????");
+    checkVariant(MsgPackExtension(2, "?????????", 9), "\xC7\x09\x02?????????");
+    checkVariant(MsgPackExtension(2, "???????????????", 15),
+                 "\xC7\x0F\x02???????????????");
+    checkVariant(MsgPackExtension(2, "?????????????????", 17),
+                 "\xC7\x11\x02?????????????????");
+  }
+
+  SECTION("ext 16") {
+    auto str = std::string(256, '?');
+    checkVariant(MsgPackExtension(2, str.data(), str.size()),
+                 "\xC8\x01\x00\x02"_s + str);
+  }
+
   SECTION("serialize round double as integer") {  // Issue #1718
     checkVariant(-32768.0, "\xD1\x80\x00");
     checkVariant(-129.0, "\xD1\xFF\x7F");
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/CMakeLists.txt
index 48cf8c0f1f6e9359d0ac711e39dd720cc9050e65..c3d6dc0705332818329190e41260b750d3216368 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(NumbersTests
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/convertNumber.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/convertNumber.cpp
index ca0cc5e03e0a35538e547040c1892002292bd486..7a2ae533adf28b6eed4aeace6109b2afd1cf2506 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/convertNumber.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/convertNumber.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <stdint.h>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseDouble.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseDouble.cpp
index 301aa0ce00bc642ec870f51db65bd8460e8de269..cd84ca4aa50be8988b8318c3db4b4397b96b7a74 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseDouble.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseDouble.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_USE_DOUBLE 1
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseFloat.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseFloat.cpp
index 4e9c1d47787c0bf2bcdbb4f31761e37b96e701d0..4a42f442afb24ec82d0e941290023470cef468e6 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseFloat.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseFloat.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #define ARDUINOJSON_USE_DOUBLE 0
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseInteger.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseInteger.cpp
index 880da7640600ac770b8364082ac5fea7d66cddc4..21803045b8a8cde3cdbb2b58f60bbb94563d35ec 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseInteger.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseInteger.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <stdint.h>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseNumber.cpp b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseNumber.cpp
index 48163c15060f2776e7175562a0bbb926111c7054..ba9412fc02b97126d54ddae77897515369f7ba89 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseNumber.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/Numbers/parseNumber.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <ArduinoJson.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9214f5b90d17e1e7aa47325d15a162382d033cc0
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/CMakeLists.txt
@@ -0,0 +1,25 @@
+# ArduinoJson - https://arduinojson.org
+# Copyright © 2014-2024, Benoit BLANCHON
+# MIT License
+
+add_executable(ResourceManagerTests
+	allocVariant.cpp
+	clear.cpp
+	saveString.cpp
+	shrinkToFit.cpp
+	size.cpp
+	StringBuilder.cpp
+	swap.cpp
+)
+
+add_compile_definitions(ResourceManagerTests
+	ARDUINOJSON_SLOT_ID_SIZE=1 # require less RAM for overflow tests
+	ARDUINOJSON_POOL_CAPACITY=16
+)
+
+add_test(ResourceManager ResourceManagerTests)
+
+set_tests_properties(ResourceManager
+	PROPERTIES
+		LABELS "Catch"
+)
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/StringBuilder.cpp b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/StringBuilder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f8398587779577660ac8ab58a4282ff43ab79e6d
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/StringBuilder.cpp
@@ -0,0 +1,144 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson/Memory/StringBuilder.hpp>
+#include <ArduinoJson/Memory/VariantPoolImpl.hpp>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+
+using namespace ArduinoJson::detail;
+
+TEST_CASE("StringBuilder") {
+  KillswitchAllocator killswitch;
+  SpyingAllocator spyingAllocator(&killswitch);
+  ResourceManager resources(&spyingAllocator);
+
+  SECTION("Empty string") {
+    StringBuilder str(&resources);
+
+    str.startString();
+    str.save();
+
+    REQUIRE(resources.size() == sizeofString(""));
+    REQUIRE(resources.overflowed() == false);
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                Reallocate(sizeofStringBuffer(), sizeofString("")),
+            });
+  }
+
+  SECTION("Short string fits in first allocation") {
+    StringBuilder str(&resources);
+
+    str.startString();
+    str.append("hello");
+
+    REQUIRE(str.isValid() == true);
+    REQUIRE(str.str() == "hello");
+    REQUIRE(resources.overflowed() == false);
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         Allocate(sizeofStringBuffer()),
+                                     });
+  }
+
+  SECTION("Long string needs reallocation") {
+    StringBuilder str(&resources);
+    const char* lorem =
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+        "eiusmod tempor incididunt ut labore et dolore magna aliqua.";
+
+    str.startString();
+    str.append(lorem);
+
+    REQUIRE(str.isValid() == true);
+    REQUIRE(str.str() == lorem);
+    REQUIRE(resources.overflowed() == false);
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer(1)),
+                Reallocate(sizeofStringBuffer(1), sizeofStringBuffer(2)),
+                Reallocate(sizeofStringBuffer(2), sizeofStringBuffer(3)),
+            });
+  }
+
+  SECTION("Realloc fails") {
+    StringBuilder str(&resources);
+
+    str.startString();
+    killswitch.on();
+    str.append(
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+        "eiusmod tempor incididunt ut labore et dolore magna aliqua.");
+
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofStringBuffer()),
+                ReallocateFail(sizeofStringBuffer(), sizeofStringBuffer(2)),
+                Deallocate(sizeofStringBuffer()),
+            });
+    REQUIRE(str.isValid() == false);
+    REQUIRE(resources.overflowed() == true);
+  }
+
+  SECTION("Initial allocation fails") {
+    StringBuilder str(&resources);
+
+    killswitch.on();
+    str.startString();
+
+    REQUIRE(str.isValid() == false);
+    REQUIRE(resources.overflowed() == true);
+    REQUIRE(spyingAllocator.log() == AllocatorLog{
+                                         AllocateFail(sizeofStringBuffer()),
+                                     });
+  }
+}
+
+static StringNode* addStringToPool(ResourceManager& resources, const char* s) {
+  StringBuilder str(&resources);
+  str.startString();
+  str.append(s);
+  return str.save();
+}
+
+TEST_CASE("StringBuilder::save() deduplicates strings") {
+  ResourceManager resources;
+
+  SECTION("Basic") {
+    auto s1 = addStringToPool(resources, "hello");
+    auto s2 = addStringToPool(resources, "world");
+    auto s3 = addStringToPool(resources, "hello");
+
+    REQUIRE(s1 == s3);
+    REQUIRE(s2 != s3);
+    REQUIRE(s1->references == 2);
+    REQUIRE(s2->references == 1);
+    REQUIRE(s3->references == 2);
+    REQUIRE(resources.size() == sizeofString("hello") + sizeofString("world"));
+  }
+
+  SECTION("Requires terminator") {
+    auto s1 = addStringToPool(resources, "hello world");
+    auto s2 = addStringToPool(resources, "hello");
+
+    REQUIRE(s2 != s1);
+    REQUIRE(s1->references == 1);
+    REQUIRE(s2->references == 1);
+    REQUIRE(resources.size() ==
+            sizeofString("hello world") + sizeofString("hello"));
+  }
+
+  SECTION("Don't overrun") {
+    auto s1 = addStringToPool(resources, "hello world");
+    auto s2 = addStringToPool(resources, "wor");
+
+    REQUIRE(s2 != s1);
+    REQUIRE(s1->references == 1);
+    REQUIRE(s2->references == 1);
+    REQUIRE(resources.size() ==
+            sizeofString("hello world") + sizeofString("wor"));
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/allocVariant.cpp b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/allocVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4a3685d8514b5a62e74c375bec16bee3ca6d458b
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/allocVariant.cpp
@@ -0,0 +1,92 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.hpp>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+
+using namespace ArduinoJson::detail;
+
+TEST_CASE("ResourceManager::allocSlot()") {
+  SECTION("Returns different pointer") {
+    ResourceManager resources;
+
+    VariantSlot* s1 = resources.allocSlot();
+    REQUIRE(s1 != 0);
+    VariantSlot* s2 = resources.allocSlot();
+    REQUIRE(s2 != 0);
+
+    REQUIRE(s1 != s2);
+  }
+
+  SECTION("Returns the same slot after calling freeSlot()") {
+    ResourceManager resources;
+
+    auto s1 = resources.allocSlot();
+    auto s2 = resources.allocSlot();
+    resources.freeSlot(s1);
+    resources.freeSlot(s2);
+    auto s3 = resources.allocSlot();
+    auto s4 = resources.allocSlot();
+    auto s5 = resources.allocSlot();
+
+    REQUIRE(s2.id() != s1.id());
+    REQUIRE(s3.id() == s2.id());
+    REQUIRE(s4.id() == s1.id());
+    REQUIRE(s5.id() != s1.id());
+    REQUIRE(s5.id() != s2.id());
+  }
+
+  SECTION("Returns aligned pointers") {
+    ResourceManager resources;
+
+    REQUIRE(isAligned(resources.allocSlot().operator VariantSlot*()));
+    REQUIRE(isAligned(resources.allocSlot().operator VariantSlot*()));
+  }
+
+  SECTION("Returns null if pool list allocation fails") {
+    ResourceManager resources(FailingAllocator::instance());
+
+    auto variant = resources.allocSlot();
+    REQUIRE(variant.id() == NULL_SLOT);
+    REQUIRE(static_cast<VariantSlot*>(variant) == nullptr);
+  }
+
+  SECTION("Returns null if pool allocation fails") {
+    ResourceManager resources(FailingAllocator::instance());
+
+    resources.allocSlot();
+
+    auto variant = resources.allocSlot();
+    REQUIRE(variant.id() == NULL_SLOT);
+    REQUIRE(static_cast<VariantSlot*>(variant) == nullptr);
+  }
+
+  SECTION("Try overflow pool counter") {
+    ResourceManager resources;
+
+    // this test assumes SlotId is 8-bit; otherwise it consumes a lot of memory
+    // tyhe GitHub Workflow gets killed
+    REQUIRE(NULL_SLOT == 255);
+
+    // fill all the pools
+    for (SlotId i = 0; i < NULL_SLOT; i++) {
+      auto slot = resources.allocSlot();
+      REQUIRE(slot.id() == i);  // or != NULL_SLOT
+      REQUIRE(static_cast<VariantSlot*>(slot) != nullptr);
+    }
+
+    REQUIRE(resources.overflowed() == false);
+
+    // now all allocations should fail
+    for (int i = 0; i < 10; i++) {
+      auto slot = resources.allocSlot();
+      REQUIRE(slot.id() == NULL_SLOT);
+      REQUIRE(static_cast<VariantSlot*>(slot) == nullptr);
+    }
+
+    REQUIRE(resources.overflowed() == true);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/clear.cpp b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/clear.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..30afd905904fdaa19548ad91c8e5fab1c1d9bb28
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/clear.cpp
@@ -0,0 +1,30 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson/Memory/ResourceManager.hpp>
+#include <ArduinoJson/Memory/VariantPoolImpl.hpp>
+#include <ArduinoJson/Strings/StringAdapters.hpp>
+#include <catch.hpp>
+
+using namespace ArduinoJson::detail;
+
+TEST_CASE("ResourceManager::clear()") {
+  ResourceManager resources;
+
+  SECTION("Discards allocated variants") {
+    resources.allocSlot();
+
+    resources.clear();
+    REQUIRE(resources.size() == 0);
+  }
+
+  SECTION("Discards allocated strings") {
+    resources.saveString(adaptString("123456789"));
+    REQUIRE(resources.size() == sizeofString(9));
+
+    resources.clear();
+
+    REQUIRE(resources.size() == 0);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/saveString.cpp b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/saveString.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f19ac1979513ebd99a4ad9d106dd5ba62f7e2fbe
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/saveString.cpp
@@ -0,0 +1,71 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson/Memory/ResourceManager.hpp>
+#include <ArduinoJson/Memory/VariantPoolImpl.hpp>
+#include <ArduinoJson/Strings/StringAdapters.hpp>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+
+using namespace ArduinoJson::detail;
+
+static StringNode* saveString(ResourceManager& resources, const char* s) {
+  return resources.saveString(adaptString(s));
+}
+
+static StringNode* saveString(ResourceManager& resources, const char* s,
+                              size_t n) {
+  return resources.saveString(adaptString(s, n));
+}
+
+TEST_CASE("ResourceManager::saveString()") {
+  ResourceManager resources;
+
+  SECTION("Duplicates different strings") {
+    auto a = saveString(resources, "hello");
+    auto b = saveString(resources, "world");
+    REQUIRE(+a->data != +b->data);
+    REQUIRE(a->length == 5);
+    REQUIRE(b->length == 5);
+    REQUIRE(a->references == 1);
+    REQUIRE(b->references == 1);
+    REQUIRE(resources.size() == sizeofString("hello") + sizeofString("world"));
+  }
+
+  SECTION("Deduplicates identical strings") {
+    auto a = saveString(resources, "hello");
+    auto b = saveString(resources, "hello");
+    REQUIRE(a == b);
+    REQUIRE(a->length == 5);
+    REQUIRE(a->references == 2);
+    REQUIRE(resources.size() == sizeofString("hello"));
+  }
+
+  SECTION("Deduplicates identical strings that contain NUL") {
+    auto a = saveString(resources, "hello\0world", 11);
+    auto b = saveString(resources, "hello\0world", 11);
+    REQUIRE(a == b);
+    REQUIRE(a->length == 11);
+    REQUIRE(a->references == 2);
+    REQUIRE(resources.size() == sizeofString("hello world"));
+  }
+
+  SECTION("Don't stop on first NUL") {
+    auto a = saveString(resources, "hello");
+    auto b = saveString(resources, "hello\0world", 11);
+    REQUIRE(a != b);
+    REQUIRE(a->length == 5);
+    REQUIRE(b->length == 11);
+    REQUIRE(a->references == 1);
+    REQUIRE(b->references == 1);
+    REQUIRE(resources.size() ==
+            sizeofString("hello") + sizeofString("hello world"));
+  }
+
+  SECTION("Returns NULL when allocation fails") {
+    ResourceManager pool2(FailingAllocator::instance());
+    REQUIRE(saveString(pool2, "a") == nullptr);
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/shrinkToFit.cpp b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/shrinkToFit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..74197703594f86dc799a96b41416e437da5149a4
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/shrinkToFit.cpp
@@ -0,0 +1,57 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson/Memory/ResourceManager.hpp>
+#include <ArduinoJson/Memory/VariantPoolImpl.hpp>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+
+using namespace ArduinoJson::detail;
+
+TEST_CASE("ResourceManager::shrinkToFit()") {
+  SpyingAllocator spyingAllocator;
+  ResourceManager resources(&spyingAllocator);
+
+  SECTION("empty") {
+    resources.shrinkToFit();
+
+    REQUIRE(spyingAllocator.log() == AllocatorLog{});
+  }
+
+  SECTION("only one pool") {
+    resources.allocSlot();
+
+    resources.shrinkToFit();
+
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()),
+                Reallocate(sizeofPool(), sizeof(VariantSlot)),
+            });
+  }
+
+  SECTION("more pools than initial count") {
+    for (size_t i = 0;
+         i < ARDUINOJSON_POOL_CAPACITY * ARDUINOJSON_INITIAL_POOL_COUNT + 1;
+         i++)
+      resources.allocSlot();
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()) * ARDUINOJSON_INITIAL_POOL_COUNT,
+                Allocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2)),
+                Allocate(sizeofPool()),
+            });
+
+    spyingAllocator.clearLog();
+    resources.shrinkToFit();
+
+    REQUIRE(spyingAllocator.log() ==
+            AllocatorLog{
+                Reallocate(sizeofPool(), sizeof(VariantSlot)),
+                Reallocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2),
+                           sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT + 1)),
+            });
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/size.cpp b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/size.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6b09b17ef3335970e933a9d2ef44cb551b78f012
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/size.cpp
@@ -0,0 +1,31 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson/Memory/ResourceManager.hpp>
+#include <ArduinoJson/Memory/VariantPoolImpl.hpp>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+
+using namespace ArduinoJson::detail;
+
+TEST_CASE("ResourceManager::size()") {
+  TimebombAllocator timebomb(0);
+  ResourceManager resources(&timebomb);
+
+  SECTION("Initial size is 0") {
+    REQUIRE(0 == resources.size());
+  }
+
+  SECTION("Doesn't grow when allocation of second pool fails") {
+    timebomb.setCountdown(1);
+    for (size_t i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
+      resources.allocSlot();
+    size_t size = resources.size();
+
+    resources.allocSlot();
+
+    REQUIRE(size == resources.size());
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/swap.cpp b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/swap.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4fdb45cb5c8a385761f662a0d95f7d8d3c4c09ef
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/extras/tests/ResourceManager/swap.cpp
@@ -0,0 +1,96 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson/Memory/Alignment.hpp>
+#include <ArduinoJson/Memory/ResourceManager.hpp>
+#include <ArduinoJson/Memory/VariantPoolImpl.hpp>
+#include <catch.hpp>
+
+#include "Allocators.hpp"
+
+using namespace ArduinoJson::detail;
+
+static void fullPreallocatedPools(ResourceManager& resources) {
+  for (int i = 0;
+       i < ARDUINOJSON_INITIAL_POOL_COUNT * ARDUINOJSON_POOL_CAPACITY; i++)
+    resources.allocSlot();
+}
+
+TEST_CASE("ResourceManager::swap()") {
+  SECTION("Both using preallocated pool list") {
+    SpyingAllocator spy;
+    ResourceManager a(&spy);
+    ResourceManager b(&spy);
+
+    auto a1 = a.allocSlot();
+    auto b1 = b.allocSlot();
+
+    swap(a, b);
+
+    REQUIRE(a1->data() == b.getSlot(a1.id())->data());
+    REQUIRE(b1->data() == a.getSlot(b1.id())->data());
+
+    REQUIRE(spy.log() == AllocatorLog{
+                             Allocate(sizeofPool()) * 2,
+                         });
+  }
+
+  SECTION("Only left using preallocated pool list") {
+    SpyingAllocator spy;
+    ResourceManager a(&spy);
+    ResourceManager b(&spy);
+    fullPreallocatedPools(b);
+
+    auto a1 = a.allocSlot();
+    auto b1 = b.allocSlot();
+    swap(a, b);
+
+    REQUIRE(a1->data() == b.getSlot(a1.id())->data());
+    REQUIRE(b1->data() == a.getSlot(b1.id())->data());
+
+    REQUIRE(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()) * (ARDUINOJSON_INITIAL_POOL_COUNT + 1),
+                Allocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2)),
+                Allocate(sizeofPool()),
+            });
+  }
+
+  SECTION("Only right using preallocated pool list") {
+    SpyingAllocator spy;
+    ResourceManager a(&spy);
+    fullPreallocatedPools(a);
+    ResourceManager b(&spy);
+
+    auto a1 = a.allocSlot();
+    auto b1 = b.allocSlot();
+    swap(a, b);
+
+    REQUIRE(a1->data() == b.getSlot(a1.id())->data());
+    REQUIRE(b1->data() == a.getSlot(b1.id())->data());
+
+    REQUIRE(spy.log() ==
+            AllocatorLog{
+                Allocate(sizeofPool()) * ARDUINOJSON_INITIAL_POOL_COUNT,
+                Allocate(sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2)),
+                Allocate(sizeofPool()) * 2,
+            });
+  }
+
+  SECTION("None is using preallocated pool list") {
+    SpyingAllocator spy;
+    ResourceManager a(&spy);
+    fullPreallocatedPools(a);
+    ResourceManager b(&spy);
+    fullPreallocatedPools(b);
+
+    auto a1 = a.allocSlot();
+    auto b1 = b.allocSlot();
+
+    swap(a, b);
+
+    REQUIRE(a1->data() == b.getSlot(a1.id())->data());
+    REQUIRE(b1->data() == a.getSlot(b1.id())->data());
+  }
+}
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt
index 2bc4a899d92775fc259939af8d152ab9921ba5fd..3b3b5bb82444cd1e1485be99b3ce2fc4449828b5 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 add_executable(TextFormatterTests
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp
index 8e4bd7e78367e71bda61fc726c9fed9ea51258f8..a78771240a3f2e3e57156cd7ff6618f0fd05b296 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <catch.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp
index 20cbd29faad3ffeac4779403c21859647c418a36..1906750a0f1e237f17c3ded4a3cf42be668686f1 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <catch.hpp>
diff --git a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeString.cpp b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeString.cpp
index 2be9191cf9b1e016b0a0728253fd2ddd3d6e7e58..2d3d096ce391e02cd16936a6ca25df8f43c9f924 100644
--- a/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeString.cpp
+++ b/neosensor/libraries/ArduinoJson/extras/tests/TextFormatter/writeString.cpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #include <catch.hpp>
diff --git a/neosensor/libraries/ArduinoJson/idf_component.yml b/neosensor/libraries/ArduinoJson/idf_component.yml
index afc8b8a861b181512ab2e250072b9efd9ad69570..2f504e4a19a7fd1a45df01a8b4eadfaafb818f69 100644
--- a/neosensor/libraries/ArduinoJson/idf_component.yml
+++ b/neosensor/libraries/ArduinoJson/idf_component.yml
@@ -1,9 +1,9 @@
-version: "6.21.5"
+version: "7.1.0"
 description: >-
   A simple and efficient JSON library for embedded C++.
-  ArduinoJson supports ✔ serialization, ✔ deserialization, ✔ MessagePack, ✔ fixed allocation, ✔ zero-copy, ✔ streams, ✔ filtering, and more.
-  It is the most popular Arduino library on GitHub ❤❤❤❤❤.
-  Check out arduinojson.org for a comprehensive documentation.
+  ⭐ 6624 stars on GitHub!
+  Supports serialization, deserialization, MessagePack, streams, filtering, and more.
+  Fully tested and documented.
 url: https://arduinojson.org/
 files:
   exclude:
diff --git a/neosensor/libraries/ArduinoJson/keywords.txt b/neosensor/libraries/ArduinoJson/keywords.txt
index 3dff06bfcd4cbf36204b78e3d8fe5f06b44a2c70..05d113d4c9ab7839f64faccc46491f4d2327453c 100644
--- a/neosensor/libraries/ArduinoJson/keywords.txt
+++ b/neosensor/libraries/ArduinoJson/keywords.txt
@@ -1,8 +1,3 @@
-# Macros
-JSON_ARRAY_SIZE	KEYWORD2
-JSON_OBJECT_SIZE	KEYWORD2
-JSON_STRING_SIZE	KEYWORD2
-
 # Free functions
 deserializeJson	KEYWORD2
 deserializeMsgPack	KEYWORD2
@@ -17,15 +12,13 @@ measureMsgPack	KEYWORD2
 # Methods
 add	KEYWORD2
 as	KEYWORD2
-createNestedArray	KEYWORD2
-createNestedObject	KEYWORD2
 get	KEYWORD2
 set	KEYWORD2
 to	KEYWORD2
 
 # Type names
 DeserializationError	KEYWORD1	DATA_TYPE
-DynamicJsonDocument	KEYWORD1	DATA_TYPE
+JsonDocument	KEYWORD1	DATA_TYPE
 JsonArray	KEYWORD1	DATA_TYPE
 JsonArrayConst	KEYWORD1	DATA_TYPE
 JsonDocument	KEYWORD1	DATA_TYPE
@@ -37,4 +30,3 @@ JsonString	KEYWORD1	DATA_TYPE
 JsonUInt	KEYWORD1	DATA_TYPE
 JsonVariant	KEYWORD1	DATA_TYPE
 JsonVariantConst	KEYWORD1	DATA_TYPE
-StaticJsonDocument	KEYWORD1	DATA_TYPE
diff --git a/neosensor/libraries/ArduinoJson/library.json b/neosensor/libraries/ArduinoJson/library.json
index 0303e99d8da2aed8286d8a434815aeae0d67ce6e..e1c712a2b303960cc0ab778819a465adabef4184 100644
--- a/neosensor/libraries/ArduinoJson/library.json
+++ b/neosensor/libraries/ArduinoJson/library.json
@@ -1,13 +1,13 @@
 {
   "name": "ArduinoJson",
   "keywords": "json, rest, http, web",
-  "description": "A simple and efficient JSON library for embedded C++. ArduinoJson supports ✔ serialization, ✔ deserialization, ✔ MessagePack, ✔ fixed allocation, ✔ zero-copy, ✔ streams, ✔ filtering, and more. It is the most popular Arduino library on GitHub ❤❤❤❤❤. Check out arduinojson.org for a comprehensive documentation.",
+  "description": "A simple and efficient JSON library for embedded C++. ⭐ 6624 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.",
   "homepage": "https://arduinojson.org/?utm_source=meta&utm_medium=library.json",
   "repository": {
     "type": "git",
     "url": "https://github.com/bblanchon/ArduinoJson.git"
   },
-  "version": "6.21.5",
+  "version": "7.1.0",
   "authors": {
     "name": "Benoit Blanchon",
     "url": "https://blog.benoitblanchon.fr"
diff --git a/neosensor/libraries/ArduinoJson/library.properties b/neosensor/libraries/ArduinoJson/library.properties
index aa6de9a9855fa25c770dd18f9dd5891517963883..9a593081fa2a253b2d320be224a4b108a194baca 100644
--- a/neosensor/libraries/ArduinoJson/library.properties
+++ b/neosensor/libraries/ArduinoJson/library.properties
@@ -1,9 +1,9 @@
 name=ArduinoJson
-version=6.21.5
+version=7.1.0
 author=Benoit Blanchon <blog.benoitblanchon.fr>
 maintainer=Benoit Blanchon <blog.benoitblanchon.fr>
 sentence=A simple and efficient JSON library for embedded C++.
-paragraph=ArduinoJson supports ✔ serialization, ✔ deserialization, ✔ MessagePack, ✔ fixed allocation, ✔ zero-copy, ✔ streams, ✔ filtering, and more. It is the most popular Arduino library on GitHub ❤❤❤❤❤. Check out arduinojson.org for a comprehensive documentation.
+paragraph=⭐ 6624 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.
 category=Data Processing
 url=https://arduinojson.org/?utm_source=meta&utm_medium=library.properties
 architectures=*
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson.h b/neosensor/libraries/ArduinoJson/src/ArduinoJson.h
index c9ac0ca64285f408533114bdb3c7e1bdb3e2312f..fec64c3d1098c95123b08bc3c4c5075f841c1a51 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson.h
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson.h
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson.hpp
index 2d1b0be13374bf6468c3a8015587045b528bd741..085507a01248e2f6b349343274a8ed4ba9ce0412 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -30,23 +30,27 @@
 #include "ArduinoJson/Object/JsonObject.hpp"
 #include "ArduinoJson/Variant/JsonVariantConst.hpp"
 
-#include "ArduinoJson/Document/DynamicJsonDocument.hpp"
-#include "ArduinoJson/Document/StaticJsonDocument.hpp"
+#include "ArduinoJson/Document/JsonDocument.hpp"
 
+#include "ArduinoJson/Array/ArrayImpl.hpp"
 #include "ArduinoJson/Array/ElementProxy.hpp"
-#include "ArduinoJson/Array/JsonArrayImpl.hpp"
 #include "ArduinoJson/Array/Utilities.hpp"
 #include "ArduinoJson/Collection/CollectionImpl.hpp"
-#include "ArduinoJson/Object/JsonObjectImpl.hpp"
+#include "ArduinoJson/Memory/ResourceManagerImpl.hpp"
+#include "ArduinoJson/Memory/VariantPoolImpl.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"
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ArrayData.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ArrayData.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7e15ab19deb65b92450d874c1bac20a7d6c1baec
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ArrayData.hpp
@@ -0,0 +1,68 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Collection/CollectionData.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+class ArrayData : public CollectionData {
+ public:
+  VariantData* addElement(ResourceManager* resources) {
+    return addSlot(resources).data();
+  }
+
+  static VariantData* addElement(ArrayData* array, ResourceManager* resources) {
+    if (!array)
+      return nullptr;
+    return array->addElement(resources);
+  }
+
+  template <typename T>
+  bool addValue(T&& value, ResourceManager* resources);
+
+  template <typename T>
+  static bool addValue(ArrayData* array, 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);
+  }
+
+  bool copyFrom(const ArrayData& src, ResourceManager* resources);
+
+  static bool copy(ArrayData* dst, const ArrayData* src,
+                   ResourceManager* resources) {
+    if (!dst || !src)
+      return false;
+
+    return dst->copyFrom(*src, resources);
+  }
+
+ private:
+  iterator at(size_t index, const ResourceManager* resources) const;
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9b96138a56e99a3f2231413900f87ae7163e5df5
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp
@@ -0,0 +1,65 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Array/ArrayData.hpp>
+#include <ArduinoJson/Variant/VariantCompare.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::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(T&& value, ResourceManager* resources) {
+  ARDUINOJSON_ASSERT(resources != nullptr);
+  auto slot = resources->allocSlot();
+  if (!slot)
+    return false;
+  JsonVariant variant(slot->data(), resources);
+  if (!variant.set(detail::forward<T>(value))) {
+    resources->freeSlot(slot);
+    return false;
+  }
+  addSlot(slot, resources);
+  return true;
+}
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp
index d6e9aa5d10a58ad1f908a4672810bde3e6aab400..f36b68ea395cb6d272fa8bf7cd67dc46b1485eb9 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,7 +9,7 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 // A proxy class to get or set an element of an array.
-// https://arduinojson.org/v6/api/jsonarray/subscript/
+// https://arduinojson.org/v7/api/jsonarray/subscript/
 template <typename TUpstream>
 class ElementProxy : public VariantRefBase<ElementProxy<TUpstream>>,
                      public VariantOperators<ElementProxy<TUpstream>> {
@@ -22,35 +22,40 @@ class ElementProxy : public VariantRefBase<ElementProxy<TUpstream>>,
   ElementProxy(const ElementProxy& src)
       : upstream_(src.upstream_), index_(src.index_) {}
 
-  FORCE_INLINE ElementProxy& operator=(const ElementProxy& src) {
+  ElementProxy& operator=(const ElementProxy& src) {
     this->set(src);
     return *this;
   }
 
   template <typename T>
-  FORCE_INLINE ElementProxy& operator=(const T& src) {
+  ElementProxy& operator=(const T& src) {
     this->set(src);
     return *this;
   }
 
   template <typename T>
-  FORCE_INLINE ElementProxy& operator=(T* src) {
+  ElementProxy& operator=(T* src) {
     this->set(src);
     return *this;
   }
 
  private:
-  FORCE_INLINE MemoryPool* getPool() const {
-    return VariantAttorney::getPool(upstream_);
+  ResourceManager* getResourceManager() const {
+    return VariantAttorney::getResourceManager(upstream_);
   }
 
   FORCE_INLINE VariantData* getData() const {
-    return variantGetElement(VariantAttorney::getData(upstream_), index_);
+    return VariantData::getElement(
+        VariantAttorney::getData(upstream_), index_,
+        VariantAttorney::getResourceManager(upstream_));
   }
 
-  FORCE_INLINE VariantData* getOrCreateData() const {
-    return variantGetOrAddElement(VariantAttorney::getOrCreateData(upstream_),
-                                  index_, VariantAttorney::getPool(upstream_));
+  VariantData* getOrCreateData() const {
+    auto data = VariantAttorney::getOrCreateData(upstream_);
+    if (!data)
+      return nullptr;
+    return data->getOrAddElement(
+        index_, VariantAttorney::getResourceManager(upstream_));
   }
 
   TUpstream upstream_;
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArray.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArray.hpp
index d208e417542115779fcaf40a04fdf72a7ec5d17e..9a4e5f97084573e6fbee957e3d939214ff555319 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArray.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArray.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -12,7 +12,7 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 class JsonObject;
 
 // A reference to an array in a JsonDocument
-// https://arduinojson.org/v6/api/jsonarray/
+// https://arduinojson.org/v7/api/jsonarray/
 class JsonArray : public detail::VariantOperators<JsonArray> {
   friend class detail::VariantAttorney;
 
@@ -20,192 +20,199 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
   typedef JsonArrayIterator iterator;
 
   // Constructs an unbound reference.
-  FORCE_INLINE JsonArray() : data_(0), pool_(0) {}
+  JsonArray() : data_(0), resources_(0) {}
 
   // INTERNAL USE ONLY
-  FORCE_INLINE JsonArray(detail::MemoryPool* pool, detail::CollectionData* data)
-      : data_(data), pool_(pool) {}
+  JsonArray(detail::ArrayData* data, detail::ResourceManager* resources)
+      : data_(data), resources_(resources) {}
 
   // Returns a JsonVariant pointing to the array.
-  // https://arduinojson.org/v6/api/jsonvariant/
+  // https://arduinojson.org/v7/api/jsonvariant/
   operator JsonVariant() {
     void* data = data_;  // prevent warning cast-align
-    return JsonVariant(pool_, reinterpret_cast<detail::VariantData*>(data));
+    return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
+                       resources_);
   }
 
   // Returns a read-only reference to the array.
-  // https://arduinojson.org/v6/api/jsonarrayconst/
+  // https://arduinojson.org/v7/api/jsonarrayconst/
   operator JsonArrayConst() const {
-    return JsonArrayConst(data_);
+    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, 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/v6/api/jsonarray/add/
-  JsonVariant add() const {
-    if (!data_)
-      return JsonVariant();
-    return JsonVariant(pool_, data_->addElement(pool_));
+  // https://arduinojson.org/v7/api/jsonarray/add/
+  template <typename T>
+  detail::enable_if_t<detail::is_same<T, JsonVariant>::value, T> add() const {
+    return JsonVariant(detail::ArrayData::addElement(data_, resources_),
+                       resources_);
   }
 
   // Appends a value to the array.
-  // https://arduinojson.org/v6/api/jsonarray/add/
+  // https://arduinojson.org/v7/api/jsonarray/add/
   template <typename T>
-  FORCE_INLINE bool add(const T& value) const {
-    return add().set(value);
+  bool add(const T& value) const {
+    return detail::ArrayData::addValue(data_, value, resources_);
   }
 
   // Appends a value to the array.
-  // https://arduinojson.org/v6/api/jsonarray/add/
+  // https://arduinojson.org/v7/api/jsonarray/add/
   template <typename T>
-  FORCE_INLINE bool add(T* value) const {
-    return add().set(value);
+  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/v6/api/jsonarray/begin/
-  FORCE_INLINE iterator begin() const {
+  // https://arduinojson.org/v7/api/jsonarray/begin/
+  iterator begin() const {
     if (!data_)
       return iterator();
-    return iterator(pool_, data_->head());
+    return iterator(data_->createIterator(resources_), resources_);
   }
 
   // Returns an iterator following the last element of the array.
-  // https://arduinojson.org/v6/api/jsonarray/end/
-  FORCE_INLINE iterator end() const {
+  // https://arduinojson.org/v7/api/jsonarray/end/
+  iterator end() const {
     return iterator();
   }
 
   // Copies an array.
-  // https://arduinojson.org/v6/api/jsonarray/set/
-  FORCE_INLINE bool set(JsonArrayConst src) const {
-    if (!data_ || !src.data_)
+  // https://arduinojson.org/v7/api/jsonarray/set/
+  bool set(JsonArrayConst src) const {
+    if (!data_)
       return false;
-    return data_->copyFrom(*src.data_, pool_);
-  }
 
-  // Compares the content of two arrays.
-  FORCE_INLINE bool operator==(JsonArray rhs) const {
-    return JsonArrayConst(data_) == JsonArrayConst(rhs.data_);
+    clear();
+    for (auto element : src) {
+      if (!add(element))
+        return false;
+    }
+
+    return true;
   }
 
   // Removes the element at the specified iterator.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsonarray/remove/
-  FORCE_INLINE void remove(iterator it) const {
-    if (!data_)
-      return;
-    data_->removeSlot(it.slot_);
+  // 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.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsonarray/remove/
-  FORCE_INLINE void remove(size_t index) const {
-    if (!data_)
-      return;
-    data_->removeElement(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> remove(
+      TVariant variant) const {
+    if (variant.template is<size_t>())
+      remove(variant.template as<size_t>());
   }
 
   // Removes all the elements of the array.
-  // ⚠️ Doesn't release the memory associated with the removed elements.
-  // https://arduinojson.org/v6/api/jsonarray/clear/
+  // https://arduinojson.org/v7/api/jsonarray/clear/
   void clear() const {
-    if (!data_)
-      return;
-    data_->clear();
+    detail::ArrayData::clear(data_, resources_);
   }
 
   // Gets or sets the element at the specified index.
-  // https://arduinojson.org/v6/api/jsonarray/subscript/
-  FORCE_INLINE detail::ElementProxy<JsonArray> operator[](size_t index) const {
-    return {*this, index};
+  // https://arduinojson.org/v7/api/jsonarray/subscript/
+  template <typename T>
+  detail::enable_if_t<detail::is_integral<T>::value,
+                      detail::ElementProxy<JsonArray>>
+  operator[](T index) const {
+    return {*this, size_t(index)};
   }
 
-  // Creates an object and appends it to the array.
-  // https://arduinojson.org/v6/api/jsonarray/createnestedobject/
-  FORCE_INLINE JsonObject createNestedObject() const;
-
-  // Creates an array and appends it to the array.
-  // https://arduinojson.org/v6/api/jsonarray/createnestedarray/
-  FORCE_INLINE JsonArray createNestedArray() const {
-    return add().to<JsonArray>();
+  // 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,
+                      detail::ElementProxy<JsonArray>>
+  operator[](const TVariant& variant) const {
+    if (variant.template is<size_t>())
+      return operator[](variant.template as<size_t>());
+    else
+      return {*this, size_t(-1)};
   }
 
   operator JsonVariantConst() const {
-    return JsonVariantConst(collectionToVariant(data_));
+    return JsonVariantConst(collectionToVariant(data_), resources_);
   }
 
   // Returns true if the reference is unbound.
-  // https://arduinojson.org/v6/api/jsonarray/isnull/
-  FORCE_INLINE bool isNull() const {
+  // https://arduinojson.org/v7/api/jsonarray/isnull/
+  bool isNull() const {
     return data_ == 0;
   }
 
   // Returns true if the reference is bound.
-  // https://arduinojson.org/v6/api/jsonarray/isnull/
-  FORCE_INLINE operator bool() const {
+  // https://arduinojson.org/v7/api/jsonarray/isnull/
+  operator bool() const {
     return data_ != 0;
   }
 
-  // Returns the number of bytes occupied by the array.
-  // https://arduinojson.org/v6/api/jsonarray/memoryusage/
-  FORCE_INLINE size_t memoryUsage() const {
-    return data_ ? data_->memoryUsage() : 0;
-  }
-
   // Returns the depth (nesting level) of the array.
-  // https://arduinojson.org/v6/api/jsonarray/nesting/
-  FORCE_INLINE size_t nesting() const {
-    return variantNesting(collectionToVariant(data_));
+  // 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/v6/api/jsonarray/size/
-  FORCE_INLINE size_t size() const {
-    return data_ ? data_->size() : 0;
-  }
-
- private:
-  detail::MemoryPool* getPool() const {
-    return pool_;
+  // https://arduinojson.org/v7/api/jsonarray/size/
+  size_t size() const {
+    return data_ ? data_->size(resources_) : 0;
   }
 
-  detail::VariantData* getData() const {
-    return collectionToVariant(data_);
+  // DEPRECATED: use add<JsonVariant>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonVariant>() instead")
+  JsonVariant add() const {
+    return add<JsonVariant>();
   }
 
-  detail::VariantData* getOrCreateData() const {
-    return collectionToVariant(data_);
+  // DEPRECATED: use add<JsonArray>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonArray>() instead")
+  JsonArray createNestedArray() const {
+    return add<JsonArray>();
   }
 
-  detail::CollectionData* data_;
-  detail::MemoryPool* pool_;
-};
+  // DEPRECATED: use add<JsonObject>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonObject>() instead")
+  JsonObject createNestedObject() const;
 
-template <>
-struct Converter<JsonArray> : private detail::VariantAttorney {
-  static void toJson(JsonVariantConst src, JsonVariant dst) {
-    variantCopyFrom(getData(dst), getData(src), getPool(dst));
+  // DEPRECATED: always returns zero
+  ARDUINOJSON_DEPRECATED("always returns zero")
+  size_t memoryUsage() const {
+    return 0;
   }
 
-  static JsonArray fromJson(JsonVariant src) {
-    auto data = getData(src);
-    auto pool = getPool(src);
-    return JsonArray(pool, data != 0 ? data->asArray() : 0);
+ private:
+  detail::ResourceManager* getResourceManager() const {
+    return resources_;
   }
 
-  static detail::InvalidConversion<JsonVariantConst, JsonArray> fromJson(
-      JsonVariantConst);
-
-  static bool checkJson(JsonVariantConst) {
-    return false;
+  detail::VariantData* getData() const {
+    return collectionToVariant(data_);
   }
 
-  static bool checkJson(JsonVariant src) {
-    auto data = getData(src);
-    return data && data->isArray();
+  detail::VariantData* getOrCreateData() const {
+    return collectionToVariant(data_);
   }
+
+  detail::ArrayData* data_;
+  detail::ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayConst.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayConst.hpp
index 6a6463c8cd8db99ee7ad5412c0eac0be86a7615d..c6e027f8faa85181e7cbbe03881c470b14bf8f86 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayConst.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayConst.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -13,7 +13,7 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 class JsonObject;
 
 // A read-only reference to an array in a JsonDocument
-// https://arduinojson.org/v6/api/jsonarrayconst/
+// https://arduinojson.org/v7/api/jsonarrayconst/
 class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
   friend class JsonArray;
   friend class detail::VariantAttorney;
@@ -22,89 +22,80 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
   typedef JsonArrayConstIterator iterator;
 
   // Returns an iterator to the first element of the array.
-  // https://arduinojson.org/v6/api/jsonarrayconst/begin/
-  FORCE_INLINE iterator begin() const {
+  // https://arduinojson.org/v7/api/jsonarrayconst/begin/
+  iterator begin() const {
     if (!data_)
       return iterator();
-    return iterator(data_->head());
+    return iterator(data_->createIterator(resources_), resources_);
   }
 
   // Returns an iterator to the element following the last element of the array.
-  // https://arduinojson.org/v6/api/jsonarrayconst/end/
-  FORCE_INLINE iterator end() const {
+  // https://arduinojson.org/v7/api/jsonarrayconst/end/
+  iterator end() const {
     return iterator();
   }
 
   // Creates an unbound reference.
-  FORCE_INLINE JsonArrayConst() : data_(0) {}
+  JsonArrayConst() : data_(0), resources_(0) {}
 
   // INTERNAL USE ONLY
-  FORCE_INLINE JsonArrayConst(const detail::CollectionData* data)
-      : data_(data) {}
+  JsonArrayConst(const detail::ArrayData* data,
+                 const detail::ResourceManager* resources)
+      : data_(data), resources_(resources) {}
 
-  // Compares the content of two arrays.
-  // Returns true if the two arrays are equal.
-  FORCE_INLINE bool operator==(JsonArrayConst rhs) const {
-    if (data_ == rhs.data_)
-      return true;
-    if (!data_ || !rhs.data_)
-      return false;
-
-    iterator it1 = begin();
-    iterator it2 = rhs.begin();
-
-    for (;;) {
-      bool end1 = it1 == end();
-      bool end2 = it2 == rhs.end();
-      if (end1 && end2)
-        return true;
-      if (end1 || end2)
-        return false;
-      if (*it1 != *it2)
-        return false;
-      ++it1;
-      ++it2;
-    }
+  // 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, 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/v6/api/jsonarrayconst/subscript/
-  FORCE_INLINE JsonVariantConst operator[](size_t index) const {
-    return JsonVariantConst(data_ ? data_->getElement(index) : 0);
+  // https://arduinojson.org/v7/api/jsonarrayconst/subscript/
+  template <typename TVariant>
+  detail::enable_if_t<detail::IsVariant<TVariant>::value, 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(collectionToVariant(data_));
+    return JsonVariantConst(getData(), resources_);
   }
 
   // Returns true if the reference is unbound.
-  // https://arduinojson.org/v6/api/jsonarrayconst/isnull/
-  FORCE_INLINE bool isNull() const {
+  // https://arduinojson.org/v7/api/jsonarrayconst/isnull/
+  bool isNull() const {
     return data_ == 0;
   }
 
   // Returns true if the reference is bound.
-  // https://arduinojson.org/v6/api/jsonarrayconst/isnull/
-  FORCE_INLINE operator bool() const {
+  // https://arduinojson.org/v7/api/jsonarrayconst/isnull/
+  operator bool() const {
     return data_ != 0;
   }
 
-  // Returns the number of bytes occupied by the array.
-  // https://arduinojson.org/v6/api/jsonarrayconst/memoryusage/
-  FORCE_INLINE size_t memoryUsage() const {
-    return data_ ? data_->memoryUsage() : 0;
-  }
-
   // Returns the depth (nesting level) of the array.
-  // https://arduinojson.org/v6/api/jsonarrayconst/nesting/
-  FORCE_INLINE size_t nesting() const {
-    return variantNesting(collectionToVariant(data_));
+  // 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/v6/api/jsonarrayconst/size/
-  FORCE_INLINE size_t size() const {
-    return data_ ? data_->size() : 0;
+  // 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:
@@ -112,24 +103,31 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
     return collectionToVariant(data_);
   }
 
-  const detail::CollectionData* data_;
+  const detail::ArrayData* data_;
+  const detail::ResourceManager* resources_;
 };
 
-template <>
-struct Converter<JsonArrayConst> : private detail::VariantAttorney {
-  static void toJson(JsonVariantConst src, JsonVariant dst) {
-    variantCopyFrom(getData(dst), getData(src), getPool(dst));
-  }
+// 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;
 
-  static JsonArrayConst fromJson(JsonVariantConst src) {
-    auto data = getData(src);
-    return data ? data->asArray() : 0;
-  }
+  auto a = lhs.begin();
+  auto b = rhs.begin();
 
-  static bool checkJson(JsonVariantConst src) {
-    auto data = getData(src);
-    return data && data->isArray();
+  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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayImpl.hpp
deleted file mode 100644
index dc0487af8c95c7365e952352bdcfc658f6a3481b..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayImpl.hpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Array/JsonArray.hpp>
-#include <ArduinoJson/Object/JsonObject.hpp>
-
-ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
-
-inline JsonObject JsonArray::createNestedObject() const {
-  return add().to<JsonObject>();
-}
-
-ARDUINOJSON_END_PUBLIC_NAMESPACE
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-template <typename TDerived>
-inline JsonArray VariantRefBase<TDerived>::createNestedArray() const {
-  return add().template to<JsonArray>();
-}
-
-template <typename TDerived>
-inline JsonObject VariantRefBase<TDerived>::createNestedObject() const {
-  return add().template to<JsonObject>();
-}
-
-template <typename TDerived>
-inline ElementProxy<TDerived> VariantRefBase<TDerived>::operator[](
-    size_t index) const {
-  return ElementProxy<TDerived>(derived(), index);
-}
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayIterator.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayIterator.hpp
index d9048b28c7e976795795b47875e085d09825f357..70bb28f4a1acd8ba2e5f91879d6067f963aa3c1b 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayIterator.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/JsonArrayIterator.hpp
@@ -1,121 +1,96 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Variant/JsonVariant.hpp>
-#include <ArduinoJson/Variant/SlotFunctions.hpp>
 
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
-class VariantPtr {
+template <typename T>
+class Ptr {
  public:
-  VariantPtr(detail::MemoryPool* pool, detail::VariantData* data)
-      : variant_(pool, data) {}
+  Ptr(T value) : value_(value) {}
 
-  JsonVariant* operator->() {
-    return &variant_;
+  T* operator->() {
+    return &value_;
   }
 
-  JsonVariant& operator*() {
-    return variant_;
+  T& operator*() {
+    return value_;
   }
 
  private:
-  JsonVariant variant_;
+  T value_;
 };
 
 class JsonArrayIterator {
   friend class JsonArray;
 
  public:
-  JsonArrayIterator() : slot_(0) {}
-  explicit JsonArrayIterator(detail::MemoryPool* pool,
-                             detail::VariantSlot* slot)
-      : pool_(pool), slot_(slot) {}
+  JsonArrayIterator() {}
+  explicit JsonArrayIterator(detail::ArrayData::iterator iterator,
+                             detail::ResourceManager* resources)
+      : iterator_(iterator), resources_(resources) {}
 
-  JsonVariant operator*() const {
-    return JsonVariant(pool_, slot_->data());
+  JsonVariant operator*() {
+    return JsonVariant(iterator_.data(), resources_);
   }
-  VariantPtr operator->() {
-    return VariantPtr(pool_, slot_->data());
+  Ptr<JsonVariant> operator->() {
+    return operator*();
   }
 
   bool operator==(const JsonArrayIterator& other) const {
-    return slot_ == other.slot_;
+    return iterator_ == other.iterator_;
   }
 
   bool operator!=(const JsonArrayIterator& other) const {
-    return slot_ != other.slot_;
+    return iterator_ != other.iterator_;
   }
 
   JsonArrayIterator& operator++() {
-    slot_ = slot_->next();
-    return *this;
-  }
-
-  JsonArrayIterator& operator+=(size_t distance) {
-    slot_ = slot_->next(distance);
+    iterator_.next(resources_);
     return *this;
   }
 
  private:
-  detail::MemoryPool* pool_;
-  detail::VariantSlot* slot_;
-};
-
-class VariantConstPtr {
- public:
-  VariantConstPtr(const detail::VariantData* data) : variant_(data) {}
-
-  JsonVariantConst* operator->() {
-    return &variant_;
-  }
-
-  JsonVariantConst& operator*() {
-    return variant_;
-  }
-
- private:
-  JsonVariantConst variant_;
+  detail::ArrayData::iterator iterator_;
+  detail::ResourceManager* resources_;
 };
 
 class JsonArrayConstIterator {
   friend class JsonArray;
 
  public:
-  JsonArrayConstIterator() : slot_(0) {}
-  explicit JsonArrayConstIterator(const detail::VariantSlot* slot)
-      : slot_(slot) {}
+  JsonArrayConstIterator() {}
+  explicit JsonArrayConstIterator(detail::ArrayData::iterator iterator,
+                                  const detail::ResourceManager* resources)
+      : iterator_(iterator), resources_(resources) {}
 
   JsonVariantConst operator*() const {
-    return JsonVariantConst(slot_->data());
+    return JsonVariantConst(iterator_.data(), resources_);
   }
-  VariantConstPtr operator->() {
-    return VariantConstPtr(slot_->data());
+  Ptr<JsonVariantConst> operator->() {
+    return operator*();
   }
 
   bool operator==(const JsonArrayConstIterator& other) const {
-    return slot_ == other.slot_;
+    return iterator_ == other.iterator_;
   }
 
   bool operator!=(const JsonArrayConstIterator& other) const {
-    return slot_ != other.slot_;
+    return iterator_ != other.iterator_;
   }
 
   JsonArrayConstIterator& operator++() {
-    slot_ = slot_->next();
-    return *this;
-  }
-
-  JsonArrayConstIterator& operator+=(size_t distance) {
-    slot_ = slot_->next(distance);
+    iterator_.next(resources_);
     return *this;
   }
 
  private:
-  const detail::VariantSlot* slot_;
+  detail::ArrayData::iterator iterator_;
+  const detail::ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp
index 398e11d456975904afc7a3ca94a036488cf4dc20..9073468c82e214df1c22eab872fd092bc1ea0d0e 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -12,29 +12,29 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 // Copies a value to a JsonVariant.
 // This is a degenerated form of copyArray() to stop the recursion.
 template <typename T>
-inline typename detail::enable_if<!detail::is_array<T>::value, bool>::type
-copyArray(const T& src, JsonVariant dst) {
+inline detail::enable_if_t<!detail::is_array<T>::value, 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/v6/api/misc/copyarray/
+// https://arduinojson.org/v7/api/misc/copyarray/
 template <typename T, size_t N, typename TDestination>
-inline typename detail::enable_if<
-    !detail::is_base_of<JsonDocument, TDestination>::value, bool>::type
+inline detail::enable_if_t<
+    !detail::is_base_of<JsonDocument, TDestination>::value, 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/v6/api/misc/copyarray/
+// https://arduinojson.org/v7/api/misc/copyarray/
 template <typename T, typename TDestination>
-inline typename detail::enable_if<
-    !detail::is_base_of<JsonDocument, TDestination>::value, bool>::type
+inline detail::enable_if_t<
+    !detail::is_base_of<JsonDocument, TDestination>::value, 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.add());
+    ok &= copyArray(src[i], dst.template add<JsonVariant>());
   }
   return ok;
 }
@@ -47,14 +47,14 @@ inline bool copyArray(const char* src, size_t, const TDestination& dst) {
 }
 
 // Copies values from an array to a JsonDocument.
-// https://arduinojson.org/v6/api/misc/copyarray/
+// 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/v6/api/misc/copyarray/
+// 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>());
@@ -63,21 +63,21 @@ inline bool copyArray(const T* src, size_t len, JsonDocument& dst) {
 // Copies a value from a JsonVariant.
 // This is a degenerated form of copyArray() to stop the recursion.
 template <typename T>
-inline typename detail::enable_if<!detail::is_array<T>::value, size_t>::type
-copyArray(JsonVariantConst src, T& dst) {
+inline detail::enable_if_t<!detail::is_array<T>::value, 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/v6/api/misc/copyarray/
+// 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/v6/api/misc/copyarray/
+// 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;
@@ -101,12 +101,11 @@ inline size_t copyArray(JsonVariantConst src, char (&dst)[N]) {
 }
 
 // Copies values from a JsonDocument to an array.
-// https://arduinojson.org/v6/api/misc/copyarray/
+// https://arduinojson.org/v7/api/misc/copyarray/
 template <typename TSource, typename T>
-inline typename detail::enable_if<
-    detail::is_array<T>::value &&
-        detail::is_base_of<JsonDocument, TSource>::value,
-    size_t>::type
+inline detail::enable_if_t<detail::is_array<T>::value &&
+                               detail::is_base_of<JsonDocument, TSource>::value,
+                           size_t>
 copyArray(const TSource& src, T& dst) {
   return copyArray(src.template as<JsonArrayConst>(), dst);
 }
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp
index 090c98b9e278103bb623199b3ca9e6614798fa68..07e698235c8a8d8c75c4632213387b3735e6c6d0 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -11,74 +11,113 @@
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
-class MemoryPool;
 class VariantData;
 class VariantSlot;
 
-class CollectionData {
-  VariantSlot* head_;
-  VariantSlot* tail_;
+class CollectionIterator {
+  friend class CollectionData;
 
  public:
-  // Must be a POD!
-  // - no constructor
-  // - no destructor
-  // - no virtual
-  // - no inheritance
+  CollectionIterator() : slot_(nullptr), currentId_(NULL_SLOT) {}
 
-  // Array only
+  void next(const ResourceManager* resources);
 
-  VariantData* addElement(MemoryPool* pool);
+  bool done() const {
+    return slot_ == nullptr;
+  }
 
-  VariantData* getElement(size_t index) const;
+  bool operator==(const CollectionIterator& other) const {
+    return slot_ == other.slot_;
+  }
 
-  VariantData* getOrAddElement(size_t index, MemoryPool* pool);
+  bool operator!=(const CollectionIterator& other) const {
+    return slot_ != other.slot_;
+  }
 
-  void removeElement(size_t index);
+  VariantData* operator->() {
+    ARDUINOJSON_ASSERT(slot_ != nullptr);
+    return data();
+  }
 
-  // Object only
+  VariantData& operator*() {
+    ARDUINOJSON_ASSERT(slot_ != nullptr);
+    return *data();
+  }
 
-  template <typename TAdaptedString>
-  VariantData* addMember(TAdaptedString key, MemoryPool* pool);
+  const VariantData& operator*() const {
+    ARDUINOJSON_ASSERT(slot_ != nullptr);
+    return *data();
+  }
 
-  template <typename TAdaptedString>
-  VariantData* getMember(TAdaptedString key) const;
+  const char* key() const;
+  bool ownsKey() const;
 
-  template <typename TAdaptedString>
-  VariantData* getOrAddMember(TAdaptedString key, MemoryPool* pool);
+  void setKey(StringNode*);
+  void setKey(const char*);
 
-  template <typename TAdaptedString>
-  void removeMember(TAdaptedString key) {
-    removeSlot(getSlot(key));
+  VariantData* data() {
+    return reinterpret_cast<VariantData*>(slot_);
   }
 
-  template <typename TAdaptedString>
-  bool containsKey(const TAdaptedString& key) const;
+  const VariantData* data() const {
+    return reinterpret_cast<const VariantData*>(slot_);
+  }
 
-  // Generic
+ private:
+  CollectionIterator(VariantSlot* slot, SlotId slotId);
 
-  void clear();
-  size_t memoryUsage() const;
-  size_t size() const;
+  VariantSlot* slot_;
+  SlotId currentId_, nextId_;
+};
 
-  VariantSlot* addSlot(MemoryPool*);
-  void removeSlot(VariantSlot* slot);
+class CollectionData {
+  SlotId head_ = NULL_SLOT;
+  SlotId tail_ = NULL_SLOT;
 
-  bool copyFrom(const CollectionData& src, MemoryPool* pool);
+ public:
+  // Placement new
+  static void* operator new(size_t, void* p) noexcept {
+    return p;
+  }
 
-  VariantSlot* head() const {
-    return head_;
+  static void operator delete(void*, void*) noexcept {}
+
+  using iterator = CollectionIterator;
+
+  iterator createIterator(const ResourceManager* resources) const {
+    return iterator(resources->getSlot(head_), head_);
   }
 
-  void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance);
+  size_t size(const ResourceManager*) const;
+  size_t nesting(const ResourceManager*) const;
 
- private:
-  VariantSlot* getSlot(size_t index) const;
+  void clear(ResourceManager* resources);
 
-  template <typename TAdaptedString>
-  VariantSlot* getSlot(TAdaptedString key) const;
+  static void clear(CollectionData* collection, ResourceManager* resources) {
+    if (!collection)
+      return;
+    collection->clear(resources);
+  }
+
+  void remove(iterator it, ResourceManager* resources);
+
+  static void remove(CollectionData* collection, iterator it,
+                     ResourceManager* resources) {
+    if (collection)
+      return collection->remove(it, resources);
+  }
 
-  VariantSlot* getPreviousSlot(VariantSlot*) const;
+  SlotId head() const {
+    return head_;
+  }
+
+  void addSlot(SlotWithId slot, ResourceManager* resources);
+
+ protected:
+  iterator addSlot(ResourceManager*);
+
+ private:
+  SlotWithId getPreviousSlot(VariantSlot*, const ResourceManager*) const;
 };
 
 inline const VariantData* collectionToVariant(
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp
index 134d5bd646dfc4cbee086b24ea62d81b1bfd33b1..c7e23a35117cc09b6e501bec73b4a4e1441fd0a1 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp
@@ -1,197 +1,137 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Collection/CollectionData.hpp>
-#include <ArduinoJson/Strings/StoragePolicy.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 VariantSlot* CollectionData::addSlot(MemoryPool* pool) {
-  VariantSlot* slot = pool->allocVariant();
-  if (!slot)
-    return 0;
-
-  if (tail_) {
-    ARDUINOJSON_ASSERT(pool->owns(tail_));  // Can't alter a linked array/object
-    tail_->setNextNotNull(slot);
-    tail_ = slot;
-  } else {
-    head_ = slot;
-    tail_ = slot;
-  }
-
-  slot->clear();
-  return slot;
+inline CollectionIterator::CollectionIterator(VariantSlot* slot, SlotId slotId)
+    : slot_(slot), currentId_(slotId) {
+  nextId_ = slot_ ? slot_->next() : NULL_SLOT;
 }
 
-inline VariantData* CollectionData::addElement(MemoryPool* pool) {
-  return slotData(addSlot(pool));
+inline const char* CollectionIterator::key() const {
+  ARDUINOJSON_ASSERT(slot_ != nullptr);
+  return slot_->key();
 }
 
-template <typename TAdaptedString>
-inline VariantData* CollectionData::addMember(TAdaptedString key,
-                                              MemoryPool* pool) {
-  VariantSlot* slot = addSlot(pool);
-  if (!slotSetKey(slot, key, pool)) {
-    removeSlot(slot);
-    return 0;
-  }
-  return slot->data();
+inline void CollectionIterator::setKey(const char* s) {
+  ARDUINOJSON_ASSERT(slot_ != nullptr);
+  ARDUINOJSON_ASSERT(s != nullptr);
+  return slot_->setKey(s);
 }
 
-inline void CollectionData::clear() {
-  head_ = 0;
-  tail_ = 0;
+inline void CollectionIterator::setKey(StringNode* s) {
+  ARDUINOJSON_ASSERT(slot_ != nullptr);
+  ARDUINOJSON_ASSERT(s != nullptr);
+  return slot_->setKey(s);
 }
 
-template <typename TAdaptedString>
-inline bool CollectionData::containsKey(const TAdaptedString& key) const {
-  return getSlot(key) != 0;
+inline bool CollectionIterator::ownsKey() const {
+  ARDUINOJSON_ASSERT(slot_ != nullptr);
+  return slot_->ownsKey();
 }
 
-inline bool CollectionData::copyFrom(const CollectionData& src,
-                                     MemoryPool* pool) {
-  clear();
-  for (VariantSlot* s = src.head_; s; s = s->next()) {
-    VariantData* var;
-    if (s->key() != 0) {
-      JsonString key(s->key(),
-                     s->ownsKey() ? JsonString::Copied : JsonString::Linked);
-      var = addMember(adaptString(key), pool);
-    } else {
-      var = addElement(pool);
-    }
-    if (!var)
-      return false;
-    if (!var->copyFrom(*s->data(), pool))
-      return false;
-  }
-  return true;
+inline void CollectionIterator::next(const ResourceManager* resources) {
+  ARDUINOJSON_ASSERT(currentId_ != NULL_SLOT);
+  slot_ = resources->getSlot(nextId_);
+  currentId_ = nextId_;
+  if (slot_)
+    nextId_ = slot_->next();
 }
 
-template <typename TAdaptedString>
-inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const {
-  if (key.isNull())
-    return 0;
-  VariantSlot* slot = head_;
-  while (slot) {
-    if (stringEquals(key, adaptString(slot->key())))
-      break;
-    slot = slot->next();
+inline CollectionData::iterator CollectionData::addSlot(
+    ResourceManager* resources) {
+  auto slot = resources->allocSlot();
+  if (!slot)
+    return {};
+  if (tail_ != NULL_SLOT) {
+    auto tail = resources->getSlot(tail_);
+    tail->setNext(slot.id());
+    tail_ = slot.id();
+  } else {
+    head_ = slot.id();
+    tail_ = slot.id();
   }
-  return slot;
-}
-
-inline VariantSlot* CollectionData::getSlot(size_t index) const {
-  if (!head_)
-    return 0;
-  return head_->next(index);
+  return iterator(slot, slot.id());
 }
 
-inline VariantSlot* CollectionData::getPreviousSlot(VariantSlot* target) const {
-  VariantSlot* current = head_;
-  while (current) {
-    VariantSlot* next = current->next();
-    if (next == target)
-      return current;
-    current = next;
+inline void CollectionData::addSlot(SlotWithId slot,
+                                    ResourceManager* resources) {
+  if (tail_ != NULL_SLOT) {
+    auto tail = resources->getSlot(tail_);
+    tail->setNext(slot.id());
+    tail_ = slot.id();
+  } else {
+    head_ = slot.id();
+    tail_ = slot.id();
   }
-  return 0;
-}
-
-template <typename TAdaptedString>
-inline VariantData* CollectionData::getMember(TAdaptedString key) const {
-  VariantSlot* slot = getSlot(key);
-  return slot ? slot->data() : 0;
 }
 
-template <typename TAdaptedString>
-inline VariantData* CollectionData::getOrAddMember(TAdaptedString key,
-                                                   MemoryPool* pool) {
-  // ignore null key
-  if (key.isNull())
-    return 0;
-
-  // search a matching key
-  VariantSlot* slot = getSlot(key);
-  if (slot)
-    return slot->data();
-
-  return addMember(key, pool);
-}
+inline void CollectionData::clear(ResourceManager* resources) {
+  auto next = head_;
+  while (next != NULL_SLOT) {
+    auto currId = next;
+    auto slot = resources->getSlot(next);
+    next = slot->next();
+    resources->freeSlot(SlotWithId(slot, currId));
+  }
 
-inline VariantData* CollectionData::getElement(size_t index) const {
-  VariantSlot* slot = getSlot(index);
-  return slot ? slot->data() : 0;
+  head_ = NULL_SLOT;
+  tail_ = NULL_SLOT;
 }
 
-inline VariantData* CollectionData::getOrAddElement(size_t index,
-                                                    MemoryPool* pool) {
-  VariantSlot* slot = head_;
-  while (slot && index > 0) {
-    slot = slot->next();
-    index--;
+inline SlotWithId CollectionData::getPreviousSlot(
+    VariantSlot* target, const ResourceManager* resources) const {
+  auto prev = SlotWithId();
+  auto currentId = head_;
+  while (currentId != NULL_SLOT) {
+    auto currentSlot = resources->getSlot(currentId);
+    if (currentSlot == target)
+      return prev;
+    prev = SlotWithId(currentSlot, currentId);
+    currentId = currentSlot->next();
   }
-  if (!slot)
-    index++;
-  while (index > 0) {
-    slot = addSlot(pool);
-    index--;
-  }
-  return slotData(slot);
+  return SlotWithId();
 }
 
-inline void CollectionData::removeSlot(VariantSlot* slot) {
-  if (!slot)
+inline void CollectionData::remove(iterator it, ResourceManager* resources) {
+  if (it.done())
     return;
-  VariantSlot* prev = getPreviousSlot(slot);
-  VariantSlot* next = slot->next();
+  auto curr = it.slot_;
+  auto prev = getPreviousSlot(curr, resources);
+  auto next = curr->next();
   if (prev)
     prev->setNext(next);
   else
     head_ = next;
-  if (!next)
-    tail_ = prev;
+  if (next == NULL_SLOT)
+    tail_ = prev.id();
+  resources->freeSlot({it.slot_, it.currentId_});
 }
 
-inline void CollectionData::removeElement(size_t index) {
-  removeSlot(getSlot(index));
-}
-
-inline size_t CollectionData::memoryUsage() const {
-  size_t total = 0;
-  for (VariantSlot* s = head_; s; s = s->next()) {
-    total += sizeof(VariantSlot) + s->data()->memoryUsage();
-    if (s->ownsKey())
-      total += strlen(s->key()) + 1;
+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 total;
-}
-
-inline size_t CollectionData::size() const {
-  return slotSize(head_);
-}
-
-template <typename T>
-inline void movePointer(T*& p, ptrdiff_t offset) {
-  if (!p)
-    return;
-  p = reinterpret_cast<T*>(
-      reinterpret_cast<void*>(reinterpret_cast<char*>(p) + offset));
-  ARDUINOJSON_ASSERT(isAligned(p));
+  return maxChildNesting + 1;
 }
 
-inline void CollectionData::movePointers(ptrdiff_t stringDistance,
-                                         ptrdiff_t variantDistance) {
-  movePointer(head_, variantDistance);
-  movePointer(tail_, variantDistance);
-  for (VariantSlot* slot = head_; slot; slot = slot->next())
-    slot->movePointers(stringDistance, variantDistance);
+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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Configuration.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Configuration.hpp
index ac0ea660a28ac3c23fdfc86e5cd0dc2be5d6ee5b..ae6089044d0931629bc0c0e905dd48a36764b38d 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Configuration.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Configuration.hpp
@@ -1,10 +1,11 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, 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>) && \
@@ -25,6 +26,7 @@
 #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)
@@ -55,50 +57,100 @@
 #endif
 
 // Store floating-point values with float (0) or double (1)
+// https://arduinojson.org/v7/config/use_double/
 #ifndef ARDUINOJSON_USE_DOUBLE
 #  define ARDUINOJSON_USE_DOUBLE 1
 #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 integral values with long (0) or long long (1)
+// https://arduinojson.org/v7/config/use_long_long/
 #ifndef ARDUINOJSON_USE_LONG_LONG
-#  if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ >= 4 || \
-      defined(_MSC_VER)
+#  if ARDUINOJSON_SIZEOF_POINTER >= 4  // 32 & 64 bits systems
 #    define ARDUINOJSON_USE_LONG_LONG 1
+#  else
+#    define ARDUINOJSON_USE_LONG_LONG 0
 #  endif
 #endif
-#ifndef ARDUINOJSON_USE_LONG_LONG
-#  define ARDUINOJSON_USE_LONG_LONG 0
-#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 bits to store the pointer to next node
-// (saves RAM but limits the number of values in a document)
-#ifndef ARDUINOJSON_SLOT_OFFSET_SIZE
-#  if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ <= 2
-// Address space == 16-bit => max 127 values
-#    define ARDUINOJSON_SLOT_OFFSET_SIZE 1
-#  elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ >= 8 || \
-      defined(_WIN64) && _WIN64
-// Address space == 64-bit => max 2147483647 values
-#    define ARDUINOJSON_SLOT_OFFSET_SIZE 4
+// 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_SIZEOF_POINTER <= 2
+#    define ARDUINOJSON_POOL_CAPACITY 16  // 128 bytes
+#  elif ARDUINOJSON_SIZEOF_POINTER == 4
+#    define ARDUINOJSON_POOL_CAPACITY 64  // 1024 bytes
 #  else
-// Address space == 32-bit => max 32767 values
-#    define ARDUINOJSON_SLOT_OFFSET_SIZE 2
+#    define ARDUINOJSON_POOL_CAPACITY 128  // 3072 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
@@ -109,6 +161,7 @@
 #  endif
 
 // Enable support for PROGMEM
+// https://arduinojson.org/v7/config/enable_progmem/
 #  ifndef ARDUINOJSON_ENABLE_PROGMEM
 #    define ARDUINOJSON_ENABLE_PROGMEM 1
 #  endif
@@ -116,11 +169,13 @@
 #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
@@ -131,6 +186,7 @@
 #  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
@@ -142,32 +198,38 @@
 #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
@@ -195,10 +257,6 @@
 #  define ARDUINOJSON_TAB "  "
 #endif
 
-#ifndef ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
-#  define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1
-#endif
-
 #ifndef ARDUINOJSON_STRING_BUFFER_SIZE
 #  define ARDUINOJSON_STRING_BUFFER_SIZE 32
 #endif
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp
index 1bfc393925755e3850de4869194fba4ab2093b3f..fd32a55e9eb74624305766bf97a6989ec5b74a8f 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationOptions.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationOptions.hpp
index 8400cc0b23113e5a58dece81bbdcd0a89eff9fb9..4c73934fc7f7801619a2e504d9aed7e01b388982 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationOptions.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationOptions.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp
index 39883027da6507cbb6d31a9649c898a26a7bbec3..76705560b3a2987e4aa14dae869b5bcbaf3e3cfc 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp
@@ -1,17 +1,24 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
-#include <ArduinoJson/Namespace.hpp>
+#include <ArduinoJson/Variant/JsonVariant.hpp>
+#include <ArduinoJson/Variant/VariantAttorney.hpp>
 
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 namespace DeserializationOption {
 class Filter {
  public:
-  explicit Filter(JsonVariantConst v) : variant_(v) {}
+#if ARDUINOJSON_AUTO_SHRINK
+  explicit Filter(JsonDocument& doc) : variant_(doc) {
+    doc.shrinkToFit();
+  }
+#endif
+
+  explicit Filter(JsonVariantConst variant) : variant_(variant) {}
 
   bool allow() const {
     return variant_;
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp
index 6434275bec9b8a42d4f7a70182811d65a7a28d19..d30402b4b22fc71da5509434e99f8cb652295c85 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp
index 44437686a1f28db47c391802f84d3cbd981fa310..fed87b34d079cbfe7dbc75812324859f6ab7b8bb 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -19,7 +19,7 @@ struct Reader {
 
   int read() {
     // clang-format off
-    return source_->read();  // Error here? See https://arduinojson.org/v6/invalid-input/
+    return source_->read();  // Error here? See https://arduinojson.org/v7/invalid-input/
     // clang-format on
   }
 
@@ -62,9 +62,8 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TInput>
-Reader<typename remove_reference<TInput>::type> makeReader(TInput&& input) {
-  return Reader<typename remove_reference<TInput>::type>{
-      detail::forward<TInput>(input)};
+Reader<remove_reference_t<TInput>> makeReader(TInput&& input) {
+  return Reader<remove_reference_t<TInput>>{detail::forward<TInput>(input)};
 }
 
 template <typename TChar>
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp
index 8a8738858113feea23513cd039f6ffc43489d989..a4b1143128e6a1d859f5f38baef1a8d3048f4834 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,13 +9,12 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TSource>
-struct Reader<TSource,
-              typename enable_if<is_base_of<Stream, TSource>::value>::type> {
+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
+    // don't use stream_->read() as it ignores the timeout
     char c;
     return stream_->readBytes(&c, 1) ? static_cast<unsigned char>(c) : -1;
   }
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp
index 14491f44a85e65972d13733786d1e6bea68763aa..d55859f73f71f45445d320fab1579e7527c42e92 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,8 +9,7 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TSource>
-struct Reader<TSource,
-              typename enable_if<is_base_of<::String, TSource>::value>::type>
+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()) {}
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp
index 97714afb14040ac14a004dcd4a43374a8e1c26cd..fa8ff23d750593207728be9211cd57d50c0ea858 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp
index c0ca4a7713bb62d7a7e11e2a61468171d0a31695..5584ec531f4f2f50950c3ea118ec02ba23cdd7b7 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp
@@ -1,9 +1,11 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
+#include <ArduinoJson/Polyfills/type_traits.hpp>
+
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TIterator>
@@ -29,13 +31,8 @@ class IteratorReader {
   }
 };
 
-template <typename T>
-struct void_ {
-  typedef void type;
-};
-
 template <typename TSource>
-struct Reader<TSource, typename void_<typename TSource::const_iterator>::type>
+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(),
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp
index eff67bac35615a155291de3c12e3326935e186d2..4739d72259f0029dff4613ba6f578e71722ecef3 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -19,8 +19,7 @@ template <typename T>
 struct IsCharOrVoid<const T> : IsCharOrVoid<T> {};
 
 template <typename TSource>
-struct Reader<TSource*,
-              typename enable_if<IsCharOrVoid<TSource>::value>::type> {
+struct Reader<TSource*, enable_if_t<IsCharOrVoid<TSource>::value>> {
   const char* ptr_;
 
  public:
@@ -39,8 +38,7 @@ struct Reader<TSource*,
 };
 
 template <typename TSource>
-struct BoundedReader<TSource*,
-                     typename enable_if<IsCharOrVoid<TSource>::value>::type>
+struct BoundedReader<TSource*, enable_if_t<IsCharOrVoid<TSource>::value>>
     : public IteratorReader<const char*> {
  public:
   explicit BoundedReader(const void* ptr, size_t len)
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp
index 41e0c00aac1807c10330a87d6e59a27e6e15633c..947dd7fee22128bc8f139b2e85a8c07e3b3a4d4e 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,8 +9,7 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TSource>
-struct Reader<TSource, typename enable_if<
-                           is_base_of<std::istream, TSource>::value>::type> {
+struct Reader<TSource, enable_if_t<is_base_of<std::istream, TSource>::value>> {
  public:
   explicit Reader(std::istream& stream) : stream_(&stream) {}
 
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp
index b60f1cec87787972050c56e0033690e2ddb0d6f6..92f9541c6a37b1f7983a524952406042cbf64d06 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -10,7 +10,7 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TVariant>
-struct Reader<TVariant, typename enable_if<IsVariant<TVariant>::value>::type>
+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*>()) {}
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp
index 9f4d78e85e66c76e0254b974a32d77230f8d2464..b33313e16a55d8552a4eb8b2a13f54b9fe1f7fbb 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -8,7 +8,6 @@
 #include <ArduinoJson/Deserialization/DeserializationOptions.hpp>
 #include <ArduinoJson/Deserialization/Reader.hpp>
 #include <ArduinoJson/Polyfills/utility.hpp>
-#include <ArduinoJson/StringStorage/StringStorage.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
@@ -23,44 +22,61 @@ struct first_or_void<T, Rest...> {
   using type = T;
 };
 
-template <template <typename, typename> class TDeserializer, typename TReader,
-          typename TWriter>
-TDeserializer<TReader, TWriter> makeDeserializer(MemoryPool* pool,
-                                                 TReader reader,
-                                                 TWriter writer) {
-  ARDUINOJSON_ASSERT(pool != 0);
-  return TDeserializer<TReader, TWriter>(pool, reader, writer);
+// A meta-function that returns true if T is a valid destination type for
+// deserialize()
+template <class T, class = void>
+struct is_deserialize_destination : false_type {};
+
+template <class T>
+struct is_deserialize_destination<
+    T, enable_if_t<is_same<decltype(VariantAttorney::getResourceManager(
+                               detail::declval<T&>())),
+                           ResourceManager*>::value>> : true_type {};
+
+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, typename> class TDeserializer, typename TStream,
-          typename... Args,
-          typename = typename enable_if<  // issue #1897
-              !is_integral<typename first_or_void<Args...>::type>::value>::type>
-DeserializationError deserialize(JsonDocument& doc, TStream&& input,
+template <template <typename> class TDeserializer, typename TDestination,
+          typename TStream, typename... Args,
+          typename = enable_if_t<  // issue #1897
+              !is_integral<typename first_or_void<Args...>::type>::value>>
+DeserializationError deserialize(TDestination&& dst, TStream&& input,
                                  Args... args) {
-  auto reader = makeReader(detail::forward<TStream>(input));
-  auto data = VariantAttorney::getData(doc);
-  auto pool = VariantAttorney::getPool(doc);
-  auto options = makeDeserializationOptions(args...);
-  doc.clear();
-  return makeDeserializer<TDeserializer>(pool, reader,
-                                         makeStringStorage(input, pool))
-      .parse(*data, options.filter, options.nestingLimit);
+  return doDeserialize<TDeserializer>(
+      dst, makeReader(detail::forward<TStream>(input)),
+      makeDeserializationOptions(args...));
 }
 
-template <template <typename, typename> class TDeserializer, typename TChar,
-          typename Size, typename... Args,
-          typename = typename enable_if<is_integral<Size>::value>::type>
-DeserializationError deserialize(JsonDocument& doc, TChar* input,
+template <template <typename> class TDeserializer, typename TDestination,
+          typename TChar, typename Size, typename... Args,
+          typename = enable_if_t<is_integral<Size>::value>>
+DeserializationError deserialize(TDestination&& dst, TChar* input,
                                  Size inputSize, Args... args) {
-  auto reader = makeReader(input, size_t(inputSize));
-  auto data = VariantAttorney::getData(doc);
-  auto pool = VariantAttorney::getPool(doc);
-  auto options = makeDeserializationOptions(args...);
-  doc.clear();
-  return makeDeserializer<TDeserializer>(pool, reader,
-                                         makeStringStorage(input, pool))
-      .parse(*data, options.filter, options.nestingLimit);
+  return doDeserialize<TDeserializer>(dst, makeReader(input, size_t(inputSize)),
+                                      makeDeserializationOptions(args...));
 }
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp
deleted file mode 100644
index bc206f02f3d90cd566aea7fe657652c1343dbaad..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp
+++ /dev/null
@@ -1,168 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Document/JsonDocument.hpp>
-
-ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
-
-// Helper to implement the "base-from-member" idiom
-// (we need to store the allocator before constructing JsonDocument)
-template <typename TAllocator>
-class AllocatorOwner {
- public:
-  AllocatorOwner() {}
-  AllocatorOwner(TAllocator a) : allocator_(a) {}
-
-  void* allocate(size_t size) {
-    return allocator_.allocate(size);
-  }
-
-  void deallocate(void* ptr) {
-    if (ptr)
-      allocator_.deallocate(ptr);
-  }
-
-  void* reallocate(void* ptr, size_t new_size) {
-    return allocator_.reallocate(ptr, new_size);
-  }
-
-  TAllocator& allocator() {
-    return allocator_;
-  }
-
- private:
-  TAllocator allocator_;
-};
-
-// A JsonDocument that uses the provided allocator to allocate its memory pool.
-// https://arduinojson.org/v6/api/basicjsondocument/
-template <typename TAllocator>
-class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
- public:
-  explicit BasicJsonDocument(size_t capa, TAllocator alloc = TAllocator())
-      : AllocatorOwner<TAllocator>(alloc), JsonDocument(allocPool(capa)) {}
-
-  // Copy-constructor
-  BasicJsonDocument(const BasicJsonDocument& src)
-      : AllocatorOwner<TAllocator>(src), JsonDocument() {
-    copyAssignFrom(src);
-  }
-
-  // Move-constructor
-  BasicJsonDocument(BasicJsonDocument&& src) : AllocatorOwner<TAllocator>(src) {
-    moveAssignFrom(src);
-  }
-
-  BasicJsonDocument(const JsonDocument& src) {
-    copyAssignFrom(src);
-  }
-
-  // Construct from variant, array, or object
-  template <typename T>
-  BasicJsonDocument(const T& src,
-                    typename detail::enable_if<
-                        detail::is_same<T, JsonVariant>::value ||
-                        detail::is_same<T, JsonVariantConst>::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>::type* = 0)
-      : JsonDocument(allocPool(src.memoryUsage())) {
-    set(src);
-  }
-
-  // disambiguate
-  BasicJsonDocument(JsonVariant src)
-      : JsonDocument(allocPool(src.memoryUsage())) {
-    set(src);
-  }
-
-  ~BasicJsonDocument() {
-    freePool();
-  }
-
-  BasicJsonDocument& operator=(const BasicJsonDocument& src) {
-    copyAssignFrom(src);
-    return *this;
-  }
-
-  BasicJsonDocument& operator=(BasicJsonDocument&& src) {
-    moveAssignFrom(src);
-    return *this;
-  }
-
-  template <typename T>
-  BasicJsonDocument& operator=(const T& src) {
-    size_t requiredSize = src.memoryUsage();
-    if (requiredSize > capacity())
-      reallocPool(requiredSize);
-    set(src);
-    return *this;
-  }
-
-  // Reduces the capacity of the memory pool to match the current usage.
-  // https://arduinojson.org/v6/api/basicjsondocument/shrinktofit/
-  void shrinkToFit() {
-    ptrdiff_t bytes_reclaimed = pool_.squash();
-    if (bytes_reclaimed == 0)
-      return;
-
-    void* old_ptr = pool_.buffer();
-    void* new_ptr = this->reallocate(old_ptr, pool_.capacity());
-
-    ptrdiff_t ptr_offset =
-        static_cast<char*>(new_ptr) - static_cast<char*>(old_ptr);
-
-    pool_.movePointers(ptr_offset);
-    data_.movePointers(ptr_offset, ptr_offset - bytes_reclaimed);
-  }
-
-  // Reclaims the memory leaked when removing and replacing values.
-  // https://arduinojson.org/v6/api/jsondocument/garbagecollect/
-  bool garbageCollect() {
-    // make a temporary clone and move assign
-    BasicJsonDocument tmp(*this);
-    if (!tmp.capacity())
-      return false;
-    moveAssignFrom(tmp);
-    return true;
-  }
-
-  using AllocatorOwner<TAllocator>::allocator;
-
- private:
-  detail::MemoryPool allocPool(size_t requiredSize) {
-    size_t capa = detail::addPadding(requiredSize);
-    return {reinterpret_cast<char*>(this->allocate(capa)), capa};
-  }
-
-  void reallocPool(size_t requiredSize) {
-    size_t capa = detail::addPadding(requiredSize);
-    if (capa == pool_.capacity())
-      return;
-    freePool();
-    replacePool(allocPool(detail::addPadding(requiredSize)));
-  }
-
-  void freePool() {
-    this->deallocate(getPool()->buffer());
-  }
-
-  void copyAssignFrom(const JsonDocument& src) {
-    reallocPool(src.capacity());
-    set(src);
-  }
-
-  void moveAssignFrom(BasicJsonDocument& src) {
-    freePool();
-    data_ = src.data_;
-    pool_ = src.pool_;
-    src.data_.setNull();
-    src.pool_ = {0, 0};
-  }
-};
-
-ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp
deleted file mode 100644
index 3c2dc3a61cf9f85f5ef72eff400be71192dd2d82..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Document/BasicJsonDocument.hpp>
-
-#include <stdlib.h>  // malloc, free
-
-ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
-
-// The allocator of DynamicJsonDocument.
-struct DefaultAllocator {
-  void* allocate(size_t size) {
-    return malloc(size);
-  }
-
-  void deallocate(void* ptr) {
-    free(ptr);
-  }
-
-  void* reallocate(void* ptr, size_t new_size) {
-    return realloc(ptr, new_size);
-  }
-};
-
-// A JsonDocument with a memory pool in the heap.
-// https://arduinojson.org/v6/api/dynamicjsondocument/
-typedef BasicJsonDocument<DefaultAllocator> DynamicJsonDocument;
-
-ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp
index 39c1536f12b232b6af397a619fcdf4f82349271f..acad2b2f06edbbd59f38439cd93986403172322b 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp
@@ -1,308 +1,383 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Array/ElementProxy.hpp>
-#include <ArduinoJson/Memory/MemoryPool.hpp>
+#include <ArduinoJson/Memory/Allocator.hpp>
+#include <ArduinoJson/Memory/ResourceManager.hpp>
 #include <ArduinoJson/Object/JsonObject.hpp>
 #include <ArduinoJson/Object/MemberProxy.hpp>
-#include <ArduinoJson/Strings/StoragePolicy.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/v6/api/jsondocument/
+// https://arduinojson.org/v7/api/jsondocument/
 class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
   friend class detail::VariantAttorney;
 
  public:
-  JsonDocument(const JsonDocument&) = delete;
-  JsonDocument& operator=(const JsonDocument&) = delete;
+  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>
+  JsonDocument(
+      const T& src, Allocator* alloc = detail::DefaultAllocator::instance(),
+      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>* = 0)
+      : 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/v6/api/jsondocument/as/
+  // 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/v6/api/jsondocument/as/
+  // 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/v6/api/jsondocument/clear/
+  // https://arduinojson.org/v7/api/jsondocument/clear/
   void clear() {
-    pool_.clear();
-    data_.setNull();
+    resources_.clear();
+    data_.reset();
   }
 
   // Returns true if the root is of the specified type.
-  // https://arduinojson.org/v6/api/jsondocument/is/
+  // 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/v6/api/jsondocument/is/
+  // 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/v6/api/jsondocument/isnull/
+  // https://arduinojson.org/v7/api/jsondocument/isnull/
   bool isNull() const {
     return getVariant().isNull();
   }
 
-  // Returns the number of used bytes in the memory pool.
-  // https://arduinojson.org/v6/api/jsondocument/memoryusage/
-  size_t memoryUsage() const {
-    return pool_.size();
-  }
-
   // Returns trues if the memory pool was too small.
-  // https://arduinojson.org/v6/api/jsondocument/overflowed/
+  // https://arduinojson.org/v7/api/jsondocument/overflowed/
   bool overflowed() const {
-    return pool_.overflowed();
+    return resources_.overflowed();
   }
 
   // Returns the depth (nesting level) of the array.
-  // https://arduinojson.org/v6/api/jsondocument/nesting/
+  // https://arduinojson.org/v7/api/jsondocument/nesting/
   size_t nesting() const {
-    return variantNesting(&data_);
-  }
-
-  // Returns the capacity of the memory pool.
-  // https://arduinojson.org/v6/api/jsondocument/capacity/
-  size_t capacity() const {
-    return pool_.capacity();
+    return data_.nesting(&resources_);
   }
 
   // Returns the number of elements in the root array or object.
-  // https://arduinojson.org/v6/api/jsondocument/size/
+  // https://arduinojson.org/v7/api/jsondocument/size/
   size_t size() const {
-    return data_.size();
+    return data_.size(&resources_);
   }
 
   // Copies the specified document.
-  // https://arduinojson.org/v6/api/jsondocument/set/
+  // 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/v6/api/jsondocument/set/
+  // https://arduinojson.org/v7/api/jsondocument/set/
   template <typename T>
-  typename detail::enable_if<!detail::is_base_of<JsonDocument, T>::value,
-                             bool>::type
-  set(const T& src) {
+  detail::enable_if_t<!detail::is_base_of<JsonDocument, T>::value, bool> set(
+      const T& src) {
     return to<JsonVariant>().set(src);
   }
 
   // Clears the document and converts it to the specified type.
-  // https://arduinojson.org/v6/api/jsondocument/to/
+  // https://arduinojson.org/v7/api/jsondocument/to/
   template <typename T>
   typename detail::VariantTo<T>::type to() {
     clear();
     return getVariant().template to<T>();
   }
 
-  // Creates an array and appends it to the root array.
-  // https://arduinojson.org/v6/api/jsondocument/createnestedarray/
-  JsonArray createNestedArray() {
-    return add().to<JsonArray>();
-  }
-
-  // Creates an array and adds it to the root object.
-  // https://arduinojson.org/v6/api/jsondocument/createnestedarray/
-  template <typename TChar>
-  JsonArray createNestedArray(TChar* key) {
-    return operator[](key).template to<JsonArray>();
-  }
-
-  // Creates an array and adds it to the root object.
-  // https://arduinojson.org/v6/api/jsondocument/createnestedarray/
-  template <typename TString>
-  JsonArray createNestedArray(const TString& key) {
-    return operator[](key).template to<JsonArray>();
-  }
-
-  // Creates an object and appends it to the root array.
-  // https://arduinojson.org/v6/api/jsondocument/createnestedobject/
-  JsonObject createNestedObject() {
-    return add().to<JsonObject>();
-  }
-
-  // Creates an object and adds it to the root object.
-  // https://arduinojson.org/v6/api/jsondocument/createnestedobject/
-  template <typename TChar>
-  JsonObject createNestedObject(TChar* key) {
-    return operator[](key).template to<JsonObject>();
-  }
-
-  // Creates an object and adds it to the root object.
-  // https://arduinojson.org/v6/api/jsondocument/createnestedobject/
-  template <typename TString>
-  JsonObject createNestedObject(const TString& key) {
-    return operator[](key).template to<JsonObject>();
-  }
-
   // Returns true if the root object contains the specified key.
-  // https://arduinojson.org/v6/api/jsondocument/containskey/
+  // https://arduinojson.org/v7/api/jsondocument/containskey/
   template <typename TChar>
   bool containsKey(TChar* key) const {
-    return data_.getMember(detail::adaptString(key)) != 0;
+    return data_.getMember(detail::adaptString(key), &resources_) != 0;
   }
 
   // Returns true if the root object contains the specified key.
-  // https://arduinojson.org/v6/api/jsondocument/containskey/
+  // https://arduinojson.org/v7/api/jsondocument/containskey/
   template <typename TString>
-  bool containsKey(const TString& key) const {
-    return data_.getMember(detail::adaptString(key)) != 0;
+  detail::enable_if_t<detail::IsString<TString>::value, bool> containsKey(
+      const TString& key) const {
+    return data_.getMember(detail::adaptString(key), &resources_) != 0;
+  }
+
+  // Returns true if the root object contains the specified key.
+  // https://arduinojson.org/v7/api/jsondocument/containskey/
+  template <typename TVariant>
+  detail::enable_if_t<detail::IsVariant<TVariant>::value, bool> containsKey(
+      const TVariant& key) const {
+    return containsKey(key.template as<const char*>());
   }
 
   // Gets or sets a root object's member.
-  // https://arduinojson.org/v6/api/jsondocument/subscript/
+  // https://arduinojson.org/v7/api/jsondocument/subscript/
   template <typename TString>
-  FORCE_INLINE typename detail::enable_if<
-      detail::IsString<TString>::value,
-      detail::MemberProxy<JsonDocument&, TString>>::type
+  detail::enable_if_t<detail::IsString<TString>::value,
+                      detail::MemberProxy<JsonDocument&, TString>>
   operator[](const TString& key) {
     return {*this, key};
   }
 
   // Gets or sets a root object's member.
-  // https://arduinojson.org/v6/api/jsondocument/subscript/
+  // https://arduinojson.org/v7/api/jsondocument/subscript/
   template <typename TChar>
-  FORCE_INLINE typename detail::enable_if<
-      detail::IsString<TChar*>::value,
-      detail::MemberProxy<JsonDocument&, TChar*>>::type
+  detail::enable_if_t<detail::IsString<TChar*>::value,
+                      detail::MemberProxy<JsonDocument&, TChar*>>
   operator[](TChar* key) {
     return {*this, key};
   }
 
   // Gets a root object's member.
-  // https://arduinojson.org/v6/api/jsondocument/subscript/
+  // https://arduinojson.org/v7/api/jsondocument/subscript/
   template <typename TString>
-  FORCE_INLINE typename detail::enable_if<detail::IsString<TString>::value,
-                                          JsonVariantConst>::type
+  detail::enable_if_t<detail::IsString<TString>::value, JsonVariantConst>
   operator[](const TString& key) const {
-    return JsonVariantConst(data_.getMember(detail::adaptString(key)));
+    return JsonVariantConst(
+        data_.getMember(detail::adaptString(key), &resources_), &resources_);
   }
 
   // Gets a root object's member.
-  // https://arduinojson.org/v6/api/jsondocument/subscript/
+  // https://arduinojson.org/v7/api/jsondocument/subscript/
   template <typename TChar>
-  FORCE_INLINE typename detail::enable_if<detail::IsString<TChar*>::value,
-                                          JsonVariantConst>::type
+  detail::enable_if_t<detail::IsString<TChar*>::value, JsonVariantConst>
   operator[](TChar* key) const {
-    return JsonVariantConst(data_.getMember(detail::adaptString(key)));
+    return JsonVariantConst(
+        data_.getMember(detail::adaptString(key), &resources_), &resources_);
   }
 
   // Gets or sets a root array's element.
-  // https://arduinojson.org/v6/api/jsondocument/subscript/
-  FORCE_INLINE detail::ElementProxy<JsonDocument&> operator[](size_t index) {
-    return {*this, index};
+  // https://arduinojson.org/v7/api/jsondocument/subscript/
+  template <typename T>
+  detail::enable_if_t<detail::is_integral<T>::value,
+                      detail::ElementProxy<JsonDocument&>>
+  operator[](T index) {
+    return {*this, size_t(index)};
   }
 
   // Gets a root array's member.
-  // https://arduinojson.org/v6/api/jsondocument/subscript/
-  FORCE_INLINE JsonVariantConst operator[](size_t index) const {
-    return JsonVariantConst(data_.getElement(index));
+  // 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, JsonVariantConst>
+  operator[](const TVariant& key) const {
+    if (key.template is<const char*>())
+      return operator[](key.template as<const char*>());
+    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, 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/v6/api/jsondocument/add/
-  FORCE_INLINE JsonVariant add() {
-    return JsonVariant(&pool_, data_.addElement(&pool_));
+  // https://arduinojson.org/v7/api/jsondocument/add/
+  template <typename T>
+  detail::enable_if_t<detail::is_same<T, JsonVariant>::value, T> add() {
+    return JsonVariant(data_.addElement(&resources_), &resources_);
   }
 
   // Appends a value to the root array.
-  // https://arduinojson.org/v6/api/jsondocument/add/
+  // https://arduinojson.org/v7/api/jsondocument/add/
   template <typename TValue>
-  FORCE_INLINE bool add(const TValue& value) {
-    return add().set(value);
+  bool add(const TValue& value) {
+    return data_.addValue(value, &resources_);
   }
 
   // Appends a value to the root array.
-  // https://arduinojson.org/v6/api/jsondocument/add/
+  // https://arduinojson.org/v7/api/jsondocument/add/
   template <typename TChar>
-  FORCE_INLINE bool add(TChar* value) {
-    return add().set(value);
+  bool add(TChar* value) {
+    return data_.addValue(value, &resources_);
   }
 
   // Removes an element of the root array.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsondocument/remove/
-  FORCE_INLINE void remove(size_t index) {
-    data_.remove(index);
+  // https://arduinojson.org/v7/api/jsondocument/remove/
+  template <typename T>
+  detail::enable_if_t<detail::is_integral<T>::value> remove(T index) {
+    detail::VariantData::removeElement(getData(), size_t(index),
+                                       getResourceManager());
   }
 
   // Removes a member of the root object.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsondocument/remove/
+  // https://arduinojson.org/v7/api/jsondocument/remove/
   template <typename TChar>
-  FORCE_INLINE typename detail::enable_if<detail::IsString<TChar*>::value>::type
-  remove(TChar* key) {
-    data_.remove(detail::adaptString(key));
+  detail::enable_if_t<detail::IsString<TChar*>::value> remove(TChar* key) {
+    detail::VariantData::removeMember(getData(), detail::adaptString(key),
+                                      getResourceManager());
   }
 
   // Removes a member of the root object.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsondocument/remove/
+  // https://arduinojson.org/v7/api/jsondocument/remove/
   template <typename TString>
-  FORCE_INLINE
-      typename detail::enable_if<detail::IsString<TString>::value>::type
-      remove(const TString& key) {
-    data_.remove(detail::adaptString(key));
+  detail::enable_if_t<detail::IsString<TString>::value> remove(
+      const TString& key) {
+    detail::VariantData::removeMember(getData(), detail::adaptString(key),
+                                      getResourceManager());
   }
 
-  FORCE_INLINE operator JsonVariant() {
+  // 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> 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();
   }
 
-  FORCE_INLINE operator JsonVariantConst() const {
+  operator JsonVariantConst() const {
     return getVariant();
   }
 
- protected:
-  JsonDocument() : pool_(0, 0) {}
+  friend void swap(JsonDocument& a, JsonDocument& b) {
+    swap(a.resources_, b.resources_);
+    swap_(a.data_, b.data_);
+  }
 
-  JsonDocument(detail::MemoryPool pool) : pool_(pool) {}
+  // DEPRECATED: use add<JsonVariant>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonVariant>() instead")
+  JsonVariant add() {
+    return add<JsonVariant>();
+  }
 
-  JsonDocument(char* buf, size_t capa) : pool_(buf, capa) {}
+  // DEPRECATED: use add<JsonArray>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonArray>() instead")
+  JsonArray createNestedArray() {
+    return add<JsonArray>();
+  }
 
-  ~JsonDocument() {}
+  // 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>();
+  }
 
-  void replacePool(detail::MemoryPool pool) {
-    pool_ = pool;
+  // 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(&pool_, &data_);
+    return JsonVariant(&data_, &resources_);
   }
 
   JsonVariantConst getVariant() const {
-    return JsonVariantConst(&data_);
+    return JsonVariantConst(&data_, &resources_);
   }
 
-  detail::MemoryPool pool_;
-  detail::VariantData data_;
-
- protected:
-  detail::MemoryPool* getPool() {
-    return &pool_;
+  detail::ResourceManager* getResourceManager() {
+    return &resources_;
   }
 
   detail::VariantData* getData() {
@@ -316,6 +391,9 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
   detail::VariantData* getOrCreateData() {
     return &data_;
   }
+
+  detail::ResourceManager resources_;
+  detail::VariantData data_;
 };
 
 inline void convertToJson(const JsonDocument& src, JsonVariant dst) {
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp
deleted file mode 100644
index 0716666436c235efa8868372990acade9b4bf416..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp
+++ /dev/null
@@ -1,61 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Document/JsonDocument.hpp>
-
-ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
-
-// A JsonDocument with a memory pool on the stack.
-template <size_t desiredCapacity>
-class StaticJsonDocument : public JsonDocument {
-  static const size_t capacity_ =
-      detail::AddPadding<detail::Max<1, desiredCapacity>::value>::value;
-
- public:
-  StaticJsonDocument() : JsonDocument(buffer_, capacity_) {}
-
-  StaticJsonDocument(const StaticJsonDocument& src)
-      : JsonDocument(buffer_, capacity_) {
-    set(src);
-  }
-
-  template <typename T>
-  StaticJsonDocument(
-      const T& src,
-      typename detail::enable_if<
-          detail::is_convertible<T, JsonVariantConst>::value>::type* = 0)
-      : JsonDocument(buffer_, capacity_) {
-    set(src);
-  }
-
-  // disambiguate
-  StaticJsonDocument(JsonVariant src) : JsonDocument(buffer_, capacity_) {
-    set(src);
-  }
-
-  StaticJsonDocument& operator=(const StaticJsonDocument& src) {
-    set(src);
-    return *this;
-  }
-
-  template <typename T>
-  StaticJsonDocument& operator=(const T& src) {
-    set(src);
-    return *this;
-  }
-
-  // Reclaims the memory leaked when removing and replacing values.
-  // https://arduinojson.org/v6/api/jsondocument/garbagecollect/
-  void garbageCollect() {
-    StaticJsonDocument tmp(*this);
-    set(tmp);
-  }
-
- private:
-  char buffer_[capacity_];
-};
-
-ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp
index d2740cc4235c1e2e3d6b6266b28ea4c04e728dd7..e1b7199ee17ce278af5c76354ca86f2d16eeb851 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp
index a566aea9ea6ffaafc257d62670dbb0d5ad566d58..33de891a370b04b19935b039ebc095f691ba38be 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,7 +9,7 @@
 #include <ArduinoJson/Json/Latch.hpp>
 #include <ArduinoJson/Json/Utf16.hpp>
 #include <ArduinoJson/Json/Utf8.hpp>
-#include <ArduinoJson/Memory/MemoryPool.hpp>
+#include <ArduinoJson/Memory/ResourceManager.hpp>
 #include <ArduinoJson/Numbers/parseNumber.hpp>
 #include <ArduinoJson/Polyfills/assert.hpp>
 #include <ArduinoJson/Polyfills/type_traits.hpp>
@@ -18,15 +18,14 @@
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
-template <typename TReader, typename TStringStorage>
+template <typename TReader>
 class JsonDeserializer {
  public:
-  JsonDeserializer(MemoryPool* pool, TReader reader,
-                   TStringStorage stringStorage)
-      : stringStorage_(stringStorage),
+  JsonDeserializer(ResourceManager* resources, TReader reader)
+      : stringBuilder_(resources),
         foundSomething_(false),
         latch_(reader),
-        pool_(pool) {}
+        resources_(resources) {}
 
   template <typename TFilter>
   DeserializationError parse(VariantData& variant, TFilter filter,
@@ -35,7 +34,7 @@ class JsonDeserializer {
 
     err = parseVariant(variant, filter, nestingLimit);
 
-    if (!err && latch_.last() != 0 && !variant.isEnclosed()) {
+    if (!err && latch_.last() != 0 && variant.isFloat()) {
       // We don't detect trailing characters earlier, so we need to check now
       return DeserializationError::InvalidInput;
     }
@@ -147,7 +146,7 @@ class JsonDeserializer {
 
   template <typename TFilter>
   DeserializationError::Code parseArray(
-      CollectionData& array, TFilter filter,
+      ArrayData& array, TFilter filter,
       DeserializationOption::NestingLimit nestingLimit) {
     DeserializationError::Code err;
 
@@ -167,18 +166,18 @@ class JsonDeserializer {
     if (eat(']'))
       return DeserializationError::Ok;
 
-    TFilter memberFilter = filter[0UL];
+    TFilter elementFilter = filter[0UL];
 
     // Read each value
     for (;;) {
-      if (memberFilter.allow()) {
+      if (elementFilter.allow()) {
         // Allocate slot in array
-        VariantData* value = array.addElement(pool_);
+        VariantData* value = array.addElement(resources_);
         if (!value)
           return DeserializationError::NoMemory;
 
         // 1 - Parse value
-        err = parseVariant(*value, memberFilter, nestingLimit.decrement());
+        err = parseVariant(*value, elementFilter, nestingLimit.decrement());
         if (err)
           return err;
       } else {
@@ -233,7 +232,7 @@ class JsonDeserializer {
 
   template <typename TFilter>
   DeserializationError::Code parseObject(
-      CollectionData& object, TFilter filter,
+      ObjectData& object, TFilter filter,
       DeserializationOption::NestingLimit nestingLimit) {
     DeserializationError::Code err;
 
@@ -269,29 +268,26 @@ class JsonDeserializer {
       if (!eat(':'))
         return DeserializationError::InvalidInput;
 
-      JsonString key = stringStorage_.str();
+      JsonString key = stringBuilder_.str();
 
       TFilter memberFilter = filter[key.c_str()];
 
       if (memberFilter.allow()) {
-        VariantData* variant = object.getMember(adaptString(key.c_str()));
-        if (!variant) {
+        auto member = object.getMember(adaptString(key.c_str()), resources_);
+        if (!member) {
           // Save key in memory pool.
-          // This MUST be done before adding the slot.
-          key = stringStorage_.save();
+          auto savedKey = stringBuilder_.save();
 
           // Allocate slot in object
-          VariantSlot* slot = object.addSlot(pool_);
-          if (!slot)
+          member = object.addMember(savedKey, resources_);
+          if (!member)
             return DeserializationError::NoMemory;
-
-          slot->setKey(key);
-
-          variant = slot->data();
+        } else {
+          member->setNull(resources_);
         }
 
         // Parse value
-        err = parseVariant(*variant, memberFilter, nestingLimit.decrement());
+        err = parseVariant(*member, memberFilter, nestingLimit.decrement());
         if (err)
           return err;
       } else {
@@ -377,7 +373,7 @@ class JsonDeserializer {
   }
 
   DeserializationError::Code parseKey() {
-    stringStorage_.startString();
+    stringBuilder_.startString();
     if (isQuote(current())) {
       return parseQuotedString();
     } else {
@@ -388,13 +384,13 @@ class JsonDeserializer {
   DeserializationError::Code parseStringValue(VariantData& variant) {
     DeserializationError::Code err;
 
-    stringStorage_.startString();
+    stringBuilder_.startString();
 
     err = parseQuotedString();
     if (err)
       return err;
 
-    variant.setString(stringStorage_.save());
+    variant.setOwnedString(stringBuilder_.save());
 
     return DeserializationError::Ok;
   }
@@ -430,9 +426,9 @@ class JsonDeserializer {
           if (err)
             return err;
           if (codepoint.append(codeunit))
-            Utf8::encodeCodepoint(codepoint.value(), stringStorage_);
+            Utf8::encodeCodepoint(codepoint.value(), stringBuilder_);
 #else
-          stringStorage_.append('\\');
+          stringBuilder_.append('\\');
 #endif
           continue;
         }
@@ -444,10 +440,10 @@ class JsonDeserializer {
         move();
       }
 
-      stringStorage_.append(c);
+      stringBuilder_.append(c);
     }
 
-    if (!stringStorage_.isValid())
+    if (!stringBuilder_.isValid())
       return DeserializationError::NoMemory;
 
     return DeserializationError::Ok;
@@ -460,14 +456,14 @@ class JsonDeserializer {
     if (canBeInNonQuotedString(c)) {  // no quotes
       do {
         move();
-        stringStorage_.append(c);
+        stringBuilder_.append(c);
         c = current();
       } while (canBeInNonQuotedString(c));
     } else {
       return DeserializationError::InvalidInput;
     }
 
-    if (!stringStorage_.isValid())
+    if (!stringBuilder_.isValid())
       return DeserializationError::NoMemory;
 
     return DeserializationError::Ok;
@@ -659,10 +655,10 @@ class JsonDeserializer {
     return DeserializationError::Ok;
   }
 
-  TStringStorage stringStorage_;
+  StringBuilder stringBuilder_;
   bool foundSomething_;
   Latch<TReader> latch_;
-  MemoryPool* pool_;
+  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
@@ -673,21 +669,25 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // Parses a JSON input, filters, and puts the result in a JsonDocument.
-// https://arduinojson.org/v6/api/json/deserializejson/
-template <typename... Args>
-DeserializationError deserializeJson(JsonDocument& doc, Args&&... args) {
+// https://arduinojson.org/v7/api/json/deserializejson/
+template <typename TDestination, typename... Args>
+detail::enable_if_t<detail::is_deserialize_destination<TDestination>::value,
+                    DeserializationError>
+deserializeJson(TDestination&& dst, Args&&... args) {
   using namespace detail;
-  return deserialize<JsonDeserializer>(doc, detail::forward<Args>(args)...);
+  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/v6/api/json/deserializejson/
-template <typename TChar, typename... Args>
-DeserializationError deserializeJson(JsonDocument& doc, TChar* input,
-                                     Args&&... args) {
+// https://arduinojson.org/v7/api/json/deserializejson/
+template <typename TDestination, typename TChar, typename... Args>
+detail::enable_if_t<detail::is_deserialize_destination<TDestination>::value,
+                    DeserializationError>
+deserializeJson(TDestination&& dst, TChar* input, Args&&... args) {
   using namespace detail;
-  return deserialize<JsonDeserializer>(doc, input,
-                                       detail::forward<Args>(args)...);
+  return deserialize<JsonDeserializer>(detail::forward<TDestination>(dst),
+                                       input, detail::forward<Args>(args)...);
 }
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp
index 781c9c93553b47d1386914235fe479fce811895e..189ae33f7d6f3ef9645b669a62b98ec2462e7a4a 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -7,93 +7,96 @@
 #include <ArduinoJson/Json/TextFormatter.hpp>
 #include <ArduinoJson/Serialization/measure.hpp>
 #include <ArduinoJson/Serialization/serialize.hpp>
-#include <ArduinoJson/Variant/Visitor.hpp>
+#include <ArduinoJson/Variant/VariantDataVisitor.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TWriter>
-class JsonSerializer : public Visitor<size_t> {
+class JsonSerializer : public VariantDataVisitor<size_t> {
  public:
   static const bool producesText = true;
 
-  JsonSerializer(TWriter writer) : formatter_(writer) {}
+  JsonSerializer(TWriter writer, const ResourceManager* resources)
+      : formatter_(writer), resources_(resources) {}
 
-  FORCE_INLINE size_t visitArray(const CollectionData& array) {
+  size_t visit(const ArrayData& array) {
     write('[');
 
-    const VariantSlot* slot = array.head();
+    auto slotId = array.head();
+
+    while (slotId != NULL_SLOT) {
+      auto slot = resources_->getSlot(slotId);
 
-    while (slot != 0) {
       slot->data()->accept(*this);
 
-      slot = slot->next();
-      if (slot == 0)
-        break;
+      slotId = slot->next();
 
-      write(',');
+      if (slotId != NULL_SLOT)
+        write(',');
     }
 
     write(']');
     return bytesWritten();
   }
 
-  size_t visitObject(const CollectionData& object) {
+  size_t visit(const ObjectData& object) {
     write('{');
 
-    const VariantSlot* slot = object.head();
+    auto slotId = object.head();
+
+    while (slotId != NULL_SLOT) {
+      auto slot = resources_->getSlot(slotId);
 
-    while (slot != 0) {
       formatter_.writeString(slot->key());
       write(':');
       slot->data()->accept(*this);
 
-      slot = slot->next();
-      if (slot == 0)
-        break;
+      slotId = slot->next();
 
-      write(',');
+      if (slotId != NULL_SLOT)
+        write(',');
     }
 
     write('}');
     return bytesWritten();
   }
 
-  size_t visitFloat(JsonFloat value) {
+  size_t visit(JsonFloat value) {
     formatter_.writeFloat(value);
     return bytesWritten();
   }
 
-  size_t visitString(const char* value) {
+  size_t visit(const char* value) {
     formatter_.writeString(value);
     return bytesWritten();
   }
 
-  size_t visitString(const char* value, size_t n) {
-    formatter_.writeString(value, n);
+  size_t visit(JsonString value) {
+    formatter_.writeString(value.c_str(), value.size());
     return bytesWritten();
   }
 
-  size_t visitRawJson(const char* data, size_t n) {
-    formatter_.writeRaw(data, n);
+  size_t visit(RawString value) {
+    formatter_.writeRaw(value.data(), value.size());
     return bytesWritten();
   }
 
-  size_t visitSignedInteger(JsonInteger value) {
+  size_t visit(JsonInteger value) {
     formatter_.writeInteger(value);
     return bytesWritten();
   }
 
-  size_t visitUnsignedInteger(JsonUInt value) {
+  size_t visit(JsonUInt value) {
     formatter_.writeInteger(value);
     return bytesWritten();
   }
 
-  size_t visitBoolean(bool value) {
+  size_t visit(bool value) {
     formatter_.writeBoolean(value);
     return bytesWritten();
   }
 
-  size_t visitNull() {
+  size_t visit(nullptr_t) {
     formatter_.writeRaw("null");
     return bytesWritten();
   }
@@ -113,6 +116,9 @@ class JsonSerializer : public Visitor<size_t> {
 
  private:
   TextFormatter<TWriter> formatter_;
+
+ protected:
+  const ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
@@ -120,7 +126,7 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // Produces a minified JSON document.
-// https://arduinojson.org/v6/api/json/serializejson/
+// https://arduinojson.org/v7/api/json/serializejson/
 template <typename TDestination>
 size_t serializeJson(JsonVariantConst source, TDestination& destination) {
   using namespace detail;
@@ -128,7 +134,7 @@ size_t serializeJson(JsonVariantConst source, TDestination& destination) {
 }
 
 // Produces a minified JSON document.
-// https://arduinojson.org/v6/api/json/serializejson/
+// https://arduinojson.org/v7/api/json/serializejson/
 inline size_t serializeJson(JsonVariantConst source, void* buffer,
                             size_t bufferSize) {
   using namespace detail;
@@ -136,7 +142,7 @@ inline size_t serializeJson(JsonVariantConst source, void* buffer,
 }
 
 // Computes the length of the document that serializeJson() produces.
-// https://arduinojson.org/v6/api/json/measurejson/
+// https://arduinojson.org/v7/api/json/measurejson/
 inline size_t measureJson(JsonVariantConst source) {
   using namespace detail;
   return measure<JsonSerializer>(source);
@@ -144,8 +150,8 @@ inline size_t measureJson(JsonVariantConst source) {
 
 #if ARDUINOJSON_ENABLE_STD_STREAM
 template <typename T>
-inline typename detail::enable_if<
-    detail::is_convertible<T, JsonVariantConst>::value, std::ostream&>::type
+inline detail::enable_if_t<detail::is_convertible<T, JsonVariantConst>::value,
+                           std::ostream&>
 operator<<(std::ostream& os, const T& source) {
   serializeJson(source, os);
   return os;
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Latch.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Latch.hpp
index 08070eb3f78768fe0ce3df7b8cada2079c5623bd..f9d9e5104e8403cd4fb6c3a38a3f15df990f45f6 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Latch.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Latch.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp
index e8a92064239190170a739b9880e8eb3a99486cf0..ab049ad6f85969d0f794c1c03292afacc349533e 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -16,19 +16,20 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
   typedef JsonSerializer<TWriter> base;
 
  public:
-  PrettyJsonSerializer(TWriter writer) : base(writer), nesting_(0) {}
+  PrettyJsonSerializer(TWriter writer, const ResourceManager* resources)
+      : base(writer, resources), nesting_(0) {}
 
-  size_t visitArray(const CollectionData& array) {
-    const VariantSlot* slot = array.head();
-    if (slot) {
+  size_t visit(const ArrayData& array) {
+    auto it = array.createIterator(base::resources_);
+    if (!it.done()) {
       base::write("[\r\n");
       nesting_++;
-      while (slot != 0) {
+      while (!it.done()) {
         indent();
-        slot->data()->accept(*this);
+        it->accept(*this);
 
-        slot = slot->next();
-        base::write(slot ? ",\r\n" : "\r\n");
+        it.next(base::resources_);
+        base::write(it.done() ? "\r\n" : ",\r\n");
       }
       nesting_--;
       indent();
@@ -39,19 +40,19 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
     return this->bytesWritten();
   }
 
-  size_t visitObject(const CollectionData& object) {
-    const VariantSlot* slot = object.head();
-    if (slot) {
+  size_t visit(const ObjectData& object) {
+    auto it = object.createIterator(base::resources_);
+    if (!it.done()) {
       base::write("{\r\n");
       nesting_++;
-      while (slot != 0) {
+      while (!it.done()) {
         indent();
-        base::visitString(slot->key());
+        base::visit(it.key());
         base::write(": ");
-        slot->data()->accept(*this);
+        it->accept(*this);
 
-        slot = slot->next();
-        base::write(slot ? ",\r\n" : "\r\n");
+        it.next(base::resources_);
+        base::write(it.done() ? "\r\n" : ",\r\n");
       }
       nesting_--;
       indent();
@@ -62,6 +63,8 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
     return this->bytesWritten();
   }
 
+  using base::visit;
+
  private:
   void indent() {
     for (uint8_t i = 0; i < nesting_; i++)
@@ -76,7 +79,7 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // Produces JsonDocument to create a prettified JSON document.
-// https://arduinojson.org/v6/api/json/serializejsonpretty/
+// https://arduinojson.org/v7/api/json/serializejsonpretty/
 template <typename TDestination>
 size_t serializeJsonPretty(JsonVariantConst source, TDestination& destination) {
   using namespace ArduinoJson::detail;
@@ -84,7 +87,7 @@ size_t serializeJsonPretty(JsonVariantConst source, TDestination& destination) {
 }
 
 // Produces JsonDocument to create a prettified JSON document.
-// https://arduinojson.org/v6/api/json/serializejsonpretty/
+// https://arduinojson.org/v7/api/json/serializejsonpretty/
 inline size_t serializeJsonPretty(JsonVariantConst source, void* buffer,
                                   size_t bufferSize) {
   using namespace ArduinoJson::detail;
@@ -92,7 +95,7 @@ inline size_t serializeJsonPretty(JsonVariantConst source, void* buffer,
 }
 
 // Computes the length of the document that serializeJsonPretty() produces.
-// https://arduinojson.org/v6/api/json/measurejsonpretty/
+// https://arduinojson.org/v7/api/json/measurejsonpretty/
 inline size_t measureJsonPretty(JsonVariantConst source) {
   using namespace ArduinoJson::detail;
   return measure<PrettyJsonSerializer>(source);
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp
index f8eb2822c6c002a2dab9233b9a88965f8549a1b5..5b13222c4aca65f5e92bafe58553ec115af52fee 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -100,8 +100,8 @@ class TextFormatter {
   }
 
   template <typename T>
-  typename enable_if<is_signed<T>::value>::type writeInteger(T value) {
-    typedef typename make_unsigned<T>::type unsigned_type;
+  enable_if_t<is_signed<T>::value> writeInteger(T value) {
+    typedef make_unsigned_t<T> unsigned_type;
     unsigned_type unsigned_value;
     if (value < 0) {
       writeRaw('-');
@@ -113,7 +113,7 @@ class TextFormatter {
   }
 
   template <typename T>
-  typename enable_if<is_unsigned<T>::value>::type writeInteger(T value) {
+  enable_if_t<is_unsigned<T>::value> writeInteger(T value) {
     char buffer[22];
     char* end = buffer + sizeof(buffer);
     char* begin = end;
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp
index 6e314db2827a94e0660f1836030443d2aae7b70f..2111451aa171bd3547015a89d3d6df4266dc3b08 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp
index 1e33c186cd6906c9824e80ea97c069c99da23485..ad659b5a1d31812862c1991f556755f7cf5142fc 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp
index 61b8654e7f40000c76ce04a81af9447d243f9541..65d60695b77994719dbf183ffe1f4553a37c9b83 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/Allocator.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/Allocator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..aadc3686ca6d8380066ad937e00fa17251d60d3b
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/Allocator.hpp
@@ -0,0 +1,49 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, 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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp
deleted file mode 100644
index 754d58513acfa8a41044d33fce660afcd864a539..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp
+++ /dev/null
@@ -1,253 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Memory/Alignment.hpp>
-#include <ArduinoJson/Polyfills/assert.hpp>
-#include <ArduinoJson/Polyfills/mpl/max.hpp>
-#include <ArduinoJson/Strings/StringAdapters.hpp>
-#include <ArduinoJson/Variant/VariantSlot.hpp>
-
-#include <string.h>  // memmove
-
-#define JSON_STRING_SIZE(SIZE) (SIZE + 1)
-
-// Computes the size required to store an array in a JsonDocument.
-// https://arduinojson.org/v6/how-to/determine-the-capacity-of-the-jsondocument/
-#define JSON_ARRAY_SIZE(NUMBER_OF_ELEMENTS) \
-  ((NUMBER_OF_ELEMENTS) * sizeof(ArduinoJson::detail::VariantSlot))
-
-// Returns the size (in bytes) of an object with n elements.
-// Can be very handy to determine the size of a StaticMemoryPool.
-#define JSON_OBJECT_SIZE(NUMBER_OF_ELEMENTS) \
-  ((NUMBER_OF_ELEMENTS) * sizeof(ArduinoJson::detail::VariantSlot))
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-// begin_                                   end_
-// v                                           v
-// +-------------+--------------+--------------+
-// | strings...  |   (free)     |  ...variants |
-// +-------------+--------------+--------------+
-//               ^              ^
-//             left_          right_
-
-class MemoryPool {
- public:
-  MemoryPool(char* buf, size_t capa)
-      : begin_(buf),
-        left_(buf),
-        right_(buf ? buf + capa : 0),
-        end_(buf ? buf + capa : 0),
-        overflowed_(false) {
-    ARDUINOJSON_ASSERT(isAligned(begin_));
-    ARDUINOJSON_ASSERT(isAligned(right_));
-    ARDUINOJSON_ASSERT(isAligned(end_));
-  }
-
-  void* buffer() {
-    return begin_;  // NOLINT(clang-analyzer-unix.Malloc)
-                    // movePointers() alters this pointer
-  }
-
-  // Gets the capacity of the memoryPool in bytes
-  size_t capacity() const {
-    return size_t(end_ - begin_);
-  }
-
-  size_t size() const {
-    return size_t(left_ - begin_ + end_ - right_);
-  }
-
-  bool overflowed() const {
-    return overflowed_;
-  }
-
-  VariantSlot* allocVariant() {
-    return allocRight<VariantSlot>();
-  }
-
-  template <typename TAdaptedString>
-  const char* saveString(TAdaptedString str) {
-    if (str.isNull())
-      return 0;
-
-#if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
-    const char* existingCopy = findString(str);
-    if (existingCopy)
-      return existingCopy;
-#endif
-
-    size_t n = str.size();
-
-    char* newCopy = allocString(n + 1);
-    if (newCopy) {
-      stringGetChars(str, newCopy, n);
-      newCopy[n] = 0;  // force null-terminator
-    }
-    return newCopy;
-  }
-
-  void getFreeZone(char** zoneStart, size_t* zoneSize) const {
-    *zoneStart = left_;
-    *zoneSize = size_t(right_ - left_);
-  }
-
-  const char* saveStringFromFreeZone(size_t len) {
-#if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
-    const char* dup = findString(adaptString(left_, len));
-    if (dup)
-      return dup;
-#endif
-
-    const char* str = left_;
-    left_ += len;
-    *left_++ = 0;
-    checkInvariants();
-    return str;
-  }
-
-  void markAsOverflowed() {
-    overflowed_ = true;
-  }
-
-  void clear() {
-    left_ = begin_;
-    right_ = end_;
-    overflowed_ = false;
-  }
-
-  bool canAlloc(size_t bytes) const {
-    return left_ + bytes <= right_;
-  }
-
-  bool owns(void* p) const {
-    return begin_ <= p && p < end_;
-  }
-
-  // Workaround for missing placement new
-  void* operator new(size_t, void* p) {
-    return p;
-  }
-
-  // Squash the free space between strings and variants
-  //
-  // begin_                    end_
-  // v                            v
-  // +-------------+--------------+
-  // | strings...  |  ...variants |
-  // +-------------+--------------+
-  //               ^
-  //          left_ right_
-  //
-  // This funcion is called before a realloc.
-  ptrdiff_t squash() {
-    char* new_right = addPadding(left_);
-    if (new_right >= right_)
-      return 0;
-
-    size_t right_size = static_cast<size_t>(end_ - right_);
-    memmove(new_right, right_, right_size);
-
-    ptrdiff_t bytes_reclaimed = right_ - new_right;
-    right_ = new_right;
-    end_ = new_right + right_size;
-    return bytes_reclaimed;
-  }
-
-  // Move all pointers together
-  // This funcion is called after a realloc.
-  void movePointers(ptrdiff_t offset) {
-    begin_ += offset;
-    left_ += offset;
-    right_ += offset;
-    end_ += offset;
-  }
-
- private:
-  void checkInvariants() {
-    ARDUINOJSON_ASSERT(begin_ <= left_);
-    ARDUINOJSON_ASSERT(left_ <= right_);
-    ARDUINOJSON_ASSERT(right_ <= end_);
-    ARDUINOJSON_ASSERT(isAligned(right_));
-  }
-
-#if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
-  template <typename TAdaptedString>
-  const char* findString(const TAdaptedString& str) const {
-    size_t n = str.size();
-    for (char* next = begin_; next + n < left_; ++next) {
-      if (next[n] == '\0' && stringEquals(str, adaptString(next, n)))
-        return next;
-
-      // jump to next terminator
-      while (*next)
-        ++next;
-    }
-    return 0;
-  }
-#endif
-
-  char* allocString(size_t n) {
-    if (!canAlloc(n)) {
-      overflowed_ = true;
-      return 0;
-    }
-    char* s = left_;
-    left_ += n;
-    checkInvariants();
-    return s;
-  }
-
-  template <typename T>
-  T* allocRight() {
-    return reinterpret_cast<T*>(allocRight(sizeof(T)));
-  }
-
-  void* allocRight(size_t bytes) {
-    if (!canAlloc(bytes)) {
-      overflowed_ = true;
-      return 0;
-    }
-    right_ -= bytes;
-    return right_;
-  }
-
-  char *begin_, *left_, *right_, *end_;
-  bool overflowed_;
-};
-
-template <typename TAdaptedString, typename TCallback>
-bool storeString(MemoryPool* pool, TAdaptedString str,
-                 StringStoragePolicy::Copy, TCallback callback) {
-  const char* copy = pool->saveString(str);
-  JsonString storedString(copy, str.size(), JsonString::Copied);
-  callback(storedString);
-  return copy != 0;
-}
-
-template <typename TAdaptedString, typename TCallback>
-bool storeString(MemoryPool*, TAdaptedString str, StringStoragePolicy::Link,
-                 TCallback callback) {
-  JsonString storedString(str.data(), str.size(), JsonString::Linked);
-  callback(storedString);
-  return !str.isNull();
-}
-
-template <typename TAdaptedString, typename TCallback>
-bool storeString(MemoryPool* pool, TAdaptedString str,
-                 StringStoragePolicy::LinkOrCopy policy, TCallback callback) {
-  if (policy.link)
-    return storeString(pool, str, StringStoragePolicy::Link(), callback);
-  else
-    return storeString(pool, str, StringStoragePolicy::Copy(), callback);
-}
-
-template <typename TAdaptedString, typename TCallback>
-bool storeString(MemoryPool* pool, TAdaptedString str, TCallback callback) {
-  return storeString(pool, str, str.storagePolicy(), callback);
-}
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/ResourceManager.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/ResourceManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2270e4f652c4d5c3460a339540ebbb2aed627cb1
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/ResourceManager.hpp
@@ -0,0 +1,125 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Memory/Allocator.hpp>
+#include <ArduinoJson/Memory/StringPool.hpp>
+#include <ArduinoJson/Memory/VariantPoolList.hpp>
+#include <ArduinoJson/Polyfills/assert.hpp>
+#include <ArduinoJson/Polyfills/utility.hpp>
+#include <ArduinoJson/Strings/StringAdapters.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+class VariantSlot;
+class VariantPool;
+
+class ResourceManager {
+ public:
+  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 VariantPool::slotsToBytes(variantPools_.usage()) +
+           stringPool_.size();
+  }
+
+  bool overflowed() const {
+    return overflowed_;
+  }
+
+  SlotWithId allocSlot() {
+    auto p = variantPools_.allocSlot(allocator_);
+    if (!p)
+      overflowed_ = true;
+    return p;
+  }
+
+  void freeSlot(SlotWithId slot);
+
+  VariantSlot* getSlot(SlotId id) const {
+    return variantPools_.getSlot(id);
+  }
+
+  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_;
+  VariantPoolList variantPools_;
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/ResourceManagerImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/ResourceManagerImpl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..136dc2dd105a4fc397a66721b750ab6310230040
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/ResourceManagerImpl.hpp
@@ -0,0 +1,20 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Collection/CollectionData.hpp>
+#include <ArduinoJson/Memory/ResourceManager.hpp>
+#include <ArduinoJson/Variant/VariantData.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+inline void ResourceManager::freeSlot(SlotWithId slot) {
+  if (slot->ownsKey())
+    dereferenceString(slot->key());
+  slot->data()->setNull(this);
+  variantPools_.freeSlot(slot);
+}
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringBuffer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringBuffer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d541239331fa1da0ca4e903b108334ef2058ee81
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringBuffer.hpp
@@ -0,0 +1,67 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, 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;
+  }
+
+  StringNode* save() {
+    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;
+  }
+
+  JsonString str() const {
+    ARDUINOJSON_ASSERT(node_ != nullptr);
+
+    return JsonString(node_->data, node_->length, JsonString::Copied);
+  }
+
+ private:
+  ResourceManager* resources_;
+  StringNode* node_ = nullptr;
+  size_t size_ = 0;
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringBuilder.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringBuilder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f3a842aca3b5ee841591dbd95c3e6aca4b7d1b73
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringBuilder.hpp
@@ -0,0 +1,80 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, 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);
+  }
+
+  StringNode* save() {
+    ARDUINOJSON_ASSERT(node_ != nullptr);
+    node_->data[size_] = 0;
+    StringNode* node = resources_->getString(adaptString(node_->data, 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++;
+    }
+    return 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_, JsonString::Copied);
+  }
+
+ private:
+  ResourceManager* resources_;
+  StringNode* node_ = nullptr;
+  size_t size_ = 0;
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringNode.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringNode.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f60cc2e640c9cdd754269aaa2c269c82dd4d6fb2
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringNode.hpp
@@ -0,0 +1,75 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, 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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringPool.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringPool.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..dcdc98b6de16804219b5d9fe197d1c790559e165
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/StringPool.hpp
@@ -0,0 +1,105 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, 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 VariantSlot;
+class VariantPool;
+
+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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPool.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPool.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5fe37bc7df2b80d05a23b9fd52e5488e113ff16e
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPool.hpp
@@ -0,0 +1,63 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Memory/ResourceManager.hpp>
+#include <ArduinoJson/Polyfills/assert.hpp>
+#include <ArduinoJson/Polyfills/integer.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+class VariantSlot;
+using SlotId = uint_t<ARDUINOJSON_SLOT_ID_SIZE * 8>;
+using SlotCount = SlotId;
+const SlotId NULL_SLOT = SlotId(-1);
+
+class SlotWithId {
+ public:
+  SlotWithId() : slot_(nullptr), id_(NULL_SLOT) {}
+  SlotWithId(VariantSlot* slot, SlotId id) : slot_(slot), id_(id) {
+    ARDUINOJSON_ASSERT((slot == nullptr) == (id == NULL_SLOT));
+  }
+
+  SlotId id() const {
+    return id_;
+  }
+
+  operator VariantSlot*() {
+    return slot_;
+  }
+
+  VariantSlot* operator->() {
+    ARDUINOJSON_ASSERT(slot_ != nullptr);
+    return slot_;
+  }
+
+ private:
+  VariantSlot* slot_;
+  SlotId id_;
+};
+
+class VariantPool {
+ public:
+  void create(SlotCount cap, Allocator* allocator);
+  void destroy(Allocator* allocator);
+
+  SlotWithId allocSlot();
+  VariantSlot* getSlot(SlotId id) const;
+  void clear();
+  void shrinkToFit(Allocator*);
+  SlotCount usage() const;
+
+  static SlotCount bytesToSlots(size_t);
+  static size_t slotsToBytes(SlotCount);
+
+ private:
+  SlotCount capacity_;
+  SlotCount usage_;
+  VariantSlot* slots_;
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPoolImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPoolImpl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d53be32ddef7189a86228e242788ed0f5dbb874c
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPoolImpl.hpp
@@ -0,0 +1,81 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Memory/VariantPool.hpp>
+#include <ArduinoJson/Variant/VariantSlot.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+inline void VariantPool::create(SlotCount cap, Allocator* allocator) {
+  ARDUINOJSON_ASSERT(cap > 0);
+  slots_ =
+      reinterpret_cast<VariantSlot*>(allocator->allocate(slotsToBytes(cap)));
+  capacity_ = slots_ ? cap : 0;
+  usage_ = 0;
+}
+
+inline void VariantPool::destroy(Allocator* allocator) {
+  if (slots_)
+    allocator->deallocate(slots_);
+  slots_ = nullptr;
+  capacity_ = 0;
+  usage_ = 0;
+}
+
+inline void VariantPool::shrinkToFit(Allocator* allocator) {
+  auto newSlots = reinterpret_cast<VariantSlot*>(
+      allocator->reallocate(slots_, slotsToBytes(usage_)));
+  if (newSlots) {
+    slots_ = newSlots;
+    capacity_ = usage_;
+  }
+}
+
+inline SlotWithId VariantPool::allocSlot() {
+  if (!slots_)
+    return {};
+  if (usage_ >= capacity_)
+    return {};
+  auto index = usage_++;
+  auto slot = &slots_[index];
+  return {new (slot) VariantSlot, SlotId(index)};
+}
+
+inline VariantSlot* VariantPool::getSlot(SlotId id) const {
+  ARDUINOJSON_ASSERT(id < usage_);
+  return &slots_[id];
+}
+
+inline SlotCount VariantPool::usage() const {
+  return usage_;
+}
+
+inline void VariantPool::clear() {
+  usage_ = 0;
+}
+
+inline SlotCount VariantPool::bytesToSlots(size_t n) {
+  return static_cast<SlotCount>(n / sizeof(VariantSlot));
+}
+
+inline size_t VariantPool::slotsToBytes(SlotCount n) {
+  return n * sizeof(VariantSlot);
+}
+
+inline SlotWithId VariantPoolList::allocFromFreeList() {
+  ARDUINOJSON_ASSERT(freeList_ != NULL_SLOT);
+  auto id = freeList_;
+  auto slot = getSlot(freeList_);
+  freeList_ = slot->next();
+  return {new (slot) VariantSlot, id};
+}
+
+inline void VariantPoolList::freeSlot(SlotWithId slot) {
+  slot->setNext(freeList_);
+  freeList_ = slot.id();
+}
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPoolList.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPoolList.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8ee5cef68a97c3c6f70cc9637f5365c6b8e89eb2
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Memory/VariantPoolList.hpp
@@ -0,0 +1,189 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Memory/VariantPool.hpp>
+#include <ArduinoJson/Polyfills/assert.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+using PoolCount = SlotId;
+
+class VariantPoolList {
+ public:
+  VariantPoolList() = default;
+
+  ~VariantPoolList() {
+    ARDUINOJSON_ASSERT(count_ == 0);
+  }
+
+  friend void swap(VariantPoolList& a, VariantPoolList& 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_);
+  }
+
+  VariantPoolList& operator=(VariantPoolList&& 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;
+  }
+
+  SlotWithId 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(SlotWithId slot);
+
+  VariantSlot* 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;
+  }
+
+  void shrinkToFit(Allocator* allocator) {
+    if (count_ > 0)
+      pools_[count_ - 1].shrinkToFit(allocator);
+    if (pools_ != preallocatedPools_ && count_ != capacity_) {
+      pools_ = static_cast<VariantPool*>(
+          allocator->reallocate(pools_, count_ * sizeof(VariantPool)));
+      ARDUINOJSON_ASSERT(pools_ != nullptr);  // realloc to smaller can't fail
+      capacity_ = count_;
+    }
+  }
+
+ private:
+  SlotWithId allocFromFreeList();
+
+  SlotWithId allocFromLastPool() {
+    ARDUINOJSON_ASSERT(count_ > 0);
+    auto poolIndex = SlotId(count_ - 1);
+    auto slot = pools_[poolIndex].allocSlot();
+    if (!slot)
+      return {};
+    return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())};
+  }
+
+  VariantPool* 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(VariantPool));
+      if (!newPools)
+        return false;
+      memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_));
+    } else {
+      newPools =
+          allocator->reallocate(pools_, newCapacity * sizeof(VariantPool));
+      if (!newPools)
+        return false;
+    }
+
+    pools_ = static_cast<VariantPool*>(newPools);
+    capacity_ = newCapacity;
+    return true;
+  }
+
+  VariantPool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT];
+  VariantPool* 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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp
index ae9abd3c3773fd1386e7ff0d51c009d0d09de70d..d3cb3d34e742da469bf5c99eea720df450910553 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -51,6 +51,8 @@ class SerializedValue<TChar*> {
   size_t size_;
 };
 
+using RawString = SerializedValue<const char*>;
+
 template <typename T>
 inline SerializedValue<T> serialized(T str) {
   return SerializedValue<T>(str);
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackBinary.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackBinary.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bd275d441d2197d83d565c514c038c3004325c15
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackBinary.hpp
@@ -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);
+    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;
+      }
+    }
+    data->setNull();
+  }
+
+  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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp
index 41934295bc921f2eda04b79a1d1ff01a514b4855..0c80ddee87101618b802d18da4a9ece945d6fedd 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp
@@ -1,26 +1,26 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Deserialization/deserialize.hpp>
-#include <ArduinoJson/Memory/MemoryPool.hpp>
-#include <ArduinoJson/MsgPack/endianess.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, typename TStringStorage>
+template <typename TReader>
 class MsgPackDeserializer {
  public:
-  MsgPackDeserializer(MemoryPool* pool, TReader reader,
-                      TStringStorage stringStorage)
-      : pool_(pool),
+  MsgPackDeserializer(ResourceManager* resources, TReader reader)
+      : resources_(resources),
         reader_(reader),
-        stringStorage_(stringStorage),
+        stringBuffer_(resources),
         foundSomething_(false) {}
 
   template <typename TFilter>
@@ -38,11 +38,13 @@ class MsgPackDeserializer {
       DeserializationOption::NestingLimit nestingLimit) {
     DeserializationError::Code err;
 
-    uint8_t code = 0;  // TODO: why do we need to initialize this variable?
-    err = readByte(code);
+    uint8_t header[5];
+    err = readBytes(header, 1);
     if (err)
       return err;
 
+    const uint8_t& code = header[0];
+
     foundSomething_ = true;
 
     bool allowValue = filter.allowValue();
@@ -52,6 +54,14 @@ class MsgPackDeserializer {
       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
@@ -61,33 +71,11 @@ class MsgPackDeserializer {
         return DeserializationError::InvalidInput;
 
       case 0xc2:
-        if (allowValue)
-          variant->setBoolean(false);
-        return DeserializationError::Ok;
-
       case 0xc3:
         if (allowValue)
-          variant->setBoolean(true);
+          variant->setBoolean(code == 0xc3);
         return DeserializationError::Ok;
 
-      case 0xc4:  // bin 8 (not supported)
-        return skipString<uint8_t>();
-
-      case 0xc5:  // bin 16 (not supported)
-        return skipString<uint16_t>();
-
-      case 0xc6:  // bin 32 (not supported)
-        return skipString<uint32_t>();
-
-      case 0xc7:  // ext 8 (not supported)
-        return skipExt<uint8_t>();
-
-      case 0xc8:  // ext 16 (not supported)
-        return skipExt<uint16_t>();
-
-      case 0xc9:  // ext 32 (not supported)
-        return skipExt<uint32_t>();
-
       case 0xca:
         if (allowValue)
           return readFloat<float>(variant);
@@ -99,128 +87,97 @@ class MsgPackDeserializer {
           return readDouble<double>(variant);
         else
           return skipBytes(8);
+    }
 
-      case 0xcc:
-        if (allowValue)
-          return readInteger<uint8_t>(variant);
-        else
-          return skipBytes(1);
-
-      case 0xcd:
-        if (allowValue)
-          return readInteger<uint16_t>(variant);
-        else
-          return skipBytes(2);
-
-      case 0xce:
-        if (allowValue)
-          return readInteger<uint32_t>(variant);
-        else
-          return skipBytes(4);
-
-      case 0xcf:
-#if ARDUINOJSON_USE_LONG_LONG
-        if (allowValue)
-          return readInteger<uint64_t>(variant);
-        else
-          return skipBytes(8);
-#else
-        return skipBytes(8);  // not supported
-#endif
-
-      case 0xd0:
-        if (allowValue)
-          return readInteger<int8_t>(variant);
-        else
-          return skipBytes(1);
-
-      case 0xd1:
-        if (allowValue)
-          return readInteger<int16_t>(variant);
-        else
-          return skipBytes(2);
-
-      case 0xd2:
-        if (allowValue)
-          return readInteger<int32_t>(variant);
-        else
-          return skipBytes(4);
-
-      case 0xd3:
-#if ARDUINOJSON_USE_LONG_LONG
-        if (allowValue)
-          return readInteger<int64_t>(variant);
-        else
-          return skipBytes(8);  // not supported
-#else
-        return skipBytes(8);
-#endif
-
-      case 0xd4:  // fixext 1 (not supported)
-        return skipBytes(2);
-
-      case 0xd5:  // fixext 2 (not supported)
-        return skipBytes(3);
-
-      case 0xd6:  // fixext 4 (not supported)
-        return skipBytes(5);
-
-      case 0xd7:  // fixext 8 (not supported)
-        return skipBytes(9);
+    if (code <= 0x7f || code >= 0xe0) {  // fixint
+      if (allowValue)
+        variant->setInteger(static_cast<int8_t>(code));
+      return DeserializationError::Ok;
+    }
 
-      case 0xd8:  // fixext 16 (not supported)
-        return skipBytes(17);
+    uint8_t sizeBytes = 0;
+    size_t size = 0;
+    bool isExtension = code >= 0xc7 && code <= 0xc9;
 
-      case 0xd9:
-        if (allowValue)
-          return readString<uint8_t>(variant);
-        else
-          return skipString<uint8_t>();
+    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;
+    }
 
-      case 0xda:
-        if (allowValue)
-          return readString<uint16_t>(variant);
-        else
-          return skipString<uint16_t>();
+    if (code >= 0xd4 && code <= 0xd8) {  // fixext
+      size = size_t(1) << (code - 0xd4);
+      isExtension = true;
+    }
 
-      case 0xdb:
-        if (allowValue)
-          return readString<uint32_t>(variant);
-        else
-          return skipString<uint32_t>();
+    switch (code & 0xf0) {
+      case 0x90:  // fixarray
+      case 0x80:  // fixmap
+        size = code & 0x0F;
+        break;
+    }
 
-      case 0xdc:
-        return readArray<uint16_t>(variant, filter, nestingLimit);
+    switch (code & 0xe0) {
+      case 0xa0:  // fixstr
+        size = code & 0x1f;
+        break;
+    }
 
-      case 0xdd:
-        return readArray<uint32_t>(variant, filter, nestingLimit);
+    if (sizeBytes) {
+      err = readBytes(header + 1, sizeBytes);
+      if (err)
+        return err;
 
-      case 0xde:
-        return readObject<uint16_t>(variant, filter, nestingLimit);
+      uint32_t size32 = 0;
+      for (uint8_t i = 0; i < sizeBytes; i++)
+        size32 = (size32 << 8) | header[i + 1];
 
-      case 0xdf:
-        return readObject<uint32_t>(variant, filter, nestingLimit);
+      size = size_t(size32);
+      if (size < size32)                        // integer overflow
+        return DeserializationError::NoMemory;  // (not testable on 32/64-bit)
     }
 
-    switch (code & 0xf0) {
-      case 0x80:
-        return readObject(variant, code & 0x0F, filter, nestingLimit);
+    // array 16, 32 and fixarray
+    if (code == 0xdc || code == 0xdd || (code & 0xf0) == 0x90)
+      return readArray(variant, size, filter, nestingLimit);
 
-      case 0x90:
-        return readArray(variant, code & 0x0F, filter, nestingLimit);
-    }
+    // map 16, 32 and fixmap
+    if (code == 0xde || code == 0xdf || (code & 0xf0) == 0x80)
+      return readObject(variant, size, filter, nestingLimit);
 
-    if ((code & 0xe0) == 0xa0) {
+    // str 8, 16, 32 and fixstr
+    if (code == 0xd9 || code == 0xda || code == 0xdb || (code & 0xe0) == 0xa0) {
       if (allowValue)
-        return readString(variant, code & 0x1f);
+        return readString(variant, size);
       else
-        return skipBytes(code & 0x1f);
+        return skipBytes(size);
     }
 
-    if (allowValue)
-      variant->setInteger(static_cast<int8_t>(code));
+    if (isExtension)
+      size++;  // to include the type
 
-    return DeserializationError::Ok;
+    if (allowValue)
+      return readRawString(variant, header, uint8_t(1 + sizeBytes), size);
+    else
+      return skipBytes(size);
   }
 
   DeserializationError::Code readByte(uint8_t& value) {
@@ -231,7 +188,7 @@ class MsgPackDeserializer {
     return DeserializationError::Ok;
   }
 
-  DeserializationError::Code readBytes(uint8_t* p, size_t n) {
+  DeserializationError::Code readBytes(void* p, size_t n) {
     if (reader_.readBytes(reinterpret_cast<char*>(p), n) == n)
       return DeserializationError::Ok;
     return DeserializationError::IncompleteInput;
@@ -239,7 +196,7 @@ class MsgPackDeserializer {
 
   template <typename T>
   DeserializationError::Code readBytes(T& value) {
-    return readBytes(reinterpret_cast<uint8_t*>(&value), sizeof(value));
+    return readBytes(&value, sizeof(value));
   }
 
   DeserializationError::Code skipBytes(size_t n) {
@@ -250,36 +207,45 @@ class MsgPackDeserializer {
     return DeserializationError::Ok;
   }
 
-  template <typename T>
-  DeserializationError::Code readInteger(T& value) {
-    DeserializationError::Code err;
+  DeserializationError::Code readInteger(VariantData* variant, uint8_t width,
+                                         bool isSigned) {
+    uint8_t buffer[8];
 
-    err = readBytes(value);
+    auto err = readBytes(buffer, width);
     if (err)
       return err;
 
-    fixEndianess(value);
-
-    return DeserializationError::Ok;
-  }
+    union {
+      int64_t signedValue;
+      uint64_t unsignedValue;
+    };
 
-  template <typename T>
-  DeserializationError::Code readInteger(VariantData* variant) {
-    DeserializationError::Code err;
-    T value;
+    if (isSigned)
+      signedValue = static_cast<int8_t>(buffer[0]);  // propagate sign bit
+    else
+      unsignedValue = static_cast<uint8_t>(buffer[0]);
 
-    err = readInteger(value);
-    if (err)
-      return err;
+    for (uint8_t i = 1; i < width; i++)
+      unsignedValue = (unsignedValue << 8) | buffer[i];
 
-    variant->setInteger(value);
+    if (isSigned) {
+      auto truncatedValue = static_cast<JsonInteger>(signedValue);
+      if (truncatedValue == signedValue)
+        variant->setInteger(truncatedValue);
+      // else set null on overflow
+    } else {
+      auto truncatedValue = static_cast<JsonUInt>(unsignedValue);
+      if (truncatedValue == unsignedValue)
+        variant->setInteger(truncatedValue);
+      // else set null on overflow
+    }
 
     return DeserializationError::Ok;
   }
 
   template <typename T>
-  typename enable_if<sizeof(T) == 4, DeserializationError::Code>::type
-  readFloat(VariantData* variant) {
+  enable_if_t<sizeof(T) == 4, DeserializationError::Code> readFloat(
+      VariantData* variant) {
     DeserializationError::Code err;
     T value;
 
@@ -287,15 +253,15 @@ class MsgPackDeserializer {
     if (err)
       return err;
 
-    fixEndianess(value);
+    fixEndianness(value);
     variant->setFloat(value);
 
     return DeserializationError::Ok;
   }
 
   template <typename T>
-  typename enable_if<sizeof(T) == 8, DeserializationError::Code>::type
-  readDouble(VariantData* variant) {
+  enable_if_t<sizeof(T) == 8, DeserializationError::Code> readDouble(
+      VariantData* variant) {
     DeserializationError::Code err;
     T value;
 
@@ -303,15 +269,15 @@ class MsgPackDeserializer {
     if (err)
       return err;
 
-    fixEndianess(value);
+    fixEndianness(value);
     variant->setFloat(value);
 
     return DeserializationError::Ok;
   }
 
   template <typename T>
-  typename enable_if<sizeof(T) == 4, DeserializationError::Code>::type
-  readDouble(VariantData* variant) {
+  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
@@ -322,48 +288,12 @@ class MsgPackDeserializer {
       return err;
 
     doubleToFloat(i, o);
-    fixEndianess(value);
+    fixEndianness(value);
     variant->setFloat(value);
 
     return DeserializationError::Ok;
   }
 
-  template <typename T>
-  DeserializationError::Code readString(VariantData* variant) {
-    DeserializationError::Code err;
-    T size;
-
-    err = readInteger(size);
-    if (err)
-      return err;
-
-    return readString(variant, size);
-  }
-
-  template <typename T>
-  DeserializationError::Code readString() {
-    DeserializationError::Code err;
-    T size;
-
-    err = readInteger(size);
-    if (err)
-      return err;
-
-    return readString(size);
-  }
-
-  template <typename T>
-  DeserializationError::Code skipString() {
-    DeserializationError::Code err;
-    T size;
-
-    err = readInteger(size);
-    if (err)
-      return err;
-
-    return skipBytes(size);
-  }
-
   DeserializationError::Code readString(VariantData* variant, size_t n) {
     DeserializationError::Code err;
 
@@ -371,42 +301,37 @@ class MsgPackDeserializer {
     if (err)
       return err;
 
-    variant->setString(stringStorage_.save());
+    variant->setOwnedString(stringBuffer_.save());
     return DeserializationError::Ok;
   }
 
   DeserializationError::Code readString(size_t n) {
-    DeserializationError::Code err;
-
-    stringStorage_.startString();
-    for (; n; --n) {
-      uint8_t c;
+    char* p = stringBuffer_.reserve(n);
+    if (!p)
+      return DeserializationError::NoMemory;
 
-      err = readBytes(c);
-      if (err)
-        return err;
+    return readBytes(p, n);
+  }
 
-      stringStorage_.append(static_cast<char>(c));
-    }
+  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)
 
-    if (!stringStorage_.isValid())
+    char* p = stringBuffer_.reserve(totalSize);
+    if (!p)
       return DeserializationError::NoMemory;
 
-    return DeserializationError::Ok;
-  }
-
-  template <typename TSize, typename TFilter>
-  DeserializationError::Code readArray(
-      VariantData* variant, TFilter filter,
-      DeserializationOption::NestingLimit nestingLimit) {
-    DeserializationError::Code err;
-    TSize size;
+    memcpy(p, header, headerSize);
 
-    err = readInteger(size);
+    auto err = readBytes(p + headerSize, n);
     if (err)
       return err;
 
-    return readArray(variant, size, filter, nestingLimit);
+    variant->setRawString(stringBuffer_.save());
+    return DeserializationError::Ok;
   }
 
   template <typename TFilter>
@@ -420,7 +345,7 @@ class MsgPackDeserializer {
 
     bool allowArray = filter.allowArray();
 
-    CollectionData* array;
+    ArrayData* array;
     if (allowArray) {
       ARDUINOJSON_ASSERT(variant != 0);
       array = &variant->toArray();
@@ -428,21 +353,21 @@ class MsgPackDeserializer {
       array = 0;
     }
 
-    TFilter memberFilter = filter[0U];
+    TFilter elementFilter = filter[0U];
 
     for (; n; --n) {
       VariantData* value;
 
-      if (memberFilter.allow()) {
+      if (elementFilter.allow()) {
         ARDUINOJSON_ASSERT(array != 0);
-        value = array->addElement(pool_);
+        value = array->addElement(resources_);
         if (!value)
           return DeserializationError::NoMemory;
       } else {
         value = 0;
       }
 
-      err = parseVariant(value, memberFilter, nestingLimit.decrement());
+      err = parseVariant(value, elementFilter, nestingLimit.decrement());
       if (err)
         return err;
     }
@@ -450,20 +375,6 @@ class MsgPackDeserializer {
     return DeserializationError::Ok;
   }
 
-  template <typename TSize, typename TFilter>
-  DeserializationError::Code readObject(
-      VariantData* variant, TFilter filter,
-      DeserializationOption::NestingLimit nestingLimit) {
-    DeserializationError::Code err;
-    TSize size;
-
-    err = readInteger(size);
-    if (err)
-      return err;
-
-    return readObject(variant, size, filter, nestingLimit);
-  }
-
   template <typename TFilter>
   DeserializationError::Code readObject(
       VariantData* variant, size_t n, TFilter filter,
@@ -473,7 +384,7 @@ class MsgPackDeserializer {
     if (nestingLimit.reached())
       return DeserializationError::TooDeep;
 
-    CollectionData* object;
+    ObjectData* object;
     if (filter.allowObject()) {
       ARDUINOJSON_ASSERT(variant != 0);
       object = &variant->toObject();
@@ -486,7 +397,7 @@ class MsgPackDeserializer {
       if (err)
         return err;
 
-      JsonString key = stringStorage_.str();
+      JsonString key = stringBuffer_.str();
       TFilter memberFilter = filter[key.c_str()];
       VariantData* member;
 
@@ -494,16 +405,11 @@ class MsgPackDeserializer {
         ARDUINOJSON_ASSERT(object != 0);
 
         // Save key in memory pool.
-        // This MUST be done before adding the slot.
-        key = stringStorage_.save();
+        auto savedKey = stringBuffer_.save();
 
-        VariantSlot* slot = object->addSlot(pool_);
-        if (!slot)
+        member = object->addMember(savedKey, resources_);
+        if (!member)
           return DeserializationError::NoMemory;
-
-        slot->setKey(key);
-
-        member = slot->data();
       } else {
         member = 0;
       }
@@ -527,36 +433,24 @@ class MsgPackDeserializer {
     if ((code & 0xe0) == 0xa0)
       return readString(code & 0x1f);
 
-    switch (code) {
-      case 0xd9:
-        return readString<uint8_t>();
-
-      case 0xda:
-        return readString<uint16_t>();
-
-      case 0xdb:
-        return readString<uint32_t>();
-
-      default:
-        return DeserializationError::InvalidInput;
+    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);
     }
-  }
-
-  template <typename T>
-  DeserializationError::Code skipExt() {
-    DeserializationError::Code err;
-    T size;
 
-    err = readInteger(size);
-    if (err)
-      return err;
-
-    return skipBytes(size + 1U);
+    return DeserializationError::InvalidInput;
   }
 
-  MemoryPool* pool_;
+  ResourceManager* resources_;
   TReader reader_;
-  TStringStorage stringStorage_;
+  StringBuffer stringBuffer_;
   bool foundSomething_;
 };
 
@@ -565,20 +459,25 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // Parses a MessagePack input and puts the result in a JsonDocument.
-// https://arduinojson.org/v6/api/msgpack/deserializemsgpack/
-template <typename... Args>
-DeserializationError deserializeMsgPack(JsonDocument& doc, Args&&... args) {
+// https://arduinojson.org/v7/api/msgpack/deserializemsgpack/
+template <typename TDestination, typename... Args>
+detail::enable_if_t<detail::is_deserialize_destination<TDestination>::value,
+                    DeserializationError>
+deserializeMsgPack(TDestination&& dst, Args&&... args) {
   using namespace detail;
-  return deserialize<MsgPackDeserializer>(doc, detail::forward<Args>(args)...);
+  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/v6/api/msgpack/deserializemsgpack/
-template <typename TChar, typename... Args>
-DeserializationError deserializeMsgPack(JsonDocument& doc, TChar* input,
-                                        Args&&... args) {
+// https://arduinojson.org/v7/api/msgpack/deserializemsgpack/
+template <typename TDestination, typename TChar, typename... Args>
+detail::enable_if_t<detail::is_deserialize_destination<TDestination>::value,
+                    DeserializationError>
+deserializeMsgPack(TDestination&& dst, TChar* input, Args&&... args) {
   using namespace detail;
-  return deserialize<MsgPackDeserializer>(doc, input,
+  return deserialize<MsgPackDeserializer>(detail::forward<TDestination>(dst),
+                                          input,
                                           detail::forward<Args>(args)...);
 }
 
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackExtension.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackExtension.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5be4e5038ca3593a88d8c250f03e86d54a4f1b03
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackExtension.hpp
@@ -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);
+    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;
+      }
+    }
+    data->setNull();
+  }
+
+  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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp
index 6125330c240fde9895a4d07f32a64cfc0d809051..ce5f66a2a90b16f68350fe6dc4e7b31db20c413c 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp
@@ -1,10 +1,10 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
-#include <ArduinoJson/MsgPack/endianess.hpp>
+#include <ArduinoJson/MsgPack/endianness.hpp>
 #include <ArduinoJson/Polyfills/assert.hpp>
 #include <ArduinoJson/Polyfills/type_traits.hpp>
 #include <ArduinoJson/Serialization/CountingDecorator.hpp>
@@ -15,18 +15,20 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TWriter>
-class MsgPackSerializer : public Visitor<size_t> {
+class MsgPackSerializer : public VariantDataVisitor<size_t> {
  public:
   static const bool producesText = false;
 
-  MsgPackSerializer(TWriter writer) : writer_(writer) {}
+  MsgPackSerializer(TWriter writer, const ResourceManager* resources)
+      : writer_(writer), resources_(resources) {}
 
   template <typename T>
-  typename enable_if<sizeof(T) == 4, size_t>::type visitFloat(T value32) {
+  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 visitSignedInteger(truncatedValue);
+        return visit(truncatedValue);
     }
     writeByte(0xCA);
     writeInteger(value32);
@@ -35,17 +37,18 @@ class MsgPackSerializer : public Visitor<size_t> {
 
   template <typename T>
   ARDUINOJSON_NO_SANITIZE("float-cast-overflow")
-  typename enable_if<sizeof(T) == 8, size_t>::type visitFloat(T value64) {
+  enable_if_t<is_floating_point<T>::value && sizeof(T) == 8, size_t> visit(
+      T value64) {
     float value32 = float(value64);
     if (value32 == value64)
-      return visitFloat(value32);
+      return visit(value32);
     writeByte(0xCB);
     writeInteger(value64);
     return bytesWritten();
   }
 
-  size_t visitArray(const CollectionData& array) {
-    size_t n = array.size();
+  size_t visit(const ArrayData& array) {
+    size_t n = array.size(resources_);
     if (n < 0x10) {
       writeByte(uint8_t(0x90 + n));
     } else if (n < 0x10000) {
@@ -55,14 +58,19 @@ class MsgPackSerializer : public Visitor<size_t> {
       writeByte(0xDD);
       writeInteger(uint32_t(n));
     }
-    for (const VariantSlot* slot = array.head(); slot; slot = slot->next()) {
+
+    auto slotId = array.head();
+    while (slotId != NULL_SLOT) {
+      auto slot = resources_->getSlot(slotId);
       slot->data()->accept(*this);
+      slotId = slot->next();
     }
+
     return bytesWritten();
   }
 
-  size_t visitObject(const CollectionData& object) {
-    size_t n = object.size();
+  size_t visit(const ObjectData& object) {
+    size_t n = object.size(resources_);
     if (n < 0x10) {
       writeByte(uint8_t(0x80 + n));
     } else if (n < 0x10000) {
@@ -72,20 +80,27 @@ class MsgPackSerializer : public Visitor<size_t> {
       writeByte(0xDF);
       writeInteger(uint32_t(n));
     }
-    for (const VariantSlot* slot = object.head(); slot; slot = slot->next()) {
-      visitString(slot->key());
+
+    auto slotId = object.head();
+    while (slotId != NULL_SLOT) {
+      auto slot = resources_->getSlot(slotId);
+      visit(slot->key());
       slot->data()->accept(*this);
+      slotId = slot->next();
     }
+
     return bytesWritten();
   }
 
-  size_t visitString(const char* value) {
-    return visitString(value, strlen(value));
+  size_t visit(const char* value) {
+    return visit(JsonString(value));
   }
 
-  size_t visitString(const char* value, size_t n) {
+  size_t visit(JsonString value) {
     ARDUINOJSON_ASSERT(value != NULL);
 
+    auto n = value.size();
+
     if (n < 0x20) {
       writeByte(uint8_t(0xA0 + n));
     } else if (n < 0x100) {
@@ -98,18 +113,18 @@ class MsgPackSerializer : public Visitor<size_t> {
       writeByte(0xDB);
       writeInteger(uint32_t(n));
     }
-    writeBytes(reinterpret_cast<const uint8_t*>(value), n);
+    writeBytes(reinterpret_cast<const uint8_t*>(value.c_str()), n);
     return bytesWritten();
   }
 
-  size_t visitRawJson(const char* data, size_t size) {
-    writeBytes(reinterpret_cast<const uint8_t*>(data), size);
+  size_t visit(RawString value) {
+    writeBytes(reinterpret_cast<const uint8_t*>(value.data()), value.size());
     return bytesWritten();
   }
 
-  size_t visitSignedInteger(JsonInteger value) {
+  size_t visit(JsonInteger value) {
     if (value > 0) {
-      visitUnsignedInteger(static_cast<JsonUInt>(value));
+      visit(static_cast<JsonUInt>(value));
     } else if (value >= -0x20) {
       writeInteger(int8_t(value));
     } else if (value >= -0x80) {
@@ -137,7 +152,7 @@ class MsgPackSerializer : public Visitor<size_t> {
     return bytesWritten();
   }
 
-  size_t visitUnsignedInteger(JsonUInt value) {
+  size_t visit(JsonUInt value) {
     if (value <= 0x7F) {
       writeInteger(uint8_t(value));
     } else if (value <= 0xFF) {
@@ -165,12 +180,12 @@ class MsgPackSerializer : public Visitor<size_t> {
     return bytesWritten();
   }
 
-  size_t visitBoolean(bool value) {
+  size_t visit(bool value) {
     writeByte(value ? 0xC3 : 0xC2);
     return bytesWritten();
   }
 
-  size_t visitNull() {
+  size_t visit(nullptr_t) {
     writeByte(0xC0);
     return bytesWritten();
   }
@@ -190,11 +205,12 @@ class MsgPackSerializer : public Visitor<size_t> {
 
   template <typename T>
   void writeInteger(T value) {
-    fixEndianess(value);
+    fixEndianness(value);
     writeBytes(reinterpret_cast<uint8_t*>(&value), sizeof(value));
   }
 
   CountingDecorator<TWriter> writer_;
+  const ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
@@ -202,7 +218,7 @@ ARDUINOJSON_END_PRIVATE_NAMESPACE
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // Produces a MessagePack document.
-// https://arduinojson.org/v6/api/msgpack/serializemsgpack/
+// https://arduinojson.org/v7/api/msgpack/serializemsgpack/
 template <typename TDestination>
 inline size_t serializeMsgPack(JsonVariantConst source, TDestination& output) {
   using namespace ArduinoJson::detail;
@@ -210,7 +226,7 @@ inline size_t serializeMsgPack(JsonVariantConst source, TDestination& output) {
 }
 
 // Produces a MessagePack document.
-// https://arduinojson.org/v6/api/msgpack/serializemsgpack/
+// https://arduinojson.org/v7/api/msgpack/serializemsgpack/
 inline size_t serializeMsgPack(JsonVariantConst source, void* output,
                                size_t size) {
   using namespace ArduinoJson::detail;
@@ -218,7 +234,7 @@ inline size_t serializeMsgPack(JsonVariantConst source, void* output,
 }
 
 // Computes the length of the document that serializeMsgPack() produces.
-// https://arduinojson.org/v6/api/msgpack/measuremsgpack/
+// https://arduinojson.org/v7/api/msgpack/measuremsgpack/
 inline size_t measureMsgPack(JsonVariantConst source) {
   using namespace ArduinoJson::detail;
   return measure<MsgPackSerializer>(source);
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/endianness.hpp
similarity index 52%
rename from neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp
rename to neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/endianness.hpp
index bb2dadd318b475362c746bab78a8718a65194a15..94875cf09e6577d834fdf75463ee12411d9cce9c 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/endianness.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -15,32 +15,32 @@ inline void swapBytes(uint8_t& a, uint8_t& b) {
   b = t;
 }
 
-inline void fixEndianess(uint8_t* p, integral_constant<size_t, 8>) {
+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 fixEndianess(uint8_t* p, integral_constant<size_t, 4>) {
+inline void fixEndianness(uint8_t* p, integral_constant<size_t, 4>) {
   swapBytes(p[0], p[3]);
   swapBytes(p[1], p[2]);
 }
 
-inline void fixEndianess(uint8_t* p, integral_constant<size_t, 2>) {
+inline void fixEndianness(uint8_t* p, integral_constant<size_t, 2>) {
   swapBytes(p[0], p[1]);
 }
 
-inline void fixEndianess(uint8_t*, integral_constant<size_t, 1>) {}
+inline void fixEndianness(uint8_t*, integral_constant<size_t, 1>) {}
 
 template <typename T>
-inline void fixEndianess(T& value) {
-  fixEndianess(reinterpret_cast<uint8_t*>(&value),
-               integral_constant<size_t, sizeof(T)>());
+inline void fixEndianness(T& value) {
+  fixEndianness(reinterpret_cast<uint8_t*>(&value),
+                integral_constant<size_t, sizeof(T)>());
 }
 #else
 template <typename T>
-inline void fixEndianess(T&) {}
+inline void fixEndianness(T&) {}
 #endif
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp
index 8513d493c670754977190abe1f9f54d84bfdd800..427a23a93be34ca3f4950a876d1940cbeef74ae0 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Namespace.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Namespace.hpp
index 7cce1748000d0311b9fee1aa1201f925d246eba5..da7a4494db081a6627c5e43524b02ce304bed2ea 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Namespace.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Namespace.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -10,16 +10,16 @@
 
 #ifndef ARDUINOJSON_VERSION_NAMESPACE
 
-#  define ARDUINOJSON_VERSION_NAMESPACE                                       \
-    ARDUINOJSON_CONCAT4(                                                      \
-        ARDUINOJSON_VERSION_MACRO,                                            \
-        ARDUINOJSON_BIN2ALPHA(                                                \
-            ARDUINOJSON_ENABLE_PROGMEM, ARDUINOJSON_USE_LONG_LONG,            \
-            ARDUINOJSON_USE_DOUBLE, ARDUINOJSON_ENABLE_STRING_DEDUPLICATION), \
-        ARDUINOJSON_BIN2ALPHA(                                                \
-            ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY,              \
-            ARDUINOJSON_ENABLE_COMMENTS, ARDUINOJSON_DECODE_UNICODE),         \
-        ARDUINOJSON_SLOT_OFFSET_SIZE)
+#  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
 
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp
index 214bc7a8dd1cc30300f0e484e80749b498c2203d..0a39a7c675b96ee63389e15864886f29a80a49f4 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp
index 23abd7fff5451eaa5b5b6c76f22ea6894fe82ef0..2dcbe10c8c18c3f0044b5f8a8a83f2e0f284a179 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -77,17 +77,17 @@ struct FloatTraits<T, 8 /*64bits*/> {
 
   template <typename TOut>  // int64_t
   static T highest_for(
-      typename enable_if<is_integral<TOut>::value && is_signed<TOut>::value &&
-                             sizeof(TOut) == 8,
-                         signed>::type* = 0) {
+      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(
-      typename enable_if<is_integral<TOut>::value && is_unsigned<TOut>::value &&
-                             sizeof(TOut) == 8,
-                         unsigned>::type* = 0) {
+      enable_if_t<is_integral<TOut>::value && is_unsigned<TOut>::value &&
+                      sizeof(TOut) == 8,
+                  unsigned>* = 0) {
     return forge(0x43EFFFFFFFFFFFFF);  //  1.8446744073709549568e+19
   }
 
@@ -157,33 +157,33 @@ struct FloatTraits<T, 4 /*32bits*/> {
 
   template <typename TOut>  // int32_t
   static T highest_for(
-      typename enable_if<is_integral<TOut>::value && is_signed<TOut>::value &&
-                             sizeof(TOut) == 4,
-                         signed>::type* = 0) {
+      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(
-      typename enable_if<is_integral<TOut>::value && is_unsigned<TOut>::value &&
-                             sizeof(TOut) == 4,
-                         unsigned>::type* = 0) {
+      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(
-      typename enable_if<is_integral<TOut>::value && is_signed<TOut>::value &&
-                             sizeof(TOut) == 8,
-                         signed>::type* = 0) {
+      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(
-      typename enable_if<is_integral<TOut>::value && is_unsigned<TOut>::value &&
-                             sizeof(TOut) == 8,
-                         unsigned>::type* = 0) {
+      enable_if_t<is_integral<TOut>::value && is_unsigned<TOut>::value &&
+                      sizeof(TOut) == 8,
+                  unsigned>* = 0) {
     return forge(0x5F7FFFFF);  // 1.844674297419792384E19
   }
 
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonFloat.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonFloat.hpp
index a1259d07a47aa125d3b0e2836c9bc869c3ddf819..89f3351b41d37d4eb6ce41214e6bf6bf1dfb170c 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonFloat.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonFloat.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonInteger.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonInteger.hpp
index 9100565367bbae03256b5832f57e71242d04331d..ae025b72533dac2c925afc9ba8e93f93e1d77393 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonInteger.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/JsonInteger.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -25,4 +25,4 @@ ARDUINOJSON_END_PUBLIC_NAMESPACE
   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/v6/api/config/use_long_long/");
+                "https://arduinojson.org/v7/api/config/use_long_long/");
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp
index e706fd94d64397d0094635113abc6fe5e19dcd0c..2cbc4588b89f9588e1e073696154c7751eac5e18 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -32,34 +32,34 @@ CompareResult arithmeticCompare(const T& lhs, const T& rhs) {
 template <typename T1, typename T2>
 CompareResult arithmeticCompare(
     const T1& lhs, const T2& rhs,
-    typename enable_if<is_integral<T1>::value && is_integral<T2>::value &&
-                       sizeof(T1) < sizeof(T2)>::type* = 0) {
+    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,
-    typename enable_if<is_integral<T1>::value && is_integral<T2>::value &&
-                       sizeof(T2) < sizeof(T1)>::type* = 0) {
+    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,
-    typename enable_if<is_integral<T1>::value && is_integral<T2>::value &&
-                       is_signed<T1>::value == is_signed<T2>::value &&
-                       sizeof(T2) == sizeof(T1)>::type* = 0) {
+    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,
-    typename enable_if<is_integral<T1>::value && is_integral<T2>::value &&
-                       is_unsigned<T1>::value && is_signed<T2>::value &&
-                       sizeof(T2) == sizeof(T1)>::type* = 0) {
+    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));
@@ -68,9 +68,9 @@ CompareResult arithmeticCompare(
 template <typename T1, typename T2>
 CompareResult arithmeticCompare(
     const T1& lhs, const T2& rhs,
-    typename enable_if<is_integral<T1>::value && is_integral<T2>::value &&
-                       is_signed<T1>::value && is_unsigned<T2>::value &&
-                       sizeof(T2) == sizeof(T1)>::type* = 0) {
+    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);
@@ -79,23 +79,21 @@ CompareResult arithmeticCompare(
 template <typename T1, typename T2>
 CompareResult arithmeticCompare(
     const T1& lhs, const T2& rhs,
-    typename enable_if<is_floating_point<T1>::value ||
-                       is_floating_point<T2>::value>::type* = 0) {
+    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&,
-    typename enable_if<is_unsigned<T2>::value>::type* = 0) {
+    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,
-    typename enable_if<is_signed<T2>::value>::type* = 0) {
+    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));
@@ -103,15 +101,13 @@ CompareResult arithmeticCompareNegateLeft(
 
 template <typename T1>
 CompareResult arithmeticCompareNegateRight(
-    const T1&, JsonUInt,
-    typename enable_if<is_unsigned<T1>::value>::type* = 0) {
+    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,
-    typename enable_if<is_signed<T1>::value>::type* = 0) {
+    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);
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp
index 4fbf8655da5f69b0a39c214f51812e149ae6db8d..0136278ea66143d5cdfdaee7b6cdc79e8c56e056 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -22,18 +22,18 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 // uint32 -> int32
 // uint64 -> int32
 template <typename TOut, typename TIn>
-typename enable_if<is_integral<TIn>::value && is_unsigned<TIn>::value &&
-                       is_integral<TOut>::value && sizeof(TOut) <= sizeof(TIn),
-                   bool>::type
+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>
-typename enable_if<is_integral<TIn>::value && is_unsigned<TIn>::value &&
-                       is_integral<TOut>::value && sizeof(TIn) < sizeof(TOut),
-                   bool>::type
+enable_if_t<is_integral<TIn>::value && is_unsigned<TIn>::value &&
+                is_integral<TOut>::value && sizeof(TIn) < sizeof(TOut),
+            bool>
 canConvertNumber(TIn) {
   return true;
 }
@@ -41,18 +41,17 @@ canConvertNumber(TIn) {
 // uint32 -> float
 // int32 -> float
 template <typename TOut, typename TIn>
-typename enable_if<is_integral<TIn>::value && is_floating_point<TOut>::value,
-                   bool>::type
+enable_if_t<is_integral<TIn>::value && is_floating_point<TOut>::value, bool>
 canConvertNumber(TIn) {
   return true;
 }
 
 // int64 -> int32
 template <typename TOut, typename TIn>
-typename enable_if<is_integral<TIn>::value && is_signed<TIn>::value &&
-                       is_integral<TOut>::value && is_signed<TOut>::value &&
-                       sizeof(TOut) < sizeof(TIn),
-                   bool>::type
+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());
@@ -61,10 +60,10 @@ canConvertNumber(TIn value) {
 // int32 -> int32
 // int32 -> int64
 template <typename TOut, typename TIn>
-typename enable_if<is_integral<TIn>::value && is_signed<TIn>::value &&
-                       is_integral<TOut>::value && is_signed<TOut>::value &&
-                       sizeof(TIn) <= sizeof(TOut),
-                   bool>::type
+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;
 }
@@ -72,10 +71,10 @@ canConvertNumber(TIn) {
 // int32 -> uint32
 // int32 -> uint64
 template <typename TOut, typename TIn>
-typename enable_if<is_integral<TIn>::value && is_signed<TIn>::value &&
-                       is_integral<TOut>::value && is_unsigned<TOut>::value &&
-                       sizeof(TOut) >= sizeof(TIn),
-                   bool>::type
+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;
@@ -84,10 +83,10 @@ canConvertNumber(TIn value) {
 
 // int32 -> uint16
 template <typename TOut, typename TIn>
-typename enable_if<is_integral<TIn>::value && is_signed<TIn>::value &&
-                       is_integral<TOut>::value && is_unsigned<TOut>::value &&
-                       sizeof(TOut) < sizeof(TIn),
-                   bool>::type
+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;
@@ -97,9 +96,9 @@ canConvertNumber(TIn value) {
 // float32 -> int16
 // float64 -> int32
 template <typename TOut, typename TIn>
-typename enable_if<is_floating_point<TIn>::value && is_integral<TOut>::value &&
-                       sizeof(TOut) < sizeof(TIn),
-                   bool>::type
+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();
@@ -112,9 +111,9 @@ canConvertNumber(TIn value) {
 // float64 -> int64
 // float64 -> uint64
 template <typename TOut, typename TIn>
-typename enable_if<is_floating_point<TIn>::value && is_integral<TOut>::value &&
-                       sizeof(TOut) >= sizeof(TIn),
-                   bool>::type
+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'"
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp
index 4cec1a69e4c4a49ba0d46e3f207317d348566607..7fb3cba36e18c41867ac7f3413217c5610ad710f 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -16,11 +16,11 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename A, typename B>
-struct choose_largest : conditional<(sizeof(A) > sizeof(B)), A, B> {};
+using largest_type = conditional_t<(sizeof(A) > sizeof(B)), A, B>;
 
 inline bool parseNumber(const char* s, VariantData& result) {
   typedef FloatTraits<JsonFloat> traits;
-  typedef choose_largest<traits::mantissa_type, JsonUInt>::type mantissa_t;
+  typedef largest_type<traits::mantissa_type, JsonUInt> mantissa_t;
   typedef traits::exponent_type exponent_t;
 
   ARDUINOJSON_ASSERT(s != 0);
@@ -147,6 +147,6 @@ template <typename T>
 inline T parseNumber(const char* s) {
   VariantData value;
   parseNumber(s, value);
-  return Converter<T>::fromJson(JsonVariantConst(&value));
+  return Converter<T>::fromJson(JsonVariantConst(&value, nullptr));
 }
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObject.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObject.hpp
index 7cdc1c76eb19028536966fc19c14d86e8ea3c16b..a11af74641f31010a5795b11defe201124ca9115 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObject.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObject.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -12,7 +12,7 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 class JsonArray;
 
 // A reference to an object in a JsonDocument.
-// https://arduinojson.org/v6/api/jsonobject/
+// https://arduinojson.org/v7/api/jsonobject/
 class JsonObject : public detail::VariantOperators<JsonObject> {
   friend class detail::VariantAttorney;
 
@@ -20,181 +20,210 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
   typedef JsonObjectIterator iterator;
 
   // Creates an unbound reference.
-  FORCE_INLINE JsonObject() : data_(0), pool_(0) {}
+  JsonObject() : data_(0), resources_(0) {}
 
   // INTERNAL USE ONLY
-  FORCE_INLINE JsonObject(detail::MemoryPool* buf, detail::CollectionData* data)
-      : data_(data), pool_(buf) {}
+  JsonObject(detail::ObjectData* data, detail::ResourceManager* resource)
+      : data_(data), resources_(resource) {}
 
   operator JsonVariant() const {
     void* data = data_;  // prevent warning cast-align
-    return JsonVariant(pool_, reinterpret_cast<detail::VariantData*>(data));
+    return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
+                       resources_);
   }
 
   operator JsonObjectConst() const {
-    return JsonObjectConst(data_);
+    return JsonObjectConst(data_, resources_);
   }
 
   operator JsonVariantConst() const {
-    return JsonVariantConst(collectionToVariant(data_));
+    return JsonVariantConst(collectionToVariant(data_), resources_);
   }
 
   // Returns true if the reference is unbound.
-  // https://arduinojson.org/v6/api/jsonobject/isnull/
-  FORCE_INLINE bool isNull() const {
+  // https://arduinojson.org/v7/api/jsonobject/isnull/
+  bool isNull() const {
     return data_ == 0;
   }
 
   // Returns true if the reference is bound.
-  // https://arduinojson.org/v6/api/jsonobject/isnull/
-  FORCE_INLINE operator bool() const {
+  // https://arduinojson.org/v7/api/jsonobject/isnull/
+  operator bool() const {
     return data_ != 0;
   }
 
-  // Returns the number of bytes occupied by the object.
-  // https://arduinojson.org/v6/api/jsonobject/memoryusage/
-  FORCE_INLINE size_t memoryUsage() const {
-    return data_ ? data_->memoryUsage() : 0;
-  }
-
   // Returns the depth (nesting level) of the object.
-  // https://arduinojson.org/v6/api/jsonobject/nesting/
-  FORCE_INLINE size_t nesting() const {
-    return variantNesting(collectionToVariant(data_));
+  // 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/v6/api/jsonobject/size/
-  FORCE_INLINE size_t size() const {
-    return data_ ? data_->size() : 0;
+  // 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/v6/api/jsonobject/begin/
-  FORCE_INLINE iterator begin() const {
+  // https://arduinojson.org/v7/api/jsonobject/begin/
+  iterator begin() const {
     if (!data_)
       return iterator();
-    return iterator(pool_, data_->head());
+    return iterator(data_->createIterator(resources_), resources_);
   }
 
   // Returns an iterator following the last key-value pair of the object.
-  // https://arduinojson.org/v6/api/jsonobject/end/
-  FORCE_INLINE iterator end() const {
+  // https://arduinojson.org/v7/api/jsonobject/end/
+  iterator end() const {
     return iterator();
   }
 
   // Removes all the members of the object.
-  // ⚠️ Doesn't release the memory associated with the removed members.
-  // https://arduinojson.org/v6/api/jsonobject/clear/
+  // https://arduinojson.org/v7/api/jsonobject/clear/
   void clear() const {
-    if (!data_)
-      return;
-    data_->clear();
+    detail::ObjectData::clear(data_, resources_);
   }
 
   // Copies an object.
-  // https://arduinojson.org/v6/api/jsonobject/set/
-  FORCE_INLINE bool set(JsonObjectConst src) {
+  // https://arduinojson.org/v7/api/jsonobject/set/
+  bool set(JsonObjectConst src) {
     if (!data_ || !src.data_)
       return false;
-    return data_->copyFrom(*src.data_, pool_);
-  }
 
-  // Compares the content of two objects.
-  FORCE_INLINE bool operator==(JsonObject rhs) const {
-    return JsonObjectConst(data_) == JsonObjectConst(rhs.data_);
+    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/v6/api/jsonobject/subscript/
+  // https://arduinojson.org/v7/api/jsonobject/subscript/
   template <typename TString>
-  FORCE_INLINE
-      typename detail::enable_if<detail::IsString<TString>::value,
-                                 detail::MemberProxy<JsonObject, TString>>::type
-      operator[](const TString& key) const {
+  detail::enable_if_t<detail::IsString<TString>::value,
+                      detail::MemberProxy<JsonObject, TString>>
+  operator[](const TString& key) const {
     return {*this, key};
   }
 
   // Gets or sets the member with specified key.
-  // https://arduinojson.org/v6/api/jsonobject/subscript/
+  // https://arduinojson.org/v7/api/jsonobject/subscript/
   template <typename TChar>
-  FORCE_INLINE
-      typename detail::enable_if<detail::IsString<TChar*>::value,
-                                 detail::MemberProxy<JsonObject, TChar*>>::type
-      operator[](TChar* key) const {
+  detail::enable_if_t<detail::IsString<TChar*>::value,
+                      detail::MemberProxy<JsonObject, TChar*>>
+  operator[](TChar* key) const {
     return {*this, 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,
+                      detail::MemberProxy<JsonObject, const char*>>
+  operator[](const TVariant& key) const {
+    if (key.template is<const char*>())
+      return {*this, key.template as<const char*>()};
+    else
+      return {*this, nullptr};
+  }
+
   // Removes the member at the specified iterator.
-  // ⚠️ Doesn't release the memory associated with the removed member.
-  // https://arduinojson.org/v6/api/jsonobject/remove/
+  // https://arduinojson.org/v7/api/jsonobject/remove/
   FORCE_INLINE void remove(iterator it) const {
-    if (!data_)
-      return;
-    data_->removeSlot(it.slot_);
+    detail::ObjectData::remove(data_, it.iterator_, resources_);
   }
 
   // Removes the member with the specified key.
-  // ⚠️ Doesn't release the memory associated with the removed member.
-  // https://arduinojson.org/v6/api/jsonobject/remove/
+  // https://arduinojson.org/v7/api/jsonobject/remove/
   template <typename TString>
-  FORCE_INLINE void remove(const TString& key) const {
-    removeMember(detail::adaptString(key));
+  detail::enable_if_t<detail::IsString<TString>::value> 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> remove(
+      const TVariant& key) const {
+    if (key.template is<const char*>())
+      remove(key.template as<const char*>());
   }
 
   // Removes the member with the specified key.
-  // ⚠️ Doesn't release the memory associated with the removed member.
-  // https://arduinojson.org/v6/api/jsonobject/remove/
+  // https://arduinojson.org/v7/api/jsonobject/remove/
   template <typename TChar>
   FORCE_INLINE void remove(TChar* key) const {
-    removeMember(detail::adaptString(key));
+    detail::ObjectData::removeMember(data_, detail::adaptString(key),
+                                     resources_);
   }
 
   // Returns true if the object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonobject/containskey/
+  // https://arduinojson.org/v7/api/jsonobject/containskey/
   template <typename TString>
-  FORCE_INLINE
-      typename detail::enable_if<detail::IsString<TString>::value, bool>::type
-      containsKey(const TString& key) const {
-    return getMember(detail::adaptString(key)) != 0;
+  detail::enable_if_t<detail::IsString<TString>::value, bool> containsKey(
+      const TString& key) const {
+    return detail::ObjectData::getMember(data_, detail::adaptString(key),
+                                         resources_) != 0;
   }
 
   // Returns true if the object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonobject/containskey/
+  // https://arduinojson.org/v7/api/jsonobject/containskey/
   template <typename TChar>
-  FORCE_INLINE
-      typename detail::enable_if<detail::IsString<TChar*>::value, bool>::type
-      containsKey(TChar* key) const {
-    return getMember(detail::adaptString(key)) != 0;
+  detail::enable_if_t<detail::IsString<TChar*>::value, bool> containsKey(
+      TChar* key) const {
+    return detail::ObjectData::getMember(data_, detail::adaptString(key),
+                                         resources_) != 0;
   }
 
-  // Creates an array and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonobject/createnestedarray/
-  template <typename TString>
-  FORCE_INLINE JsonArray createNestedArray(const TString& key) const;
+  // Returns true if the object contains the specified key.
+  // https://arduinojson.org/v7/api/jsonobject/containskey/
+  template <typename TVariant>
+  detail::enable_if_t<detail::IsVariant<TVariant>::value, bool> containsKey(
+      const TVariant& key) const {
+    return containsKey(key.template as<const char*>());
+  }
 
-  // Creates an array and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonobject/createnestedarray/
+  // DEPRECATED: use obj[key].to<JsonArray>() instead
   template <typename TChar>
-  FORCE_INLINE JsonArray createNestedArray(TChar* key) const;
+  ARDUINOJSON_DEPRECATED("use obj[key].to<JsonArray>() instead")
+  JsonArray createNestedArray(TChar* key) const {
+    return operator[](key).template to<JsonArray>();
+  }
 
-  // Creates an object and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonobject/createnestedobject/
+  // DEPRECATED: use obj[key].to<JsonArray>() instead
   template <typename TString>
-  JsonObject createNestedObject(const TString& key) const {
-    return operator[](key).template to<JsonObject>();
+  ARDUINOJSON_DEPRECATED("use obj[key].to<JsonArray>() instead")
+  JsonArray createNestedArray(const TString& key) const {
+    return operator[](key).template to<JsonArray>();
   }
 
-  // Creates an object and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonobject/createnestedobject/
+  // DEPRECATED: use obj[key].to<JsonObject>() instead
   template <typename TChar>
-  JsonObject createNestedObject(TChar* key) const {
+  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::MemoryPool* getPool() const {
-    return pool_;
+  detail::ResourceManager* getResourceManager() const {
+    return resources_;
   }
 
   detail::VariantData* getData() const {
@@ -205,47 +234,8 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
     return detail::collectionToVariant(data_);
   }
 
-  template <typename TAdaptedString>
-  inline detail::VariantData* getMember(TAdaptedString key) const {
-    if (!data_)
-      return 0;
-    return data_->getMember(key);
-  }
-
-  template <typename TAdaptedString>
-  void removeMember(TAdaptedString key) const {
-    if (!data_)
-      return;
-    data_->removeMember(key);
-  }
-
-  detail::CollectionData* data_;
-  detail::MemoryPool* pool_;
-};
-
-template <>
-struct Converter<JsonObject> : private detail::VariantAttorney {
-  static void toJson(JsonVariantConst src, JsonVariant dst) {
-    variantCopyFrom(getData(dst), getData(src), getPool(dst));
-  }
-
-  static JsonObject fromJson(JsonVariant src) {
-    auto data = getData(src);
-    auto pool = getPool(src);
-    return JsonObject(pool, data != 0 ? data->asObject() : 0);
-  }
-
-  static detail::InvalidConversion<JsonVariantConst, JsonObject> fromJson(
-      JsonVariantConst);
-
-  static bool checkJson(JsonVariantConst) {
-    return false;
-  }
-
-  static bool checkJson(JsonVariant src) {
-    auto data = getData(src);
-    return data && data->isObject();
-  }
+  detail::ObjectData* data_;
+  detail::ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectConst.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectConst.hpp
index e9b5e48355d794eb21542d229aa7f3affe5eb11c..81132e28adf9673c75c005c7f6f2652193ab1034 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectConst.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectConst.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -10,7 +10,7 @@
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // A read-only reference to an object in a JsonDocument.
-// https://arduinojson.org/v6/api/jsonobjectconst/
+// https://arduinojson.org/v7/api/jsonobjectconst/
 class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
   friend class JsonObject;
   friend class detail::VariantAttorney;
@@ -19,106 +19,115 @@ class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
   typedef JsonObjectConstIterator iterator;
 
   // Creates an unbound reference.
-  JsonObjectConst() : data_(0) {}
+  JsonObjectConst() : data_(0), resources_(0) {}
 
   // INTERNAL USE ONLY
-  JsonObjectConst(const detail::CollectionData* data) : data_(data) {}
+  JsonObjectConst(const detail::ObjectData* data,
+                  const detail::ResourceManager* resources)
+      : data_(data), resources_(resources) {}
 
   operator JsonVariantConst() const {
-    return JsonVariantConst(collectionToVariant(data_));
+    return JsonVariantConst(getData(), resources_);
   }
 
   // Returns true if the reference is unbound.
-  // https://arduinojson.org/v6/api/jsonobjectconst/isnull/
-  FORCE_INLINE bool isNull() const {
+  // https://arduinojson.org/v7/api/jsonobjectconst/isnull/
+  bool isNull() const {
     return data_ == 0;
   }
 
   // Returns true if the reference is bound.
-  // https://arduinojson.org/v6/api/jsonobjectconst/isnull/
-  FORCE_INLINE operator bool() const {
+  // https://arduinojson.org/v7/api/jsonobjectconst/isnull/
+  operator bool() const {
     return data_ != 0;
   }
 
-  // Returns the number of bytes occupied by the object.
-  // https://arduinojson.org/v6/api/jsonobjectconst/memoryusage/
-  FORCE_INLINE size_t memoryUsage() const {
-    return data_ ? data_->memoryUsage() : 0;
-  }
-
   // Returns the depth (nesting level) of the object.
-  // https://arduinojson.org/v6/api/jsonobjectconst/nesting/
-  FORCE_INLINE size_t nesting() const {
-    return variantNesting(collectionToVariant(data_));
+  // 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/v6/api/jsonobjectconst/size/
-  FORCE_INLINE size_t size() const {
-    return data_ ? data_->size() : 0;
+  // 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/v6/api/jsonobjectconst/begin/
-  FORCE_INLINE iterator begin() const {
+  // https://arduinojson.org/v7/api/jsonobjectconst/begin/
+  iterator begin() const {
     if (!data_)
       return iterator();
-    return iterator(data_->head());
+    return iterator(data_->createIterator(resources_), resources_);
   }
 
   // Returns an iterator following the last key-value pair of the object.
-  // https://arduinojson.org/v6/api/jsonobjectconst/end/
-  FORCE_INLINE iterator end() const {
+  // https://arduinojson.org/v7/api/jsonobjectconst/end/
+  iterator end() const {
     return iterator();
   }
 
   // Returns true if the object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonobjectconst/containskey/
+  // https://arduinojson.org/v7/api/jsonobjectconst/containskey/
   template <typename TString>
-  FORCE_INLINE bool containsKey(const TString& key) const {
-    return getMember(detail::adaptString(key)) != 0;
+  detail::enable_if_t<detail::IsString<TString>::value, bool> containsKey(
+      const TString& key) const {
+    return detail::ObjectData::getMember(data_, detail::adaptString(key),
+                                         resources_) != 0;
   }
 
   // Returns true if the object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonobjectconst/containskey/
+  // https://arduinojson.org/v7/api/jsonobjectconst/containskey/
   template <typename TChar>
-  FORCE_INLINE bool containsKey(TChar* key) const {
-    return getMember(detail::adaptString(key)) != 0;
+  bool containsKey(TChar* key) const {
+    return detail::ObjectData::getMember(data_, detail::adaptString(key),
+                                         resources_) != 0;
+  }
+
+  // Returns true if the object contains the specified key.
+  // https://arduinojson.org/v7/api/jsonobjectconst/containskey/
+  template <typename TVariant>
+  detail::enable_if_t<detail::IsVariant<TVariant>::value, bool> containsKey(
+      const TVariant& key) const {
+    return containsKey(key.template as<const char*>());
   }
 
   // Gets the member with specified key.
-  // https://arduinojson.org/v6/api/jsonobjectconst/subscript/
+  // https://arduinojson.org/v7/api/jsonobjectconst/subscript/
   template <typename TString>
-  FORCE_INLINE typename detail::enable_if<detail::IsString<TString>::value,
-                                          JsonVariantConst>::type
+  detail::enable_if_t<detail::IsString<TString>::value, JsonVariantConst>
   operator[](const TString& key) const {
-    return JsonVariantConst(getMember(detail::adaptString(key)));
+    return JsonVariantConst(detail::ObjectData::getMember(
+                                data_, detail::adaptString(key), resources_),
+                            resources_);
   }
 
   // Gets the member with specified key.
-  // https://arduinojson.org/v6/api/jsonobjectconst/subscript/
+  // https://arduinojson.org/v7/api/jsonobjectconst/subscript/
   template <typename TChar>
-  FORCE_INLINE typename detail::enable_if<detail::IsString<TChar*>::value,
-                                          JsonVariantConst>::type
+  detail::enable_if_t<detail::IsString<TChar*>::value, JsonVariantConst>
   operator[](TChar* key) const {
-    return JsonVariantConst(getMember(detail::adaptString(key)));
+    return JsonVariantConst(detail::ObjectData::getMember(
+                                data_, detail::adaptString(key), resources_),
+                            resources_);
   }
 
-  // Compares objects.
-  FORCE_INLINE bool operator==(JsonObjectConst rhs) const {
-    if (data_ == rhs.data_)
-      return true;
-
-    if (!data_ || !rhs.data_)
-      return false;
+  // Gets the member with specified key.
+  // https://arduinojson.org/v7/api/jsonobjectconst/subscript/
+  template <typename TVariant>
+  detail::enable_if_t<detail::IsVariant<TVariant>::value, JsonVariantConst>
+  operator[](const TVariant& key) const {
+    if (key.template is<const char*>())
+      return operator[](key.template as<const char*>());
+    else
+      return JsonVariantConst();
+  }
 
-    size_t count = 0;
-    for (iterator it = begin(); it != end(); ++it) {
-      if (it->value() != rhs[it->key()])
-        return false;
-      count++;
-    }
-    return count == rhs.size();
+  // DEPRECATED: always returns zero
+  ARDUINOJSON_DEPRECATED("always returns zero")
+  size_t memoryUsage() const {
+    return 0;
   }
 
  private:
@@ -126,31 +135,27 @@ class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
     return collectionToVariant(data_);
   }
 
-  template <typename TAdaptedString>
-  const detail::VariantData* getMember(TAdaptedString key) const {
-    if (!data_)
-      return 0;
-    return data_->getMember(key);
-  }
-
-  const detail::CollectionData* data_;
+  const detail::ObjectData* data_;
+  const detail::ResourceManager* resources_;
 };
 
-template <>
-struct Converter<JsonObjectConst> : private detail::VariantAttorney {
-  static void toJson(JsonVariantConst src, JsonVariant dst) {
-    variantCopyFrom(getData(dst), getData(src), getPool(dst));
-  }
+inline bool operator==(JsonObjectConst lhs, JsonObjectConst rhs) {
+  if (!lhs && !rhs)  // both are null
+    return true;
 
-  static JsonObjectConst fromJson(JsonVariantConst src) {
-    auto data = getData(src);
-    return data != 0 ? data->asObject() : 0;
-  }
+  if (!lhs || !rhs)  // only one is null
+    return false;
 
-  static bool checkJson(JsonVariantConst src) {
-    auto data = getData(src);
-    return data && data->isObject();
+  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
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectImpl.hpp
deleted file mode 100644
index 3b24081a285d5d202eb6314bbdaf3111b3276f80..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectImpl.hpp
+++ /dev/null
@@ -1,85 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Array/JsonArray.hpp>
-#include <ArduinoJson/Object/JsonObject.hpp>
-
-ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
-
-template <typename TString>
-inline JsonArray JsonObject::createNestedArray(const TString& key) const {
-  return operator[](key).template to<JsonArray>();
-}
-
-template <typename TChar>
-inline JsonArray JsonObject::createNestedArray(TChar* key) const {
-  return operator[](key).template to<JsonArray>();
-}
-
-ARDUINOJSON_END_PUBLIC_NAMESPACE
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-template <typename TDerived>
-template <typename TString>
-inline JsonArray VariantRefBase<TDerived>::createNestedArray(
-    const TString& key) const {
-  return operator[](key).template to<JsonArray>();
-}
-
-template <typename TDerived>
-template <typename TChar>
-inline JsonArray VariantRefBase<TDerived>::createNestedArray(TChar* key) const {
-  return operator[](key).template to<JsonArray>();
-}
-
-template <typename TDerived>
-template <typename TString>
-inline JsonObject VariantRefBase<TDerived>::createNestedObject(
-    const TString& key) const {
-  return operator[](key).template to<JsonObject>();
-}
-
-template <typename TDerived>
-template <typename TChar>
-inline JsonObject VariantRefBase<TDerived>::createNestedObject(
-    TChar* key) const {
-  return operator[](key).template to<JsonObject>();
-}
-
-template <typename TDerived>
-template <typename TString>
-inline typename enable_if<IsString<TString>::value, bool>::type
-VariantRefBase<TDerived>::containsKey(const TString& key) const {
-  return variantGetMember(VariantAttorney::getData(derived()),
-                          adaptString(key)) != 0;
-}
-
-template <typename TDerived>
-template <typename TChar>
-inline typename enable_if<IsString<TChar*>::value, bool>::type
-VariantRefBase<TDerived>::containsKey(TChar* key) const {
-  return variantGetMember(VariantAttorney::getData(derived()),
-                          adaptString(key)) != 0;
-}
-
-template <typename TDerived>
-template <typename TString>
-inline typename enable_if<IsString<TString*>::value,
-                          MemberProxy<TDerived, TString*>>::type
-VariantRefBase<TDerived>::operator[](TString* key) const {
-  return MemberProxy<TDerived, TString*>(derived(), key);
-}
-
-template <typename TDerived>
-template <typename TString>
-inline typename enable_if<IsString<TString>::value,
-                          MemberProxy<TDerived, TString>>::type
-VariantRefBase<TDerived>::operator[](const TString& key) const {
-  return MemberProxy<TDerived, TString>(derived(), key);
-}
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectIterator.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectIterator.hpp
index 55c09ed78a1334498da77ed43ee5a3c8c992b9ef..b849e62af5d0ca86dc16e770aa120e56217fa1b8 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectIterator.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonObjectIterator.hpp
@@ -1,123 +1,81 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Object/JsonPair.hpp>
-#include <ArduinoJson/Variant/SlotFunctions.hpp>
 
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
-class JsonPairPtr {
- public:
-  JsonPairPtr(detail::MemoryPool* pool, detail::VariantSlot* slot)
-      : pair_(pool, slot) {}
-
-  const JsonPair* operator->() const {
-    return &pair_;
-  }
-
-  const JsonPair& operator*() const {
-    return pair_;
-  }
-
- private:
-  JsonPair pair_;
-};
-
 class JsonObjectIterator {
   friend class JsonObject;
 
  public:
-  JsonObjectIterator() : slot_(0) {}
+  JsonObjectIterator() {}
 
-  explicit JsonObjectIterator(detail::MemoryPool* pool,
-                              detail::VariantSlot* slot)
-      : pool_(pool), slot_(slot) {}
+  explicit JsonObjectIterator(detail::ObjectData::iterator iterator,
+                              detail::ResourceManager* resources)
+      : iterator_(iterator), resources_(resources) {}
 
   JsonPair operator*() const {
-    return JsonPair(pool_, slot_);
+    return JsonPair(iterator_, resources_);
   }
-  JsonPairPtr operator->() {
-    return JsonPairPtr(pool_, slot_);
+  Ptr<JsonPair> operator->() {
+    return operator*();
   }
 
   bool operator==(const JsonObjectIterator& other) const {
-    return slot_ == other.slot_;
+    return iterator_ == other.iterator_;
   }
 
   bool operator!=(const JsonObjectIterator& other) const {
-    return slot_ != other.slot_;
+    return iterator_ != other.iterator_;
   }
 
   JsonObjectIterator& operator++() {
-    slot_ = slot_->next();
-    return *this;
-  }
-
-  JsonObjectIterator& operator+=(size_t distance) {
-    slot_ = slot_->next(distance);
+    iterator_.next(resources_);
     return *this;
   }
 
  private:
-  detail::MemoryPool* pool_;
-  detail::VariantSlot* slot_;
-};
-
-class JsonPairConstPtr {
- public:
-  JsonPairConstPtr(const detail::VariantSlot* slot) : pair_(slot) {}
-
-  const JsonPairConst* operator->() const {
-    return &pair_;
-  }
-
-  const JsonPairConst& operator*() const {
-    return pair_;
-  }
-
- private:
-  JsonPairConst pair_;
+  detail::ObjectData::iterator iterator_;
+  detail::ResourceManager* resources_;
 };
 
 class JsonObjectConstIterator {
   friend class JsonObject;
 
  public:
-  JsonObjectConstIterator() : slot_(0) {}
+  JsonObjectConstIterator() {}
 
-  explicit JsonObjectConstIterator(const detail::VariantSlot* slot)
-      : slot_(slot) {}
+  explicit JsonObjectConstIterator(detail::ObjectData::iterator iterator,
+                                   const detail::ResourceManager* resources)
+      : iterator_(iterator), resources_(resources) {}
 
   JsonPairConst operator*() const {
-    return JsonPairConst(slot_);
+    return JsonPairConst(iterator_, resources_);
   }
-  JsonPairConstPtr operator->() {
-    return JsonPairConstPtr(slot_);
+  Ptr<JsonPairConst> operator->() {
+    return operator*();
   }
 
   bool operator==(const JsonObjectConstIterator& other) const {
-    return slot_ == other.slot_;
+    return iterator_ == other.iterator_;
   }
 
   bool operator!=(const JsonObjectConstIterator& other) const {
-    return slot_ != other.slot_;
+    return iterator_ != other.iterator_;
   }
 
   JsonObjectConstIterator& operator++() {
-    slot_ = slot_->next();
-    return *this;
-  }
-
-  JsonObjectConstIterator& operator+=(size_t distance) {
-    slot_ = slot_->next(distance);
+    iterator_.next(resources_);
     return *this;
   }
 
  private:
-  const detail::VariantSlot* slot_;
+  detail::ObjectData::iterator iterator_;
+  const detail::ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonPair.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonPair.hpp
index 862a6579347067b63bbd1746d1e18a5962b49abd..4d621a16fbfc99d2edf2e927b887e61903e29ebf 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonPair.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/JsonPair.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -11,58 +11,60 @@
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // A key-value pair.
-// https://arduinojson.org/v6/api/jsonobject/begin_end/
+// https://arduinojson.org/v7/api/jsonobject/begin_end/
 class JsonPair {
  public:
   // INTERNAL USE ONLY
-  JsonPair(detail::MemoryPool* pool, detail::VariantSlot* slot) {
-    if (slot) {
-      key_ = JsonString(slot->key(), slot->ownsKey() ? JsonString::Copied
-                                                     : JsonString::Linked);
-      value_ = JsonVariant(pool, slot->data());
-    }
-  }
+  JsonPair(detail::ObjectData::iterator iterator,
+           detail::ResourceManager* resources)
+      : iterator_(iterator), resources_(resources) {}
 
   // Returns the key.
   JsonString key() const {
-    return key_;
+    if (!iterator_.done())
+      return JsonString(iterator_.key(), iterator_.ownsKey()
+                                             ? JsonString::Copied
+                                             : JsonString::Linked);
+    else
+      return JsonString();
   }
 
   // Returns the value.
-  JsonVariant value() const {
-    return value_;
+  JsonVariant value() {
+    return JsonVariant(iterator_.data(), resources_);
   }
 
  private:
-  JsonString key_;
-  JsonVariant value_;
+  detail::ObjectData::iterator iterator_;
+  detail::ResourceManager* resources_;
 };
 
 // A read-only key-value pair.
-// https://arduinojson.org/v6/api/jsonobjectconst/begin_end/
+// https://arduinojson.org/v7/api/jsonobjectconst/begin_end/
 class JsonPairConst {
  public:
-  JsonPairConst(const detail::VariantSlot* slot) {
-    if (slot) {
-      key_ = JsonString(slot->key(), slot->ownsKey() ? JsonString::Copied
-                                                     : JsonString::Linked);
-      value_ = JsonVariantConst(slot->data());
-    }
-  }
+  JsonPairConst(detail::ObjectData::iterator iterator,
+                const detail::ResourceManager* resources)
+      : iterator_(iterator), resources_(resources) {}
 
   // Returns the key.
   JsonString key() const {
-    return key_;
+    if (!iterator_.done())
+      return JsonString(iterator_.key(), iterator_.ownsKey()
+                                             ? JsonString::Copied
+                                             : JsonString::Linked);
+    else
+      return JsonString();
   }
 
   // Returns the value.
   JsonVariantConst value() const {
-    return value_;
+    return JsonVariantConst(iterator_.data(), resources_);
   }
 
  private:
-  JsonString key_;
-  JsonVariantConst value_;
+  detail::ObjectData::iterator iterator_;
+  const detail::ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp
index 78e5a5211ea87a189943ef66d660338eedb6549a..eb9feedeb6d770acef5543b247c35d4b707393ea 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,7 +9,7 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 // A proxy class to get or set a member of an object.
-// https://arduinojson.org/v6/api/jsonobject/subscript/
+// https://arduinojson.org/v7/api/jsonobject/subscript/
 template <typename TUpstream, typename TStringRef>
 class MemberProxy
     : public VariantRefBase<MemberProxy<TUpstream, TStringRef>>,
@@ -17,43 +17,46 @@ class MemberProxy
   friend class VariantAttorney;
 
  public:
-  FORCE_INLINE MemberProxy(TUpstream upstream, TStringRef key)
+  MemberProxy(TUpstream upstream, TStringRef key)
       : upstream_(upstream), key_(key) {}
 
   MemberProxy(const MemberProxy& src)
       : upstream_(src.upstream_), key_(src.key_) {}
 
-  FORCE_INLINE MemberProxy& operator=(const MemberProxy& src) {
+  MemberProxy& operator=(const MemberProxy& src) {
     this->set(src);
     return *this;
   }
 
   template <typename T>
-  FORCE_INLINE MemberProxy& operator=(const T& src) {
+  MemberProxy& operator=(const T& src) {
     this->set(src);
     return *this;
   }
 
   template <typename T>
-  FORCE_INLINE MemberProxy& operator=(T* src) {
+  MemberProxy& operator=(T* src) {
     this->set(src);
     return *this;
   }
 
  private:
-  FORCE_INLINE MemoryPool* getPool() const {
-    return VariantAttorney::getPool(upstream_);
+  ResourceManager* getResourceManager() const {
+    return VariantAttorney::getResourceManager(upstream_);
   }
 
-  FORCE_INLINE VariantData* getData() const {
-    return variantGetMember(VariantAttorney::getData(upstream_),
-                            adaptString(key_));
+  VariantData* getData() const {
+    return VariantData::getMember(
+        VariantAttorney::getData(upstream_), adaptString(key_),
+        VariantAttorney::getResourceManager(upstream_));
   }
 
-  FORCE_INLINE VariantData* getOrCreateData() const {
-    return variantGetOrAddMember(VariantAttorney::getOrCreateData(upstream_),
-                                 adaptString(key_),
-                                 VariantAttorney::getPool(upstream_));
+  VariantData* getOrCreateData() const {
+    auto data = VariantAttorney::getOrCreateData(upstream_);
+    if (!data)
+      return nullptr;
+    return data->getOrAddMember(adaptString(key_),
+                                VariantAttorney::getResourceManager(upstream_));
   }
 
  private:
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/ObjectData.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/ObjectData.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..40e743eed23fc987f908b337016bc4709df7e87f
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/ObjectData.hpp
@@ -0,0 +1,73 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Collection/CollectionData.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+class ObjectData : public CollectionData {
+ public:
+  VariantData* addMember(StringNode* key, ResourceManager* resources) {
+    ARDUINOJSON_ASSERT(key != nullptr);
+    auto it = addSlot(resources);
+    if (it.done())
+      return nullptr;
+
+    it.setKey(key);
+    return it.data();
+  }
+
+  template <typename TAdaptedString>
+  VariantData* addMember(TAdaptedString key, ResourceManager* resources) {
+    ARDUINOJSON_ASSERT(!key.isNull());
+    if (key.isLinked()) {
+      auto it = addSlot(resources);
+      if (!it.done())
+        it.setKey(key.data());
+      return it.data();
+    } else {
+      auto storedKey = resources->saveString(key);
+      if (!storedKey)
+        return nullptr;
+      auto it = addSlot(resources);
+      if (!it.done())
+        it.setKey(storedKey);
+      return it.data();
+    }
+  }
+
+  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);
+  }
+
+ private:
+  template <typename TAdaptedString>
+  iterator findKey(TAdaptedString key, const ResourceManager* resources) const;
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..eed39cadf686acd7ab4aabf9b3b46c511716f45a
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp
@@ -0,0 +1,45 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Object/ObjectData.hpp>
+#include <ArduinoJson/Variant/VariantCompare.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+template <typename TAdaptedString>
+inline VariantData* ObjectData::getMember(
+    TAdaptedString key, const ResourceManager* resources) const {
+  return findKey(key, resources).data();
+}
+
+template <typename TAdaptedString>
+VariantData* ObjectData::getOrAddMember(TAdaptedString key,
+                                        ResourceManager* resources) {
+  auto it = findKey(key, resources);
+  if (!it.done())
+    return it.data();
+  return addMember(key, resources);
+}
+
+template <typename TAdaptedString>
+inline ObjectData::iterator ObjectData::findKey(
+    TAdaptedString key, const ResourceManager* resources) const {
+  if (key.isNull())
+    return iterator();
+  for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
+    if (stringEquals(key, adaptString(it.key())))
+      return it;
+  }
+  return iterator();
+}
+
+template <typename TAdaptedString>
+inline void ObjectData::removeMember(TAdaptedString key,
+                                     ResourceManager* resources) {
+  remove(findKey(key, resources), resources);
+}
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp
index 4dac6c4676a1af0d84f07ec40ce95e2e71ee971a..1dec0a0930ef21f203ce273a3148cfa8973e7b78 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp
index 819bcf5056edf3190160d0beb426c2280739197f..a83155970c588998f5f0f7658b826ec3f984c49a 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp
index c4196fbb044488aa45a7e641244068654b51644f..268026f21b82655137b59f084eba3ca310d332db 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -7,17 +7,30 @@
 #ifdef _MSC_VER  // Visual Studio
 
 #  define FORCE_INLINE  // __forceinline causes C4714 when returning std::string
-#  define NO_INLINE __declspec(noinline)
+
+#  ifndef ARDUINOJSON_DEPRECATED
+#    define ARDUINOJSON_DEPRECATED(msg) __declspec(deprecated(msg))
+#  endif
 
 #elif defined(__GNUC__)  // GCC or Clang
 
 #  define FORCE_INLINE __attribute__((always_inline))
-#  define NO_INLINE __attribute__((noinline))
+
+#  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
-#  define NO_INLINE
+
+#  ifndef ARDUINOJSON_DEPRECATED
+#    define ARDUINOJSON_DEPRECATED(msg)
+#  endif
 
 #endif
 
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp
index b3a6231ccd94a693a3b32a48681d2866c8be126c..294e32479251122bb647ca159389ee9c819254db 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp
index 2bfce365da27e8276dcf04e0ac3f9d21ef52f272..8b74bd9aaabdd7cec469ab6175de064c9b194899 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -11,21 +11,24 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <int Bits>
-struct int_t;
+struct uint_;
 
 template <>
-struct int_t<8> {
-  typedef int8_t type;
+struct uint_<8> {
+  typedef uint8_t type;
 };
 
 template <>
-struct int_t<16> {
-  typedef int16_t type;
+struct uint_<16> {
+  typedef uint16_t type;
 };
 
 template <>
-struct int_t<32> {
-  typedef int32_t type;
+struct uint_<32> {
+  typedef uint32_t type;
 };
 
+template <int Bits>
+using uint_t = typename uint_<Bits>::type;
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp
index cf799da6dc028a7b3e98bf89c012d4c12e06083b..cb07209c3c6def84fe1b2e4e5556ec027729a9e4 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -18,22 +18,22 @@ template <typename T, typename Enable = void>
 struct numeric_limits;
 
 template <typename T>
-struct numeric_limits<T, typename enable_if<is_unsigned<T>::value>::type> {
-  static T lowest() {
+struct numeric_limits<T, enable_if_t<is_unsigned<T>::value>> {
+  static constexpr T lowest() {
     return 0;
   }
-  static T highest() {
+  static constexpr T highest() {
     return T(-1);
   }
 };
 
 template <typename T>
 struct numeric_limits<
-    T, typename enable_if<is_integral<T>::value && is_signed<T>::value>::type> {
-  static T lowest() {
+    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 T highest() {
+  static constexpr T highest() {
     return T(~lowest());
   }
 };
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp
index 45fc8d2f0b8a1d1dae7334dbd0ed6485f7477ed7..95a74f6b9052a44eb5f4e06584780fa225cfedc5 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp
index 3970b83875f8603a100342b16ec05050bc721d46..89a31792a9191cbaddb87a0eda6a33e80ae7dcd3 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp
index 9e66f96a1cc6b492b4b1cb2808b5f77bdf486be9..d65de491ee160b86647895c7befe366e24c58bc8 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp
index dc47581f54f6025332536e3833afe322a743f42b..9104ef23f3fcd205628aaa35cd5ebd350baf4ec5 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp
index 36e5c44a441381f0e899e6a7e36e9dab6bca7c6c..ca565f724b8405427b3be8781b6949c326da687c 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp
@@ -1,13 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #define ARDUINOJSON_CONCAT_(A, B) A##B
 #define ARDUINOJSON_CONCAT2(A, B) ARDUINOJSON_CONCAT_(A, B)
+#define ARDUINOJSON_CONCAT3(A, B, C) \
+  ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT2(A, B), C)
 #define ARDUINOJSON_CONCAT4(A, B, C, D) \
-  ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT2(A, B), ARDUINOJSON_CONCAT2(C, D))
+  ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT3(A, B, C), D)
+#define ARDUINOJSON_CONCAT5(A, B, C, D, E) \
+  ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT4(A, B, C, D), E)
 
 #define ARDUINOJSON_BIN2ALPHA_0000() A
 #define ARDUINOJSON_BIN2ALPHA_0001() B
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp
index 8b61e7c91b22d0c314a2976437de871a0b616815..3004695a34dce8d370341ce74af9616e1d6c7453 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp
@@ -1,11 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include "type_traits/conditional.hpp"
 #include "type_traits/enable_if.hpp"
+#include "type_traits/function_traits.hpp"
 #include "type_traits/integral_constant.hpp"
 #include "type_traits/is_array.hpp"
 #include "type_traits/is_base_of.hpp"
@@ -20,6 +21,6 @@
 #include "type_traits/is_signed.hpp"
 #include "type_traits/is_unsigned.hpp"
 #include "type_traits/make_unsigned.hpp"
-#include "type_traits/make_void.hpp"
 #include "type_traits/remove_const.hpp"
 #include "type_traits/remove_reference.hpp"
+#include "type_traits/void_t.hpp"
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp
index c481bcb83a395ddcb34501f49b696d1fd80d6453..bebd844a34c2fa13aa9a3a07a4de70ba495c1705 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -18,4 +18,8 @@ struct conditional<false, TrueType, FalseType> {
   typedef FalseType type;
 };
 
+template <bool Condition, class TrueType, class FalseType>
+using conditional_t =
+    typename conditional<Condition, TrueType, FalseType>::type;
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp
index 41f63e60b2fce4d63363daeb16b0c2df871efee9..2fd8e91d81eff750bdbf0dd5e6ef27e4e44ebe4d 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp
index 9d0c530cbc989b6163f9f0f2205fe5b1dce733b2..0fed8f4ea007b43ad3ebdce5fe52681fa8921745 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -17,4 +17,7 @@ struct enable_if<true, T> {
   typedef T type;
 };
 
+template <bool Condition, typename T = void>
+using enable_if_t = typename enable_if<Condition, T>::type;
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/function_traits.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/function_traits.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3b798734ddf7fd42ef2bbb7a8de1e16ab8dd0bd3
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/function_traits.hpp
@@ -0,0 +1,27 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Namespace.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+template <typename Sig>
+struct function_traits;
+
+template <typename ReturnType, typename Arg1>
+struct function_traits<ReturnType (*)(Arg1)> {
+  using return_type = ReturnType;
+  using arg1_type = Arg1;
+};
+
+template <typename ReturnType, typename Arg1, typename Arg2>
+struct function_traits<ReturnType (*)(Arg1, Arg2)> {
+  using return_type = ReturnType;
+  using arg1_type = Arg1;
+  using arg2_type = Arg2;
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp
index e4e224ef5167d3bf63a6c174ad155ffeeb8af075..e1db890b02f4980e8e1feed18e3ad076e71e1b4e 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp
index 470031be9080b04b41db0534354ef42a94aedf45..17a4596a6c309d7c38063d541b797ab5dff67af7 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp
index 61820a004b0a866d99641bf6a28a4ded5494ec3f..8cb3c9d32958e988ac6754b016bc7f805f98fa0d 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -20,8 +20,8 @@ class is_base_of {
 
  public:
   static const bool value =
-      sizeof(probe(reinterpret_cast<typename remove_reference<TDerived>::type*>(
-          0))) == sizeof(int);
+      sizeof(probe(reinterpret_cast<remove_reference_t<TDerived>*>(0))) ==
+      sizeof(int);
 };
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp
index f2b3e27a96fa93a2a74e3ec556b0cf8911dbf168..2e36b5650662a12d71581a960b0b6c5e3aeba51f 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp
index 330134fd82044c8d92e72b357d435b163a2df9f8..2adc86a2e3c8694d7f822a24a28c4b47ae2f11d1 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp
index 7c4810f3fc5760a2b6298f38b016e5495c82c37a..c6919d48fef7b7b0f959c55c37307cc0f124ef15 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp
index a66687961b6005af75f1a297613cca56007bd0a7..58a05ba08b052feec1ed37d9c473c4940b68fd7a 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -14,7 +14,7 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename T>
 struct is_enum {
-  static const bool value = is_convertible<T, int>::value &&
+  static const bool value = is_convertible<T, long long>::value &&
                             !is_class<T>::value && !is_integral<T>::value &&
                             !is_floating_point<T>::value;
 };
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp
index 4a58dfa01ca6eb5b81c9a16846d6551331f8a374..c84012b26f581cc530e715488ce7108204b29583 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -12,9 +12,8 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <class T>
 struct is_floating_point
-    : integral_constant<
-          bool,  //
-          is_same<float, typename remove_cv<T>::type>::value ||
-              is_same<double, typename remove_cv<T>::type>::value> {};
+    : integral_constant<bool,  //
+                        is_same<float, remove_cv_t<T>>::value ||
+                            is_same<double, remove_cv_t<T>>::value> {};
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp
index 85d183262188c6c9e0a64105943a6b60ed3fba81..e12b2288484ab883e164a9a0a78544b79989a891 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -15,18 +15,18 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 // clang-format off
 template <typename T>
 struct is_integral : integral_constant<bool,
-    is_same<typename remove_cv<T>::type, signed char>::value ||
-    is_same<typename remove_cv<T>::type, unsigned char>::value ||
-    is_same<typename remove_cv<T>::type, signed short>::value ||
-    is_same<typename remove_cv<T>::type, unsigned short>::value ||
-    is_same<typename remove_cv<T>::type, signed int>::value ||
-    is_same<typename remove_cv<T>::type, unsigned int>::value ||
-    is_same<typename remove_cv<T>::type, signed long>::value ||
-    is_same<typename remove_cv<T>::type, unsigned long>::value ||
-    is_same<typename remove_cv<T>::type, signed long long>::value ||
-    is_same<typename remove_cv<T>::type, unsigned long long>::value ||
-    is_same<typename remove_cv<T>::type, char>::value ||
-    is_same<typename remove_cv<T>::type, bool>::value> {};
+    is_same<remove_cv_t<T>, signed char>::value ||
+    is_same<remove_cv_t<T>, unsigned char>::value ||
+    is_same<remove_cv_t<T>, signed short>::value ||
+    is_same<remove_cv_t<T>, unsigned short>::value ||
+    is_same<remove_cv_t<T>, signed int>::value ||
+    is_same<remove_cv_t<T>, unsigned int>::value ||
+    is_same<remove_cv_t<T>, signed long>::value ||
+    is_same<remove_cv_t<T>, unsigned long>::value ||
+    is_same<remove_cv_t<T>, signed long long>::value ||
+    is_same<remove_cv_t<T>, unsigned long long>::value ||
+    is_same<remove_cv_t<T>, char>::value ||
+    is_same<remove_cv_t<T>, bool>::value> {};
 // clang-format on
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp
index 2c200da2a4378dc02f9f9a5f7cefe5f0b1886171..0d3b1d1e9435c86c7d42f37053446d4493004e2d 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp
index 71e668920313acf69b8d189b5d7fdb11ea841ad3..5e28fb77df8e64db12d0bce257d0547a033e16a4 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp
index eb3cc62498bb1d821035e4ffbe2f0386719aa27f..f5b503b64cf4966304c9b14ed0f71cb297b2e410 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -13,14 +13,14 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 // clang-format off
 template <typename T>
 struct is_signed : integral_constant<bool,
-    is_same<typename remove_cv<T>::type, char>::value ||
-    is_same<typename remove_cv<T>::type, signed char>::value ||
-    is_same<typename remove_cv<T>::type, signed short>::value ||
-    is_same<typename remove_cv<T>::type, signed int>::value ||
-    is_same<typename remove_cv<T>::type, signed long>::value ||
-    is_same<typename remove_cv<T>::type, signed long long>::value ||
-    is_same<typename remove_cv<T>::type, float>::value ||
-    is_same<typename remove_cv<T>::type, double>::value> {};
+    is_same<remove_cv_t<T>, char>::value ||
+    is_same<remove_cv_t<T>, signed char>::value ||
+    is_same<remove_cv_t<T>, signed short>::value ||
+    is_same<remove_cv_t<T>, signed int>::value ||
+    is_same<remove_cv_t<T>, signed long>::value ||
+    is_same<remove_cv_t<T>, signed long long>::value ||
+    is_same<remove_cv_t<T>, float>::value ||
+    is_same<remove_cv_t<T>, double>::value> {};
 // clang-format on
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp
index 803eaea03ff3e5b2744a73705cf71a549f5d4510..6ac611773140a8ee1595a31b2f803739848fa8a6 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -13,12 +13,12 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 // clang-format off
 template <typename T>
 struct is_unsigned : integral_constant<bool,
-    is_same<typename remove_cv<T>::type, unsigned char>::value ||
-    is_same<typename remove_cv<T>::type, unsigned short>::value ||
-    is_same<typename remove_cv<T>::type, unsigned int>::value ||
-    is_same<typename remove_cv<T>::type, unsigned long>::value ||
-    is_same<typename remove_cv<T>::type, unsigned long long>::value ||
-    is_same<typename remove_cv<T>::type, bool>::value> {};
+    is_same<remove_cv_t<T>, unsigned char>::value ||
+    is_same<remove_cv_t<T>, unsigned short>::value ||
+    is_same<remove_cv_t<T>, unsigned int>::value ||
+    is_same<remove_cv_t<T>, unsigned long>::value ||
+    is_same<remove_cv_t<T>, unsigned long long>::value ||
+    is_same<remove_cv_t<T>, bool>::value> {};
 // clang-format on
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp
index 8479848753a21d4de6aeb94e541d04a90aa1b253..6ea6df4c61828ed30c986de4f47c79abc85002cc 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -38,4 +38,7 @@ struct make_unsigned<signed long long> : type_identity<unsigned long long> {};
 template <>
 struct make_unsigned<unsigned long long> : type_identity<unsigned long long> {};
 
+template <typename T>
+using make_unsigned_t = typename make_unsigned<T>::type;
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_void.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_void.hpp
deleted file mode 100644
index b20d424d5051571e8930ebb8f56f5dae47e836c7..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_void.hpp
+++ /dev/null
@@ -1,14 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-template <class = void>
-struct make_void {
-  typedef void type;
-};
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp
index 2592566c1c397b95876ed320250a1afb2f7abef1..5e08dce8d73a873f7914d431f0263ea41c506808 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -18,4 +18,7 @@ struct remove_const<const T> {
   typedef T type;
 };
 
+template <typename T>
+using remove_const_t = typename remove_const<T>::type;
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp
index 70ceed3466723660cf16ea53a9548b89ff4063c4..0c7b633efba17d63f08260f057db23d39b3df182 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -25,4 +25,7 @@ struct remove_cv<const volatile T> {
   typedef T type;
 };
 
+template <typename T>
+using remove_cv_t = typename remove_cv<T>::type;
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp
index 263c730048d9e120c44bd0de66571525a720ba74..c5ddeed6e1b849188f89d8ab8baefe49a1d4508c 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -18,4 +18,7 @@ struct remove_reference<T&> {
   typedef T type;
 };
 
+template <typename T>
+using remove_reference_t = typename remove_reference<T>::type;
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp
index 4481bc7e4faa8e0150d911679731548247191331..0dd1a9f9ceb24da3595db16177024fc6d55a0960 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/void_t.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/void_t.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..722202503973321c41fef659dd60f7b06d8d5c1c
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/void_t.hpp
@@ -0,0 +1,20 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Namespace.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+template <typename...>
+struct make_void {
+  using type = void;
+};
+
+template <typename... Args>
+using void_t = typename make_void<Args...>::type;
+// NOTE: using void_t = void; doesn't work on GCC 4.8
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp
index 13fb3e706386d84f96c6ef187e98db49e6e20cb6..caac765bff9fe5d8bb911deee07848a25ef471e5 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -8,9 +8,26 @@
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
+using nullptr_t = decltype(nullptr);
+
 template <class T>
-T&& forward(typename remove_reference<T>::type& t) noexcept {
+T&& forward(remove_reference_t<T>& t) noexcept {
   return static_cast<T&&>(t);
 }
 
+template <class T>
+remove_reference_t<T>&& move(T&& t) {
+  return static_cast<remove_reference_t<T>&&>(t);
+}
+
+// Polyfull for std::swap
+// Don't use the name "swap" because it makes calls ambiguous for types in the
+// detail namespace
+template <class T>
+void swap_(T& a, T& b) {
+  T tmp = move(a);
+  a = move(b);
+  b = move(tmp);
+}
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp
index b38a22dfcb274647df5d0e038548b00e1e821d8c..e70ea1b40afd42fab6cf9a343813e60f213d0ca6 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp
index f6ba225dfbf831f31c776b0bf6d8fefb4f37b548..e35d7e91b4f43483c43d008afb983a409dde3a04 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp
index c643248798da271e38e99bbbc948e2b7c14b56fb..d3ebd173f5380e51801a1026dd74c65268a09fea 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -13,8 +13,10 @@ class Writer<::String, void> {
   static const size_t bufferCapacity = ARDUINOJSON_STRING_BUFFER_SIZE;
 
  public:
-  explicit Writer(::String& str) : destination_(&str) {
-    size_ = 0;
+  explicit Writer(::String& str) : destination_(&str), size_(0) {
+    // clear the string but don't use "" to avoid useless allocation
+    // https://cpp4arduino.com/2018/11/21/eight-tips-to-use-the-string-class-efficiently.html
+    str = static_cast<const char*>(0);
   }
 
   ~Writer() {
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp
index ff35def7abec7e4e1f0c542ca6052b994f92ccf4..7492e2129854fce8cc75066316ebbd37ce3b9bdd 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp
index 49f6a6e96a6c11c4c4c171c81c9d42eb8e429b2d..55bfe004890b9ab53b00dc7b88cf2829af272a3f 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,9 +9,8 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TDestination>
-class Writer<
-    TDestination,
-    typename enable_if<is_base_of<::Print, TDestination>::value>::type> {
+class Writer<TDestination,
+             enable_if_t<is_base_of<::Print, TDestination>::value>> {
  public:
   explicit Writer(::Print& print) : print_(&print) {}
 
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp
index 169ebe1941e9b5c04db5629eee7029a395d4305f..042aac7f87793539ee3c52c1143c9a2d058158e0 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp
index fdfead73896dfc72b7b0c5cd9f2fe1345ec875c0..abb15a533ddcca771217ae74c281026e0fac5069 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,9 +9,8 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TDestination>
-class Writer<
-    TDestination,
-    typename enable_if<is_base_of<std::ostream, TDestination>::value>::type> {
+class Writer<TDestination,
+             enable_if_t<is_base_of<std::ostream, TDestination>::value>> {
  public:
   explicit Writer(std::ostream& os) : os_(&os) {}
 
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp
index 1f8afaa8863a64a6f4b12063a73b8eaeab1e871d..7acd24c42e25b64ed64f0412f0e2685f68987e61 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,22 +9,21 @@
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
-template <class...>
-using void_t = void;
-
 template <class T, typename = void>
 struct is_std_string : false_type {};
 
 template <class T>
 struct is_std_string<
-    T, void_t<decltype(T().push_back('a')), decltype(T().append(""))>>
-    : true_type {};
+    T, enable_if_t<is_same<void, decltype(T().push_back('a'))>::value &&
+                   is_same<T&, decltype(T().append(""))>::value>> : true_type {
+};
 
 template <typename TDestination>
-class Writer<TDestination,
-             typename enable_if<is_std_string<TDestination>::value>::type> {
+class Writer<TDestination, enable_if_t<is_std_string<TDestination>::value>> {
  public:
-  Writer(TDestination& str) : str_(&str) {}
+  Writer(TDestination& str) : str_(&str) {
+    str.clear();
+  }
 
   size_t write(uint8_t c) {
     str_->push_back(static_cast<char>(c));
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp
index 5a71e104bd2a90c522478e7447fba75a00f8c0af..fa2c664565c4c79d499a153f11f3314dcd6f8ec8 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp
@@ -1,19 +1,19 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Serialization/Writers/DummyWriter.hpp>
-#include <ArduinoJson/Variant/VariantFunctions.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <template <typename> class TSerializer>
 size_t measure(ArduinoJson::JsonVariantConst source) {
   DummyWriter dp;
-  TSerializer<DummyWriter> serializer(dp);
-  return variantAccept(VariantAttorney::getData(source), serializer);
+  TSerializer<DummyWriter> serializer(
+      dp, VariantAttorney::getResourceManager(source));
+  return VariantData::accept(VariantAttorney::getData(source), serializer);
 }
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp
index 1014a7d6290251a77654269571465cb6eb4e6f5a..e393d2474d649eac028d802eeb436934f38cd8ff 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp
@@ -1,18 +1,18 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Serialization/Writer.hpp>
-#include <ArduinoJson/Variant/VariantFunctions.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <template <typename> class TSerializer, typename TWriter>
 size_t doSerialize(ArduinoJson::JsonVariantConst source, TWriter writer) {
-  TSerializer<TWriter> serializer(writer);
-  return variantAccept(VariantAttorney::getData(source), serializer);
+  TSerializer<TWriter> serializer(writer,
+                                  VariantAttorney::getResourceManager(source));
+  return VariantData::accept(VariantAttorney::getData(source), serializer);
 }
 
 template <template <typename> class TSerializer, typename TDestination>
@@ -23,17 +23,15 @@ size_t serialize(ArduinoJson::JsonVariantConst source,
 }
 
 template <template <typename> class TSerializer>
-typename enable_if<!TSerializer<StaticStringWriter>::producesText, size_t>::type
-serialize(ArduinoJson::JsonVariantConst source, void* buffer,
-          size_t bufferSize) {
+enable_if_t<!TSerializer<StaticStringWriter>::producesText, size_t> serialize(
+    ArduinoJson::JsonVariantConst source, void* buffer, size_t bufferSize) {
   StaticStringWriter writer(reinterpret_cast<char*>(buffer), bufferSize);
   return doSerialize<TSerializer>(source, writer);
 }
 
 template <template <typename> class TSerializer>
-typename enable_if<TSerializer<StaticStringWriter>::producesText, size_t>::type
-serialize(ArduinoJson::JsonVariantConst source, void* buffer,
-          size_t bufferSize) {
+enable_if_t<TSerializer<StaticStringWriter>::producesText, size_t> serialize(
+    ArduinoJson::JsonVariantConst source, void* buffer, size_t bufferSize) {
   StaticStringWriter writer(reinterpret_cast<char*>(buffer), bufferSize);
   size_t n = doSerialize<TSerializer>(source, writer);
   // add null-terminator for text output (not counted in the size)
@@ -43,7 +41,7 @@ serialize(ArduinoJson::JsonVariantConst source, void* buffer,
 }
 
 template <template <typename> class TSerializer, typename TChar, size_t N>
-typename enable_if<IsChar<TChar>::value, size_t>::type serialize(
+enable_if_t<IsChar<TChar>::value, size_t> serialize(
     ArduinoJson::JsonVariantConst source, TChar (&buffer)[N]) {
   return serialize<TSerializer>(source, buffer, N);
 }
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp
deleted file mode 100644
index bcd34898beef085c948160a9249968a6680ab93d..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp
+++ /dev/null
@@ -1,72 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Memory/MemoryPool.hpp>
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-class StringCopier {
- public:
-  StringCopier(MemoryPool* pool) : pool_(pool) {}
-
-  void startString() {
-    pool_->getFreeZone(&ptr_, &capacity_);
-    size_ = 0;
-    if (capacity_ == 0)
-      pool_->markAsOverflowed();
-  }
-
-  JsonString save() {
-    ARDUINOJSON_ASSERT(ptr_);
-    ARDUINOJSON_ASSERT(size_ < capacity_);  // needs room for the terminator
-    return JsonString(pool_->saveStringFromFreeZone(size_), size_,
-                      JsonString::Copied);
-  }
-
-  void append(const char* s) {
-    while (*s)
-      append(*s++);
-  }
-
-  void append(const char* s, size_t n) {
-    while (n-- > 0)
-      append(*s++);
-  }
-
-  void append(char c) {
-    if (size_ + 1 < capacity_)
-      ptr_[size_++] = c;
-    else
-      pool_->markAsOverflowed();
-  }
-
-  bool isValid() const {
-    return !pool_->overflowed();
-  }
-
-  size_t size() const {
-    return size_;
-  }
-
-  JsonString str() const {
-    ARDUINOJSON_ASSERT(ptr_);
-    ARDUINOJSON_ASSERT(size_ < capacity_);
-    ptr_[size_] = 0;
-    return JsonString(ptr_, size_, JsonString::Copied);
-  }
-
- private:
-  MemoryPool* pool_;
-
-  // These fields aren't initialized by the constructor but startString()
-  //
-  // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.UninitializedObject)
-  char* ptr_;
-  // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.UninitializedObject)
-  size_t size_, capacity_;
-};
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringMover.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringMover.hpp
deleted file mode 100644
index 584b25314535a932bb387df437381b580c0c672d..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringMover.hpp
+++ /dev/null
@@ -1,48 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Namespace.hpp>
-#include <ArduinoJson/Strings/JsonString.hpp>
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-class StringMover {
- public:
-  StringMover(char* ptr) : writePtr_(ptr) {}
-
-  void startString() {
-    startPtr_ = writePtr_;
-  }
-
-  FORCE_INLINE JsonString save() {
-    JsonString s = str();
-    writePtr_++;
-    return s;
-  }
-
-  void append(char c) {
-    *writePtr_++ = c;
-  }
-
-  bool isValid() const {
-    return true;
-  }
-
-  JsonString str() const {
-    writePtr_[0] = 0;  // terminator
-    return JsonString(startPtr_, size(), JsonString::Linked);
-  }
-
-  size_t size() const {
-    return size_t(writePtr_ - startPtr_);
-  }
-
- private:
-  char* writePtr_;
-  char* startPtr_;
-};
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringStorage.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringStorage.hpp
deleted file mode 100644
index 0b1f77cc8905de284519406473890c2fc821a450..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/StringStorage/StringStorage.hpp
+++ /dev/null
@@ -1,24 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/StringStorage/StringCopier.hpp>
-#include <ArduinoJson/StringStorage/StringMover.hpp>
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-template <typename TInput>
-StringCopier makeStringStorage(TInput&, MemoryPool* pool) {
-  ARDUINOJSON_ASSERT(pool != 0);
-  return StringCopier(pool);
-}
-
-template <typename TChar>
-StringMover makeStringStorage(
-    TChar* input, MemoryPool*,
-    typename enable_if<!is_const<TChar>::value>::type* = 0) {
-  return StringMover(reinterpret_cast<char*>(input));
-}
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashString.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashString.hpp
index 4989315a2b7f76fc512f99bb8ef9728dfd4449c1..fafed96338846327248c71d7323628f5f9954bbc 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashString.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashString.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -26,6 +26,10 @@ class FlashString {
     return static_cast<char>(pgm_read_byte(str_ + i));
   }
 
+  const char* data() const {
+    return nullptr;
+  }
+
   size_t size() const {
     return size_;
   }
@@ -59,8 +63,8 @@ class FlashString {
     ::memcpy_P(p, s.str_, n);
   }
 
-  StringStoragePolicy::Copy storagePolicy() const {
-    return StringStoragePolicy::Copy();
+  bool isLinked() const {
+    return false;
   }
 
  private:
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonString.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonString.hpp
index 5db7c208d8274513ba59235cf58eadc880b40f44..b98c4078ecd0755dfbae9b3161af0dd40e9c368e 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonString.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonString.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -15,9 +15,8 @@ class JsonStringAdapter : public SizedRamString {
   JsonStringAdapter(const JsonString& s)
       : SizedRamString(s.c_str(), s.size()), linked_(s.isLinked()) {}
 
-  StringStoragePolicy::LinkOrCopy storagePolicy() const {
-    StringStoragePolicy::LinkOrCopy policy = {linked_};
-    return policy;
+  bool isLinked() const {
+    return linked_;
   }
 
  private:
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamString.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamString.hpp
index 0fa2d5be1d32be7255a0aab926af7b5b03e9a431..c27c4b3d6fd355c6c0810910e8cc4afbee5c4e46 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamString.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamString.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,7 +9,6 @@
 
 #include <ArduinoJson/Polyfills/assert.hpp>
 #include <ArduinoJson/Polyfills/attributes.hpp>
-#include <ArduinoJson/Strings/StoragePolicy.hpp>
 #include <ArduinoJson/Strings/StringAdapter.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
@@ -54,8 +53,8 @@ class ZeroTerminatedRamString {
     return stringCompare(a, b) == 0;
   }
 
-  StringStoragePolicy::Copy storagePolicy() const {
-    return StringStoragePolicy::Copy();
+  bool isLinked() const {
+    return false;
   }
 
  protected:
@@ -63,7 +62,7 @@ class ZeroTerminatedRamString {
 };
 
 template <typename TChar>
-struct StringAdapter<TChar*, typename enable_if<IsChar<TChar>::value>::type> {
+struct StringAdapter<TChar*, enable_if_t<IsChar<TChar>::value>> {
   typedef ZeroTerminatedRamString AdaptedString;
 
   static AdaptedString adapt(const TChar* p) {
@@ -72,7 +71,7 @@ struct StringAdapter<TChar*, typename enable_if<IsChar<TChar>::value>::type> {
 };
 
 template <typename TChar, size_t N>
-struct StringAdapter<TChar[N], typename enable_if<IsChar<TChar>::value>::type> {
+struct StringAdapter<TChar[N], enable_if_t<IsChar<TChar>::value>> {
   typedef ZeroTerminatedRamString AdaptedString;
 
   static AdaptedString adapt(const TChar* p) {
@@ -84,8 +83,8 @@ class StaticStringAdapter : public ZeroTerminatedRamString {
  public:
   StaticStringAdapter(const char* str) : ZeroTerminatedRamString(str) {}
 
-  StringStoragePolicy::Link storagePolicy() const {
-    return StringStoragePolicy::Link();
+  bool isLinked() const {
+    return true;
   }
 };
 
@@ -122,8 +121,8 @@ class SizedRamString {
     return str_;
   }
 
-  StringStoragePolicy::Copy storagePolicy() const {
-    return StringStoragePolicy::Copy();
+  bool isLinked() const {
+    return false;
   }
 
  protected:
@@ -132,8 +131,7 @@ class SizedRamString {
 };
 
 template <typename TChar>
-struct SizedStringAdapter<TChar*,
-                          typename enable_if<IsChar<TChar>::value>::type> {
+struct SizedStringAdapter<TChar*, enable_if_t<IsChar<TChar>::value>> {
   typedef SizedRamString AdaptedString;
 
   static AdaptedString adapt(const TChar* p, size_t n) {
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringObject.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringObject.hpp
index d52b0c7fb2084026bcf792b60ad5f8ea8507aff2..095f40dce876f0707a73b9c5247a1b876827aafa 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringObject.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringObject.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -13,9 +13,8 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 template <typename T>
 struct StringAdapter<
     T,
-    typename enable_if<
-        (string_traits<T>::has_cstr || string_traits<T>::has_data) &&
-        (string_traits<T>::has_length || string_traits<T>::has_size)>::type> {
+    enable_if_t<(string_traits<T>::has_cstr || string_traits<T>::has_data) &&
+                (string_traits<T>::has_length || string_traits<T>::has_size)>> {
   typedef SizedRamString AdaptedString;
 
   static AdaptedString adapt(const T& s) {
@@ -24,26 +23,24 @@ struct StringAdapter<
 
  private:
   template <typename U>
-  static typename enable_if<string_traits<U>::has_size, size_t>::type get_size(
-      const U& s) {
+  static enable_if_t<string_traits<U>::has_size, size_t> get_size(const U& s) {
     return s.size();
   }
 
   template <typename U>
-  static typename enable_if<!string_traits<U>::has_size, size_t>::type get_size(
-      const U& s) {
+  static enable_if_t<!string_traits<U>::has_size, size_t> get_size(const U& s) {
     return s.length();
   }
 
   template <typename U>
-  static typename enable_if<string_traits<U>::has_data, const char*>::type
-  get_data(const U& s) {
+  static enable_if_t<string_traits<U>::has_data, const char*> get_data(
+      const U& s) {
     return s.data();
   }
 
   template <typename U>
-  static typename enable_if<!string_traits<U>::has_data, const char*>::type
-  get_data(const U& s) {
+  static enable_if_t<!string_traits<U>::has_data, const char*> get_data(
+      const U& s) {
     return s.c_str();
   }
 };
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp
index 31e4601dace9551283926e0baf3142aeca195261..45ef799523724b0c3072c462920647e30dee6d11 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -13,8 +13,7 @@ template <typename T, typename Enable = void>
 struct IsString : false_type {};
 
 template <typename T>
-struct IsString<
-    T, typename make_void<typename StringAdapter<T>::AdaptedString>::type>
+struct IsString<T, void_t<typename StringAdapter<T>::AdaptedString>>
     : true_type {};
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/JsonString.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/JsonString.hpp
index 538dbc8ad5e04de8577e97858c2ba49bfeba1b58..2ecb697bf6bb09e858837cc6919e2ce4bd773f3f 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/JsonString.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/JsonString.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -11,7 +11,7 @@
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // A string.
-// https://arduinojson.org/v6/api/jsonstring/
+// https://arduinojson.org/v7/api/jsonstring/
 class JsonString {
  public:
   enum Ownership { Copied, Linked };
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StoragePolicy.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StoragePolicy.hpp
deleted file mode 100644
index b8b3b6a6cdc30fcc90bedd83e3041a5280ab49fc..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StoragePolicy.hpp
+++ /dev/null
@@ -1,18 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-namespace StringStoragePolicy {
-
-struct Link {};
-struct Copy {};
-struct LinkOrCopy {
-  bool link;
-};
-}  // namespace StringStoragePolicy
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapter.hpp
index 7ebad0b5295389d9f36cbd9572ef3784319de6ce..9cf2e6fdd5892bb6f969bc17c8478c7426ab0db0 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp
index edf881cef65369ffbbe8684164d7d6ec1e723da0..5da554ce93f39b954b237d6a31858e86db4658b9 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -16,8 +16,7 @@
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 template <typename TAdaptedString1, typename TAdaptedString2>
-typename enable_if<TAdaptedString1::typeSortKey <= TAdaptedString2::typeSortKey,
-                   int>::type
+enable_if_t<TAdaptedString1::typeSortKey <= TAdaptedString2::typeSortKey, int>
 stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) {
   ARDUINOJSON_ASSERT(!s1.isNull());
   ARDUINOJSON_ASSERT(!s2.isNull());
@@ -36,15 +35,13 @@ stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) {
 }
 
 template <typename TAdaptedString1, typename TAdaptedString2>
-typename enable_if<
-    (TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), int>::type
+enable_if_t<(TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), int>
 stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) {
   return -stringCompare(s2, s1);
 }
 
 template <typename TAdaptedString1, typename TAdaptedString2>
-typename enable_if<TAdaptedString1::typeSortKey <= TAdaptedString2::typeSortKey,
-                   bool>::type
+enable_if_t<TAdaptedString1::typeSortKey <= TAdaptedString2::typeSortKey, bool>
 stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) {
   ARDUINOJSON_ASSERT(!s1.isNull());
   ARDUINOJSON_ASSERT(!s2.isNull());
@@ -60,8 +57,7 @@ stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) {
 }
 
 template <typename TAdaptedString1, typename TAdaptedString2>
-typename enable_if<
-    (TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), bool>::type
+enable_if_t<(TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), bool>
 stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) {
   return stringEquals(s2, s1);
 }
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringTraits.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringTraits.hpp
index 45804cd15a14649fd931ec4902922564dd03c4d4..797762b98c5c1f3a9c79dd9283065b9f4ecb2309 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringTraits.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Strings/StringTraits.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -19,10 +19,8 @@ template <class T, class = void>
 struct has_cstr : false_type {};
 
 template <class T>
-struct has_cstr<T,
-                typename enable_if<is_same<decltype(declval<const T>().c_str()),
-                                           const char*>::value>::type>
-    : true_type {};
+struct has_cstr<T, enable_if_t<is_same<decltype(declval<const T>().c_str()),
+                                       const char*>::value>> : true_type {};
 
 // const char* data() const
 // - std::string
@@ -33,12 +31,10 @@ template <class T, class = void>
 struct has_data : false_type {};
 
 template <class T>
-struct has_data<T,
-                typename enable_if<is_same<decltype(declval<const T>().data()),
-                                           const char*>::value>::type>
-    : true_type {};
+struct has_data<T, enable_if_t<is_same<decltype(declval<const T>().data()),
+                                       const char*>::value>> : true_type {};
 
-// size_t length() const
+// unsigned int length() const
 // - String
 
 template <class T, class = void>
@@ -46,8 +42,7 @@ struct has_length : false_type {};
 
 template <class T>
 struct has_length<
-    T, typename enable_if<
-           is_same<decltype(declval<const T>().length()), size_t>::value>::type>
+    T, enable_if_t<is_unsigned<decltype(declval<const T>().length())>::value>>
     : true_type {};
 
 // size_t size() const
@@ -60,8 +55,7 @@ struct has_size : false_type {};
 
 template <class T>
 struct has_size<
-    T, typename enable_if<
-           is_same<decltype(declval<const T>().size()), size_t>::value>::type>
+    T, enable_if_t<is_same<decltype(declval<const T>().size()), size_t>::value>>
     : true_type {};
 
 }  // namespace string_traits_impl
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp
index 8a63d9969ba4c6972cc0051398d51194cf8f63fd..0275655eb73bb6b56041c303fc0a734365ebdeb6 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -17,10 +17,7 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 // clang-format off
 template <typename T1, typename T2>
-class InvalidConversion;  // Error here? See https://arduinojson.org/v6/invalid-conversion/
+class InvalidConversion;  // Error here? See https://arduinojson.org/v7/invalid-conversion/
 // clang-format on
 
-template <typename T>
-struct ConverterNeedsWriteableRef;
-
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp
index 0708f52f7ef343c31e5acda2572f25002b039d51..3b1c7340fb3a307987a998f60ae0c599090753d6 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp
@@ -1,12 +1,13 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Json/JsonSerializer.hpp>
+#include <ArduinoJson/Memory/StringBuilder.hpp>
+#include <ArduinoJson/Polyfills/utility.hpp>
 #include <ArduinoJson/Variant/JsonVariantConst.hpp>
-#include <ArduinoJson/Variant/VariantFunctions.hpp>
 
 #if ARDUINOJSON_ENABLE_STD_STRING
 #  include <string>
@@ -20,39 +21,50 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 template <typename T, typename Enable>
 struct Converter {
+  static_assert(!detail::is_same<T, char>::value,
+                "type 'char' is not supported, use 'signed char', 'unsigned "
+                "char' or another integer type instead");
+
   static void toJson(const T& src, JsonVariant dst) {
     // clang-format off
-    convertToJson(src, dst); // Error here? See https://arduinojson.org/v6/unsupported-set/
+    convertToJson(src, dst); // Error here? See https://arduinojson.org/v7/unsupported-set/
     // clang-format on
   }
 
   static T fromJson(JsonVariantConst src) {
+    static_assert(!detail::is_same<T, char*>::value,
+                  "type 'char*' is not supported, use 'const char*' instead");
+
     // clang-format off
-    T result; // Error here? See https://arduinojson.org/v6/non-default-constructible/
-    convertFromJson(src, result);  // Error here? See https://arduinojson.org/v6/unsupported-as/
+    T result; // Error here? See https://arduinojson.org/v7/non-default-constructible/
+    convertFromJson(src, result);  // Error here? See https://arduinojson.org/v7/unsupported-as/
     // clang-format on
     return result;
   }
 
   static bool checkJson(JsonVariantConst src) {
+    static_assert(!detail::is_same<T, char*>::value,
+                  "type 'char*' is not supported, use 'const char*' instead");
+
     T dummy = T();
     // clang-format off
-    return canConvertFromJson(src, dummy);  // Error here? See https://arduinojson.org/v6/unsupported-is/
+    return canConvertFromJson(src, dummy);  // Error here? See https://arduinojson.org/v7/unsupported-is/
     // clang-format on
   }
 };
 
 template <typename T>
-struct Converter<
-    T, typename detail::enable_if<detail::is_integral<T>::value &&
-                                  !detail::is_same<bool, T>::value &&
-                                  !detail::is_same<char, T>::value>::type>
+struct Converter<T, detail::enable_if_t<detail::is_integral<T>::value &&
+                                        !detail::is_same<bool, T>::value &&
+                                        !detail::is_same<char, T>::value>>
     : private detail::VariantAttorney {
-  static void toJson(T src, JsonVariant dst) {
-    auto data = getData(dst);
+  static bool toJson(T src, JsonVariant dst) {
     ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T);
-    if (data)
-      data->setInteger(src);
+    auto data = getData(dst);
+    if (!data)
+      return false;
+    data->setInteger(src, getResourceManager(dst));
+    return true;
   }
 
   static T fromJson(JsonVariantConst src) {
@@ -68,10 +80,10 @@ struct Converter<
 };
 
 template <typename T>
-struct Converter<T, typename detail::enable_if<detail::is_enum<T>::value>::type>
+struct Converter<T, detail::enable_if_t<detail::is_enum<T>::value>>
     : private detail::VariantAttorney {
-  static void toJson(T src, JsonVariant dst) {
-    dst.set(static_cast<JsonInteger>(src));
+  static bool toJson(T src, JsonVariant dst) {
+    return dst.set(static_cast<JsonInteger>(src));
   }
 
   static T fromJson(JsonVariantConst src) {
@@ -87,10 +99,12 @@ struct Converter<T, typename detail::enable_if<detail::is_enum<T>::value>::type>
 
 template <>
 struct Converter<bool> : private detail::VariantAttorney {
-  static void toJson(bool src, JsonVariant dst) {
+  static bool toJson(bool src, JsonVariant dst) {
     auto data = getData(dst);
-    if (data)
-      data->setBoolean(src);
+    if (!data)
+      return false;
+    data->setBoolean(src, getResourceManager(dst));
+    return true;
   }
 
   static bool fromJson(JsonVariantConst src) {
@@ -105,13 +119,14 @@ struct Converter<bool> : private detail::VariantAttorney {
 };
 
 template <typename T>
-struct Converter<
-    T, typename detail::enable_if<detail::is_floating_point<T>::value>::type>
+struct Converter<T, detail::enable_if_t<detail::is_floating_point<T>::value>>
     : private detail::VariantAttorney {
-  static void toJson(T src, JsonVariant dst) {
+  static bool toJson(T src, JsonVariant dst) {
     auto data = getData(dst);
-    if (data)
-      data->setFloat(static_cast<JsonFloat>(src));
+    if (!data)
+      return false;
+    data->setFloat(static_cast<JsonFloat>(src), getResourceManager(dst));
+    return true;
   }
 
   static T fromJson(JsonVariantConst src) {
@@ -128,7 +143,8 @@ struct Converter<
 template <>
 struct Converter<const char*> : private detail::VariantAttorney {
   static void toJson(const char* src, JsonVariant dst) {
-    variantSetString(getData(dst), detail::adaptString(src), getPool(dst));
+    detail::VariantData::setString(getData(dst), detail::adaptString(src),
+                                   getResourceManager(dst));
   }
 
   static const char* fromJson(JsonVariantConst src) {
@@ -145,7 +161,8 @@ struct Converter<const char*> : private detail::VariantAttorney {
 template <>
 struct Converter<JsonString> : private detail::VariantAttorney {
   static void toJson(JsonString src, JsonVariant dst) {
-    variantSetString(getData(dst), detail::adaptString(src), getPool(dst));
+    detail::VariantData::setString(getData(dst), detail::adaptString(src),
+                                   getResourceManager(dst));
   }
 
   static JsonString fromJson(JsonVariantConst src) {
@@ -160,46 +177,31 @@ struct Converter<JsonString> : private detail::VariantAttorney {
 };
 
 template <typename T>
-inline typename detail::enable_if<detail::IsString<T>::value, bool>::type
-convertToJson(const T& src, JsonVariant dst) {
+inline detail::enable_if_t<detail::IsString<T>::value> convertToJson(
+    const T& src, JsonVariant dst) {
   using namespace detail;
   auto data = VariantAttorney::getData(dst);
-  auto pool = VariantAttorney::getPool(dst);
-  return variantSetString(data, adaptString(src), pool);
+  auto resources = VariantAttorney::getResourceManager(dst);
+  detail::VariantData::setString(data, adaptString(src), resources);
 }
 
-template <>
-struct Converter<SerializedValue<const char*>>
-    : private detail::VariantAttorney {
-  static void toJson(SerializedValue<const char*> src, JsonVariant dst) {
-    auto data = getData(dst);
-    if (data)
-      data->setLinkedRaw(src);
-  }
-};
-
 // SerializedValue<std::string>
 // SerializedValue<String>
 // SerializedValue<const __FlashStringHelper*>
 template <typename T>
-struct Converter<
-    SerializedValue<T>,
-    typename detail::enable_if<!detail::is_same<const char*, T>::value>::type>
-    : private detail::VariantAttorney {
+struct Converter<SerializedValue<T>> : private detail::VariantAttorney {
   static void toJson(SerializedValue<T> src, JsonVariant dst) {
-    auto data = getData(dst);
-    auto pool = getPool(dst);
-    if (data)
-      data->storeOwnedRaw(src, pool);
+    detail::VariantData::setRawString(getData(dst), src,
+                                      getResourceManager(dst));
   }
 };
 
 template <>
-struct Converter<decltype(nullptr)> : private detail::VariantAttorney {
-  static void toJson(decltype(nullptr), JsonVariant dst) {
-    variantSetNull(getData(dst));
+struct Converter<detail::nullptr_t> : private detail::VariantAttorney {
+  static void toJson(detail::nullptr_t, JsonVariant dst) {
+    detail::VariantData::setNull(getData(dst), getResourceManager(dst));
   }
-  static decltype(nullptr) fromJson(JsonVariantConst) {
+  static detail::nullptr_t fromJson(JsonVariantConst) {
     return nullptr;
   }
   static bool checkJson(JsonVariantConst src) {
@@ -211,61 +213,52 @@ struct Converter<decltype(nullptr)> : private detail::VariantAttorney {
 #if ARDUINOJSON_ENABLE_ARDUINO_STREAM
 
 namespace detail {
-class MemoryPoolPrint : public Print {
+class StringBuilderPrint : public Print {
  public:
-  MemoryPoolPrint(MemoryPool* pool) : pool_(pool), size_(0) {
-    pool->getFreeZone(&string_, &capacity_);
+  StringBuilderPrint(ResourceManager* resources) : copier_(resources) {
+    copier_.startString();
   }
 
-  JsonString str() {
-    ARDUINOJSON_ASSERT(size_ < capacity_);
-    return JsonString(pool_->saveStringFromFreeZone(size_), size_,
-                      JsonString::Copied);
+  StringNode* save() {
+    ARDUINOJSON_ASSERT(!overflowed());
+    return copier_.save();
   }
 
   size_t write(uint8_t c) {
-    if (size_ >= capacity_)
-      return 0;
-
-    string_[size_++] = char(c);
-    return 1;
+    copier_.append(char(c));
+    return copier_.isValid() ? 1 : 0;
   }
 
   size_t write(const uint8_t* buffer, size_t size) {
-    if (size_ + size >= capacity_) {
-      size_ = capacity_;  // mark as overflowed
-      return 0;
+    for (size_t i = 0; i < size; i++) {
+      copier_.append(char(buffer[i]));
+      if (!copier_.isValid())
+        return i;
     }
-    memcpy(&string_[size_], buffer, size);
-    size_ += size;
     return size;
   }
 
   bool overflowed() const {
-    return size_ >= capacity_;
+    return !copier_.isValid();
   }
 
  private:
-  MemoryPool* pool_;
-  size_t size_;
-  char* string_;
-  size_t capacity_;
+  StringBuilder copier_;
 };
 }  // namespace detail
 
 inline void convertToJson(const ::Printable& src, JsonVariant dst) {
-  auto pool = detail::VariantAttorney::getPool(dst);
+  auto resources = detail::VariantAttorney::getResourceManager(dst);
   auto data = detail::VariantAttorney::getData(dst);
-  if (!pool || !data)
+  if (!resources || !data)
     return;
-  detail::MemoryPoolPrint print(pool);
+  detail::StringBuilderPrint print(resources);
   src.printTo(print);
   if (print.overflowed()) {
-    pool->markAsOverflowed();
     data->setNull();
     return;
   }
-  data->setString(print.str());
+  data->setOwnedString(print.save());
 }
 
 #endif
@@ -316,17 +309,88 @@ inline bool canConvertFromJson(JsonVariantConst src, const std::string_view&) {
 
 #endif
 
-namespace detail {
-template <typename T>
-struct ConverterNeedsWriteableRef {
- protected:  // <- to avoid GCC's "all member functions in class are private"
-  static int probe(T (*f)(ArduinoJson::JsonVariant));
-  static char probe(T (*f)(ArduinoJson::JsonVariantConst));
+template <>
+struct Converter<JsonArrayConst> : private detail::VariantAttorney {
+  static void toJson(JsonArrayConst src, JsonVariant dst) {
+    if (src.isNull())
+      dst.set(nullptr);
+    else
+      dst.to<JsonArray>().set(src);
+  }
 
- public:
-  static const bool value =
-      sizeof(probe(Converter<T>::fromJson)) == sizeof(int);
+  static JsonArrayConst fromJson(JsonVariantConst src) {
+    auto data = getData(src);
+    auto array = data ? data->asArray() : nullptr;
+    return JsonArrayConst(array, getResourceManager(src));
+  }
+
+  static bool checkJson(JsonVariantConst src) {
+    auto data = getData(src);
+    return data && data->isArray();
+  }
+};
+
+template <>
+struct Converter<JsonArray> : private detail::VariantAttorney {
+  static void toJson(JsonVariantConst src, JsonVariant dst) {
+    if (src.isNull())
+      dst.set(nullptr);
+    else
+      dst.to<JsonArray>().set(src);
+  }
+
+  static JsonArray fromJson(JsonVariant src) {
+    auto data = getData(src);
+    auto resources = getResourceManager(src);
+    return JsonArray(data != 0 ? data->asArray() : 0, resources);
+  }
+
+  static bool checkJson(JsonVariant src) {
+    auto data = getData(src);
+    return data && data->isArray();
+  }
+};
+
+template <>
+struct Converter<JsonObjectConst> : private detail::VariantAttorney {
+  static void toJson(JsonVariantConst src, JsonVariant dst) {
+    if (src.isNull())
+      dst.set(nullptr);
+    else
+      dst.to<JsonObject>().set(src);
+  }
+
+  static JsonObjectConst fromJson(JsonVariantConst src) {
+    auto data = getData(src);
+    auto object = data != 0 ? data->asObject() : nullptr;
+    return JsonObjectConst(object, getResourceManager(src));
+  }
+
+  static bool checkJson(JsonVariantConst src) {
+    auto data = getData(src);
+    return data && data->isObject();
+  }
+};
+
+template <>
+struct Converter<JsonObject> : private detail::VariantAttorney {
+  static void toJson(JsonVariantConst src, JsonVariant dst) {
+    if (src.isNull())
+      dst.set(nullptr);
+    else
+      dst.to<JsonObject>().set(src);
+  }
+
+  static JsonObject fromJson(JsonVariant src) {
+    auto data = getData(src);
+    auto resources = getResourceManager(src);
+    return JsonObject(data != 0 ? data->asObject() : 0, resources);
+  }
+
+  static bool checkJson(JsonVariant src) {
+    auto data = getData(src);
+    return data && data->isObject();
+  }
 };
-}  // namespace detail
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariant.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariant.hpp
index d1f122e9c9bb8ee3c1c5e238ec45971dded0fe74..25fb3f2ff54eceac778c6d383ad83af09c312794 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariant.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariant.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -9,67 +9,64 @@
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // A reference to a value in a JsonDocument.
-// https://arduinojson.org/v6/api/jsonvariant/
+// https://arduinojson.org/v7/api/jsonvariant/
 class JsonVariant : public detail::VariantRefBase<JsonVariant>,
                     public detail::VariantOperators<JsonVariant> {
   friend class detail::VariantAttorney;
 
  public:
   // Creates an unbound reference.
-  JsonVariant() : data_(0), pool_(0) {}
+  JsonVariant() : data_(0), resources_(0) {}
 
   // INTERNAL USE ONLY
-  JsonVariant(detail::MemoryPool* pool, detail::VariantData* data)
-      : data_(data), pool_(pool) {}
+  JsonVariant(detail::VariantData* data, detail::ResourceManager* resources)
+      : data_(data), resources_(resources) {}
 
  private:
-  FORCE_INLINE detail::MemoryPool* getPool() const {
-    return pool_;
+  detail::ResourceManager* getResourceManager() const {
+    return resources_;
   }
 
-  FORCE_INLINE detail::VariantData* getData() const {
+  detail::VariantData* getData() const {
     return data_;
   }
 
-  FORCE_INLINE detail::VariantData* getOrCreateData() const {
+  detail::VariantData* getOrCreateData() const {
     return data_;
   }
 
   detail::VariantData* data_;
-  detail::MemoryPool* pool_;
+  detail::ResourceManager* resources_;
 };
 
+namespace detail {
+bool copyVariant(JsonVariant dst, JsonVariantConst src);
+}
+
 template <>
 struct Converter<JsonVariant> : private detail::VariantAttorney {
-  static void toJson(JsonVariant src, JsonVariant dst) {
-    detail::variantCopyFrom(getData(dst), getData(src), getPool(dst));
+  static void toJson(JsonVariantConst src, JsonVariant dst) {
+    copyVariant(dst, src);
   }
 
   static JsonVariant fromJson(JsonVariant src) {
     return src;
   }
 
-  static detail::InvalidConversion<JsonVariantConst, JsonVariant> fromJson(
-      JsonVariantConst);
-
   static bool checkJson(JsonVariant src) {
     auto data = getData(src);
     return !!data;
   }
-
-  static bool checkJson(JsonVariantConst) {
-    return false;
-  }
 };
 
 template <>
 struct Converter<JsonVariantConst> : private detail::VariantAttorney {
   static void toJson(JsonVariantConst src, JsonVariant dst) {
-    variantCopyFrom(getData(dst), getData(src), getPool(dst));
+    copyVariant(dst, src);
   }
 
   static JsonVariantConst fromJson(JsonVariantConst src) {
-    return JsonVariantConst(getData(src));
+    return JsonVariantConst(getData(src), getResourceManager(src));
   }
 
   static bool checkJson(JsonVariantConst src) {
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantConst.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantConst.hpp
index 606df0833302d5ffd38c8dbe3c00c02c50713936..a1678fae9e9149ac99fb91591b6f2d3f31cdc1c5 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantConst.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantConst.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -7,12 +7,11 @@
 #include <stddef.h>
 #include <stdint.h>  // for uint8_t
 
-#include <ArduinoJson/Memory/MemoryPool.hpp>
+#include <ArduinoJson/Memory/ResourceManager.hpp>
 #include <ArduinoJson/Polyfills/type_traits.hpp>
 #include <ArduinoJson/Strings/IsString.hpp>
 #include <ArduinoJson/Strings/StringAdapters.hpp>
 #include <ArduinoJson/Variant/VariantAttorney.hpp>
-#include <ArduinoJson/Variant/VariantFunctions.hpp>
 #include <ArduinoJson/Variant/VariantOperators.hpp>
 #include <ArduinoJson/Variant/VariantTag.hpp>
 
@@ -23,113 +22,151 @@ class JsonArray;
 class JsonObject;
 
 // A read-only reference to a value in a JsonDocument
-// https://arduinojson.org/v6/api/jsonarrayconst/
+// https://arduinojson.org/v7/api/jsonarrayconst/
 class JsonVariantConst : public detail::VariantTag,
                          public detail::VariantOperators<JsonVariantConst> {
   friend class detail::VariantAttorney;
 
+  template <typename T>
+  using ConversionSupported =
+      detail::is_same<typename detail::function_traits<
+                          decltype(&Converter<T>::fromJson)>::arg1_type,
+                      JsonVariantConst>;
+
  public:
   // Creates an unbound reference.
-  JsonVariantConst() : data_(0) {}
+  JsonVariantConst() : data_(nullptr), resources_(nullptr) {}
 
   // INTERNAL USE ONLY
-  explicit JsonVariantConst(const detail::VariantData* data) : data_(data) {}
+  explicit JsonVariantConst(const detail::VariantData* data,
+                            const detail::ResourceManager* resources)
+      : data_(data), resources_(resources) {}
 
   // Returns true if the value is null or the reference is unbound.
-  // https://arduinojson.org/v6/api/jsonvariantconst/isnull/
-  FORCE_INLINE bool isNull() const {
-    using namespace detail;
-    return variantIsNull(data_);
+  // https://arduinojson.org/v7/api/jsonvariantconst/isnull/
+  bool isNull() const {
+    return detail::VariantData::isNull(data_);
   }
 
   // Returns true if the reference is unbound.
-  FORCE_INLINE bool isUnbound() const {
+  bool isUnbound() const {
     return !data_;
   }
 
-  // Returns the number of bytes occupied by the value.
-  // https://arduinojson.org/v6/api/jsonvariantconst/memoryusage/
-  FORCE_INLINE size_t memoryUsage() const {
-    return data_ ? data_->memoryUsage() : 0;
-  }
-
   // Returns the depth (nesting level) of the value.
-  // https://arduinojson.org/v6/api/jsonvariantconst/nesting/
-  FORCE_INLINE size_t nesting() const {
-    return variantNesting(data_);
+  // https://arduinojson.org/v7/api/jsonvariantconst/nesting/
+  size_t nesting() const {
+    return detail::VariantData::nesting(data_, resources_);
   }
 
   // Returns the size of the array or object.
-  // https://arduinojson.org/v6/api/jsonvariantconst/size/
+  // https://arduinojson.org/v7/api/jsonvariantconst/size/
   size_t size() const {
-    return variantSize(data_);
+    return detail::VariantData::size(data_, resources_);
   }
 
   // Casts the value to the specified type.
-  // https://arduinojson.org/v6/api/jsonvariantconst/as/
-  template <typename T>
-  FORCE_INLINE typename detail::enable_if<!detail::is_same<T, char*>::value &&
-                                              !detail::is_same<T, char>::value,
-                                          T>::type
-  as() const {
+  // https://arduinojson.org/v7/api/jsonvariantconst/as/
+  template <typename T,
+            detail::enable_if_t<ConversionSupported<T>::value, bool> = true>
+  T as() const {
     return Converter<T>::fromJson(*this);
   }
 
+  // Invalid conversion. Will not compile.
+  template <typename T,
+            detail::enable_if_t<!ConversionSupported<T>::value, bool> = true>
+  detail::InvalidConversion<JsonVariantConst, T> as() const;
+
   // Returns true if the value is of the specified type.
-  // https://arduinojson.org/v6/api/jsonvariantconst/is/
+  // https://arduinojson.org/v7/api/jsonvariantconst/is/
   template <typename T>
-  FORCE_INLINE typename detail::enable_if<!detail::is_same<T, char*>::value &&
-                                              !detail::is_same<T, char>::value,
-                                          bool>::type
-  is() const {
+  detail::enable_if_t<ConversionSupported<T>::value, bool> is() const {
     return Converter<T>::checkJson(*this);
   }
 
+  // Always returns false for the unsupported types.
+  // https://arduinojson.org/v7/api/jsonvariantconst/is/
+  template <typename T>
+  detail::enable_if_t<!ConversionSupported<T>::value, bool> is() const {
+    return false;
+  }
+
   template <typename T>
-  FORCE_INLINE operator T() const {
+  operator T() const {
     return as<T>();
   }
 
   // Gets array's element at specified index.
-  // https://arduinojson.org/v6/api/jsonvariantconst/subscript/
-  FORCE_INLINE JsonVariantConst operator[](size_t index) const {
-    return JsonVariantConst(variantGetElement(data_, index));
+  // https://arduinojson.org/v7/api/jsonvariantconst/subscript/
+  template <typename T>
+  detail::enable_if_t<detail::is_integral<T>::value, JsonVariantConst>
+  operator[](T index) const {
+    return JsonVariantConst(
+        detail::VariantData::getElement(data_, size_t(index), resources_),
+        resources_);
   }
 
   // Gets object's member with specified key.
-  // https://arduinojson.org/v6/api/jsonvariantconst/subscript/
+  // https://arduinojson.org/v7/api/jsonvariantconst/subscript/
   template <typename TString>
-  FORCE_INLINE typename detail::enable_if<detail::IsString<TString>::value,
-                                          JsonVariantConst>::type
+  detail::enable_if_t<detail::IsString<TString>::value, JsonVariantConst>
   operator[](const TString& key) const {
-    return JsonVariantConst(variantGetMember(data_, detail::adaptString(key)));
+    return JsonVariantConst(detail::VariantData::getMember(
+                                data_, detail::adaptString(key), resources_),
+                            resources_);
   }
 
   // Gets object's member with specified key.
-  // https://arduinojson.org/v6/api/jsonvariantconst/subscript/
+  // https://arduinojson.org/v7/api/jsonvariantconst/subscript/
   template <typename TChar>
-  FORCE_INLINE typename detail::enable_if<detail::IsString<TChar*>::value,
-                                          JsonVariantConst>::type
+  detail::enable_if_t<detail::IsString<TChar*>::value, JsonVariantConst>
   operator[](TChar* key) const {
-    return JsonVariantConst(variantGetMember(data_, detail::adaptString(key)));
+    return JsonVariantConst(detail::VariantData::getMember(
+                                data_, detail::adaptString(key), resources_),
+                            resources_);
+  }
+
+  // Gets object's member with specified key or the array's element at the
+  // specified index.
+  // https://arduinojson.org/v7/api/jsonvariantconst/subscript/
+  template <typename TVariant>
+  detail::enable_if_t<detail::IsVariant<TVariant>::value, JsonVariantConst>
+  operator[](const TVariant& key) const {
+    if (key.template is<size_t>())
+      return operator[](key.template as<size_t>());
+    else
+      return operator[](key.template as<const char*>());
   }
 
   // Returns true if tge object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonvariantconst/containskey/
+  // https://arduinojson.org/v7/api/jsonvariantconst/containskey/
   template <typename TString>
-  FORCE_INLINE
-      typename detail::enable_if<detail::IsString<TString>::value, bool>::type
-      containsKey(const TString& key) const {
-    return variantGetMember(getData(), detail::adaptString(key)) != 0;
+  detail::enable_if_t<detail::IsString<TString>::value, bool> containsKey(
+      const TString& key) const {
+    return detail::VariantData::getMember(getData(), detail::adaptString(key),
+                                          resources_) != 0;
   }
 
   // Returns true if tge object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonvariantconst/containskey/
+  // https://arduinojson.org/v7/api/jsonvariantconst/containskey/
   template <typename TChar>
-  FORCE_INLINE
-      typename detail::enable_if<detail::IsString<TChar*>::value, bool>::type
-      containsKey(TChar* key) const {
-    return variantGetMember(getData(), detail::adaptString(key)) != 0;
+  detail::enable_if_t<detail::IsString<TChar*>::value, bool> containsKey(
+      TChar* key) const {
+    return detail::VariantData::getMember(getData(), detail::adaptString(key),
+                                          resources_) != 0;
+  }
+
+  template <typename TVariant>
+  detail::enable_if_t<detail::IsVariant<TVariant>::value, bool> containsKey(
+      const TVariant& key) const {
+    return containsKey(key.template as<const char*>());
+  }
+
+  // DEPRECATED: always returns zero
+  ARDUINOJSON_DEPRECATED("always returns zero")
+  size_t memoryUsage() const {
+    return 0;
   }
 
  protected:
@@ -137,8 +174,13 @@ class JsonVariantConst : public detail::VariantTag,
     return data_;
   }
 
+  const detail::ResourceManager* getResourceManager() const {
+    return resources_;
+  }
+
  private:
   const detail::VariantData* data_;
+  const detail::ResourceManager* resources_;
 };
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantCopier.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantCopier.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..032f1f84168b2a4cdcc528dbba736b824074af2f
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantCopier.hpp
@@ -0,0 +1,34 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Variant/JsonVariant.hpp>
+#include <ArduinoJson/Variant/JsonVariantVisitor.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+class JsonVariantCopier {
+ public:
+  typedef bool result_type;
+
+  JsonVariantCopier(JsonVariant dst) : dst_(dst) {}
+
+  template <typename T>
+  bool visit(T src) {
+    return dst_.set(src);
+  }
+
+ private:
+  JsonVariant dst_;
+};
+
+inline bool copyVariant(JsonVariant dst, JsonVariantConst src) {
+  if (dst.isUnbound())
+    return false;
+  JsonVariantCopier copier(dst);
+  return accept(src, copier);
+}
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantVisitor.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantVisitor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..47196270272715d8f3ffb4f6f05f579744dbde18
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/JsonVariantVisitor.hpp
@@ -0,0 +1,61 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Array/JsonArray.hpp>
+#include <ArduinoJson/Object/JsonObject.hpp>
+#include <ArduinoJson/Variant/JsonVariant.hpp>
+#include <ArduinoJson/Variant/VariantDataVisitor.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+template <typename TResult>
+struct JsonVariantVisitor {
+  typedef TResult result_type;
+
+  template <typename T>
+  TResult visit(const T&) {
+    return TResult();
+  }
+};
+
+template <typename TVisitor>
+class VisitorAdapter {
+ public:
+  using result_type = typename TVisitor::result_type;
+
+  VisitorAdapter(TVisitor& visitor, const ResourceManager* resources)
+      : visitor_(&visitor), resources_(resources) {}
+
+  result_type visit(const ArrayData& value) {
+    return visitor_->visit(JsonArrayConst(&value, resources_));
+  }
+
+  result_type visit(const ObjectData& value) {
+    return visitor_->visit(JsonObjectConst(&value, resources_));
+  }
+
+  template <typename T>
+  result_type visit(const T& value) {
+    return visitor_->visit(value);
+  }
+
+ private:
+  TVisitor* visitor_;
+  const ResourceManager* resources_;
+};
+
+template <typename TVisitor>
+typename TVisitor::result_type accept(JsonVariantConst variant,
+                                      TVisitor& visit) {
+  auto data = VariantAttorney::getData(variant);
+  if (!data)
+    return visit.visit(nullptr);
+  auto resources = VariantAttorney::getResourceManager(variant);
+  VisitorAdapter<TVisitor> adapter(visit, resources);
+  return data->accept(adapter);
+}
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/SlotFunctions.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/SlotFunctions.hpp
deleted file mode 100644
index 8fee77f2db81b58e3939ccb7aca995f467f9209d..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/SlotFunctions.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Polyfills/assert.hpp>
-#include <ArduinoJson/Variant/VariantData.hpp>
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-struct SlotKeySetter {
-  SlotKeySetter(VariantSlot* instance) : instance_(instance) {}
-
-  template <typename TStoredString>
-  void operator()(TStoredString s) {
-    if (!s)
-      return;
-    ARDUINOJSON_ASSERT(instance_ != 0);
-    instance_->setKey(s);
-  }
-
-  VariantSlot* instance_;
-};
-
-template <typename TAdaptedString>
-inline bool slotSetKey(VariantSlot* var, TAdaptedString key, MemoryPool* pool) {
-  if (!var)
-    return false;
-  return storeString(pool, key, SlotKeySetter(var));
-}
-
-inline size_t slotSize(const VariantSlot* var) {
-  size_t n = 0;
-  while (var) {
-    n++;
-    var = var->next();
-  }
-  return n;
-}
-
-inline VariantData* slotData(VariantSlot* slot) {
-  return reinterpret_cast<VariantData*>(slot);
-}
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantAttorney.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantAttorney.hpp
index a2f6815f6abfc862d45c580db653f15a958e7766..b75eb380690193dbe143df93f5f1b01774fa4632 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantAttorney.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantAttorney.hpp
@@ -1,11 +1,12 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <ArduinoJson/Polyfills/attributes.hpp>
 #include <ArduinoJson/Polyfills/type_traits.hpp>
+#include <ArduinoJson/Variant/VariantData.hpp>
 #include <ArduinoJson/Variant/VariantTo.hpp>
 #include "JsonVariantConst.hpp"
 
@@ -13,34 +14,20 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 // Grants access to the internal variant API
 class VariantAttorney {
-  // Tells whether getData() returns a const pointer
-  template <typename TClient>
-  struct ResultOfGetData {
-   protected:  // <- to avoid GCC's "all member functions in class are private"
-    static int probe(const VariantData*);
-    static char probe(VariantData*);
-
-    static TClient& client;
-
-   public:
-    typedef typename conditional<sizeof(probe(client.getData())) == sizeof(int),
-                                 const VariantData*, VariantData*>::type type;
-  };
-
  public:
   template <typename TClient>
-  FORCE_INLINE static MemoryPool* getPool(TClient& client) {
-    return client.getPool();
+  static auto getResourceManager(TClient& client)
+      -> decltype(client.getResourceManager()) {
+    return client.getResourceManager();
   }
 
   template <typename TClient>
-  FORCE_INLINE static typename ResultOfGetData<TClient>::type getData(
-      TClient& client) {
+  static auto getData(TClient& client) -> decltype(client.getData()) {
     return client.getData();
   }
 
   template <typename TClient>
-  FORCE_INLINE static VariantData* getOrCreateData(TClient& client) {
+  static VariantData* getOrCreateData(TClient& client) {
     return client.getOrCreateData();
   }
 };
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp
index 42445c6b99a8a1a8222b2e394760de184fd5d327..9ec775e9df1abfa1dca6adff8259123212e946f5 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -7,27 +7,25 @@
 #include <ArduinoJson/Configuration.hpp>
 #include <ArduinoJson/Numbers/arithmeticCompare.hpp>
 #include <ArduinoJson/Polyfills/type_traits.hpp>
+#include <ArduinoJson/Polyfills/utility.hpp>
 #include <ArduinoJson/Strings/StringAdapters.hpp>
-#include <ArduinoJson/Variant/Visitor.hpp>
+#include <ArduinoJson/Variant/JsonVariantVisitor.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
-class CollectionData;
-
-struct ComparerBase : Visitor<CompareResult> {};
+struct ComparerBase : JsonVariantVisitor<CompareResult> {};
 
 template <typename T, typename Enable = void>
 struct Comparer;
 
 template <typename T>
-struct Comparer<T, typename enable_if<IsString<T>::value>::type>
-    : ComparerBase {
+struct Comparer<T, enable_if_t<IsString<T>::value>> : ComparerBase {
   T rhs;  // TODO: store adapted string?
 
   explicit Comparer(T value) : rhs(value) {}
 
-  CompareResult visitString(const char* lhs, size_t n) {
-    int i = stringCompare(adaptString(rhs), adaptString(lhs, n));
+  CompareResult visit(JsonString lhs) {
+    int i = stringCompare(adaptString(rhs), adaptString(lhs));
     if (i < 0)
       return COMPARE_RESULT_GREATER;
     else if (i > 0)
@@ -36,86 +34,94 @@ struct Comparer<T, typename enable_if<IsString<T>::value>::type>
       return COMPARE_RESULT_EQUAL;
   }
 
-  CompareResult visitNull() {
+  CompareResult visit(nullptr_t) {
     if (adaptString(rhs).isNull())
       return COMPARE_RESULT_EQUAL;
     else
       return COMPARE_RESULT_DIFFER;
   }
+
+  using ComparerBase::visit;
 };
 
 template <typename T>
-struct Comparer<T, typename enable_if<is_integral<T>::value ||
-                                      is_floating_point<T>::value>::type>
+struct Comparer<
+    T, enable_if_t<is_integral<T>::value || is_floating_point<T>::value>>
     : ComparerBase {
   T rhs;
 
   explicit Comparer(T value) : rhs(value) {}
 
-  CompareResult visitFloat(JsonFloat lhs) {
+  CompareResult visit(JsonFloat lhs) {
     return arithmeticCompare(lhs, rhs);
   }
 
-  CompareResult visitSignedInteger(JsonInteger lhs) {
+  CompareResult visit(JsonInteger lhs) {
     return arithmeticCompare(lhs, rhs);
   }
 
-  CompareResult visitUnsignedInteger(JsonUInt lhs) {
+  CompareResult visit(JsonUInt lhs) {
     return arithmeticCompare(lhs, rhs);
   }
 
-  CompareResult visitBoolean(bool lhs) {
-    return visitUnsignedInteger(static_cast<JsonUInt>(lhs));
+  CompareResult visit(bool lhs) {
+    return visit(static_cast<JsonUInt>(lhs));
   }
+
+  using ComparerBase::visit;
 };
 
 struct NullComparer : ComparerBase {
-  CompareResult visitNull() {
+  CompareResult visit(nullptr_t) {
     return COMPARE_RESULT_EQUAL;
   }
+
+  using ComparerBase::visit;
 };
 
 template <>
-struct Comparer<decltype(nullptr), void> : NullComparer {
-  explicit Comparer(decltype(nullptr)) : NullComparer() {}
+struct Comparer<nullptr_t, void> : NullComparer {
+  explicit Comparer(nullptr_t) : NullComparer() {}
 };
 
 struct ArrayComparer : ComparerBase {
-  const CollectionData* rhs_;
+  JsonArrayConst rhs_;
 
-  explicit ArrayComparer(const CollectionData& rhs) : rhs_(&rhs) {}
+  explicit ArrayComparer(JsonArrayConst rhs) : rhs_(rhs) {}
 
-  CompareResult visitArray(const CollectionData& lhs) {
-    if (JsonArrayConst(&lhs) == JsonArrayConst(rhs_))
+  CompareResult visit(JsonArrayConst lhs) {
+    if (rhs_ == lhs)
       return COMPARE_RESULT_EQUAL;
     else
       return COMPARE_RESULT_DIFFER;
   }
+
+  using ComparerBase::visit;
 };
 
 struct ObjectComparer : ComparerBase {
-  const CollectionData* rhs_;
+  JsonObjectConst rhs_;
 
-  explicit ObjectComparer(const CollectionData& rhs) : rhs_(&rhs) {}
+  explicit ObjectComparer(JsonObjectConst rhs) : rhs_(rhs) {}
 
-  CompareResult visitObject(const CollectionData& lhs) {
-    if (JsonObjectConst(&lhs) == JsonObjectConst(rhs_))
+  CompareResult visit(JsonObjectConst lhs) {
+    if (lhs == rhs_)
       return COMPARE_RESULT_EQUAL;
     else
       return COMPARE_RESULT_DIFFER;
   }
+
+  using ComparerBase::visit;
 };
 
 struct RawComparer : ComparerBase {
-  const char* rhsData_;
-  size_t rhsSize_;
+  RawString rhs_;
 
-  explicit RawComparer(const char* rhsData, size_t rhsSize)
-      : rhsData_(rhsData), rhsSize_(rhsSize) {}
+  explicit RawComparer(RawString rhs) : rhs_(rhs) {}
 
-  CompareResult visitRawJson(const char* lhsData, size_t lhsSize) {
-    size_t size = rhsSize_ < lhsSize ? rhsSize_ : lhsSize;
-    int n = memcmp(lhsData, rhsData_, size);
+  CompareResult visit(RawString lhs) {
+    size_t size = rhs_.size() < lhs.size() ? rhs_.size() : lhs.size();
+    int n = memcmp(lhs.data(), rhs_.data(), size);
     if (n < 0)
       return COMPARE_RESULT_LESS;
     else if (n > 0)
@@ -123,62 +129,64 @@ struct RawComparer : ComparerBase {
     else
       return COMPARE_RESULT_EQUAL;
   }
+
+  using ComparerBase::visit;
 };
 
 struct VariantComparer : ComparerBase {
-  const VariantData* rhs;
+  JsonVariantConst rhs;
 
-  explicit VariantComparer(const VariantData* value) : rhs(value) {}
+  explicit VariantComparer(JsonVariantConst value) : rhs(value) {}
 
-  CompareResult visitArray(const CollectionData& lhs) {
+  CompareResult visit(JsonArrayConst lhs) {
     ArrayComparer comparer(lhs);
-    return accept(comparer);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitObject(const CollectionData& lhs) {
+  CompareResult visit(JsonObjectConst lhs) {
     ObjectComparer comparer(lhs);
-    return accept(comparer);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitFloat(JsonFloat lhs) {
+  CompareResult visit(JsonFloat lhs) {
     Comparer<JsonFloat> comparer(lhs);
-    return accept(comparer);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitString(const char* lhs, size_t) {
-    Comparer<const char*> comparer(lhs);
-    return accept(comparer);
+  CompareResult visit(JsonString lhs) {
+    Comparer<JsonString> comparer(lhs);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitRawJson(const char* lhsData, size_t lhsSize) {
-    RawComparer comparer(lhsData, lhsSize);
-    return accept(comparer);
+  CompareResult visit(RawString value) {
+    RawComparer comparer(value);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitSignedInteger(JsonInteger lhs) {
+  CompareResult visit(JsonInteger lhs) {
     Comparer<JsonInteger> comparer(lhs);
-    return accept(comparer);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitUnsignedInteger(JsonUInt lhs) {
+  CompareResult visit(JsonUInt lhs) {
     Comparer<JsonUInt> comparer(lhs);
-    return accept(comparer);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitBoolean(bool lhs) {
+  CompareResult visit(bool lhs) {
     Comparer<bool> comparer(lhs);
-    return accept(comparer);
+    return reverseResult(comparer);
   }
 
-  CompareResult visitNull() {
+  CompareResult visit(nullptr_t) {
     NullComparer comparer;
-    return accept(comparer);
+    return reverseResult(comparer);
   }
 
  private:
   template <typename TComparer>
-  CompareResult accept(TComparer& comparer) {
-    CompareResult reversedResult = variantAccept(rhs, comparer);
+  CompareResult reverseResult(TComparer& comparer) {
+    CompareResult reversedResult = accept(rhs, comparer);
     switch (reversedResult) {
       case COMPARE_RESULT_GREATER:
         return COMPARE_RESULT_LESS;
@@ -191,17 +199,17 @@ struct VariantComparer : ComparerBase {
 };
 
 template <typename T>
-struct Comparer<T, typename enable_if<is_convertible<
-                       T, ArduinoJson::JsonVariantConst>::value>::type>
+struct Comparer<
+    T, enable_if_t<is_convertible<T, ArduinoJson::JsonVariantConst>::value>>
     : VariantComparer {
   explicit Comparer(const T& value)
-      : VariantComparer(VariantAttorney::getData(value)) {}
+      : VariantComparer(static_cast<JsonVariantConst>(value)) {}
 };
 
 template <typename T>
 CompareResult compare(ArduinoJson::JsonVariantConst lhs, const T& rhs) {
   Comparer<T> comparer(rhs);
-  return variantAccept(VariantAttorney::getData(lhs), comparer);
+  return accept(lhs, comparer);
 }
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantContent.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantContent.hpp
index 22239019b1406d7762faddd7cd65ee120b253279..fa2cb50efd102c9c685d5c25e6c6b2f1cc7c3bc7 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantContent.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantContent.hpp
@@ -1,14 +1,15 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
 #include <stddef.h>  // size_t
 
-#include <ArduinoJson/Collection/CollectionData.hpp>
+#include <ArduinoJson/Array/ArrayData.hpp>
 #include <ArduinoJson/Numbers/JsonFloat.hpp>
 #include <ArduinoJson/Numbers/JsonInteger.hpp>
+#include <ArduinoJson/Object/ObjectData.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
@@ -17,8 +18,7 @@ enum {
 
   OWNED_VALUE_BIT = 0x01,
   VALUE_IS_NULL = 0,
-  VALUE_IS_LINKED_RAW = 0x02,
-  VALUE_IS_OWNED_RAW = 0x03,
+  VALUE_IS_RAW_STRING = 0x03,
   VALUE_IS_LINKED_STRING = 0x04,
   VALUE_IS_OWNED_STRING = 0x05,
 
@@ -38,21 +38,18 @@ enum {
   OWNED_KEY_BIT = 0x80
 };
 
-struct RawData {
-  const char* data;
-  size_t size;
-};
-
 union VariantContent {
+  VariantContent() {}
+
   JsonFloat asFloat;
   bool asBoolean;
   JsonUInt asUnsignedInteger;
   JsonInteger asSignedInteger;
+  ArrayData asArray;
+  ObjectData asObject;
   CollectionData asCollection;
-  struct {
-    const char* data;
-    size_t size;
-  } asString;
+  const char* asLinkedString;
+  struct StringNode* asOwnedString;
 };
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp
index 7ef218c00c5baac5c33f042fd3f77486d2d037d5..5a2974ba454c3ba879980494186e7471ec9ab3ee 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp
@@ -1,18 +1,22 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
-#include <ArduinoJson/Memory/MemoryPool.hpp>
+#include <ArduinoJson/Memory/StringNode.hpp>
 #include <ArduinoJson/Misc/SerializedValue.hpp>
 #include <ArduinoJson/Numbers/convertNumber.hpp>
 #include <ArduinoJson/Strings/JsonString.hpp>
 #include <ArduinoJson/Strings/StringAdapters.hpp>
 #include <ArduinoJson/Variant/VariantContent.hpp>
+#include <ArduinoJson/Variant/VariantSlot.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
+template <typename T>
+T parseNumber(const char* s);
+
 class VariantData {
   VariantContent content_;  // must be first to allow cast from array to variant
   uint8_t flags_;
@@ -20,78 +24,222 @@ class VariantData {
  public:
   VariantData() : flags_(VALUE_IS_NULL) {}
 
-  void operator=(const VariantData& src) {
-    content_ = src.content_;
-    flags_ = uint8_t((flags_ & OWNED_KEY_BIT) | (src.flags_ & ~OWNED_KEY_BIT));
-  }
-
   template <typename TVisitor>
-  typename TVisitor::result_type accept(TVisitor& visitor) const {
+  typename TVisitor::result_type accept(TVisitor& visit) const {
     switch (type()) {
       case VALUE_IS_FLOAT:
-        return visitor.visitFloat(content_.asFloat);
+        return visit.visit(content_.asFloat);
 
       case VALUE_IS_ARRAY:
-        return visitor.visitArray(content_.asCollection);
+        return visit.visit(content_.asArray);
 
       case VALUE_IS_OBJECT:
-        return visitor.visitObject(content_.asCollection);
+        return visit.visit(content_.asObject);
 
       case VALUE_IS_LINKED_STRING:
+        return visit.visit(JsonString(content_.asLinkedString));
+
       case VALUE_IS_OWNED_STRING:
-        return visitor.visitString(content_.asString.data,
-                                   content_.asString.size);
+        return visit.visit(JsonString(content_.asOwnedString->data,
+                                      content_.asOwnedString->length,
+                                      JsonString::Copied));
 
-      case VALUE_IS_OWNED_RAW:
-      case VALUE_IS_LINKED_RAW:
-        return visitor.visitRawJson(content_.asString.data,
-                                    content_.asString.size);
+      case VALUE_IS_RAW_STRING:
+        return visit.visit(RawString(content_.asOwnedString->data,
+                                     content_.asOwnedString->length));
 
       case VALUE_IS_SIGNED_INTEGER:
-        return visitor.visitSignedInteger(content_.asSignedInteger);
+        return visit.visit(content_.asSignedInteger);
 
       case VALUE_IS_UNSIGNED_INTEGER:
-        return visitor.visitUnsignedInteger(content_.asUnsignedInteger);
+        return visit.visit(content_.asUnsignedInteger);
 
       case VALUE_IS_BOOLEAN:
-        return visitor.visitBoolean(content_.asBoolean != 0);
+        return visit.visit(content_.asBoolean != 0);
 
       default:
-        return visitor.visitNull();
+        return visit.visit(nullptr);
     }
   }
 
-  template <typename T>
-  T asIntegral() const;
+  template <typename TVisitor>
+  static typename TVisitor::result_type accept(const VariantData* var,
+                                               TVisitor& visit) {
+    if (var != 0)
+      return var->accept(visit);
+    else
+      return visit.visit(nullptr);
+  }
+
+  VariantData* addElement(ResourceManager* resources) {
+    auto array = isNull() ? &toArray() : asArray();
+    return detail::ArrayData::addElement(array, resources);
+  }
+
+  static VariantData* addElement(VariantData* var, ResourceManager* resources) {
+    if (!var)
+      return nullptr;
+    return var->addElement(resources);
+  }
 
   template <typename T>
-  T asFloat() const;
+  bool addValue(T&& value, ResourceManager* resources) {
+    auto array = isNull() ? &toArray() : asArray();
+    return detail::ArrayData::addValue(array, detail::forward<T>(value),
+                                       resources);
+  }
 
-  JsonString asString() const;
+  template <typename T>
+  static bool addValue(VariantData* var, T&& value,
+                       ResourceManager* resources) {
+    if (!var)
+      return false;
+    return var->addValue(value, resources);
+  }
 
-  bool asBoolean() const;
+  bool asBoolean() const {
+    switch (type()) {
+      case VALUE_IS_BOOLEAN:
+        return content_.asBoolean;
+      case VALUE_IS_SIGNED_INTEGER:
+      case VALUE_IS_UNSIGNED_INTEGER:
+        return content_.asUnsignedInteger != 0;
+      case VALUE_IS_FLOAT:
+        return content_.asFloat != 0;
+      case VALUE_IS_NULL:
+        return false;
+      default:
+        return true;
+    }
+  }
 
-  CollectionData* asArray() {
-    return isArray() ? &content_.asCollection : 0;
+  ArrayData* asArray() {
+    return isArray() ? &content_.asArray : 0;
   }
 
-  const CollectionData* asArray() const {
+  const ArrayData* asArray() const {
     return const_cast<VariantData*>(this)->asArray();
   }
 
-  const CollectionData* asCollection() const {
+  CollectionData* asCollection() {
     return isCollection() ? &content_.asCollection : 0;
   }
 
-  CollectionData* asObject() {
-    return isObject() ? &content_.asCollection : 0;
+  const CollectionData* asCollection() const {
+    return const_cast<VariantData*>(this)->asCollection();
   }
 
-  const CollectionData* asObject() const {
+  template <typename T>
+  T asFloat() const {
+    static_assert(is_floating_point<T>::value, "T must be a floating point");
+    switch (type()) {
+      case VALUE_IS_BOOLEAN:
+        return static_cast<T>(content_.asBoolean);
+      case VALUE_IS_UNSIGNED_INTEGER:
+        return static_cast<T>(content_.asUnsignedInteger);
+      case VALUE_IS_SIGNED_INTEGER:
+        return static_cast<T>(content_.asSignedInteger);
+      case VALUE_IS_LINKED_STRING:
+      case VALUE_IS_OWNED_STRING:
+        return parseNumber<T>(content_.asOwnedString->data);
+      case VALUE_IS_FLOAT:
+        return static_cast<T>(content_.asFloat);
+      default:
+        return 0;
+    }
+  }
+
+  template <typename T>
+  T asIntegral() const {
+    static_assert(is_integral<T>::value, "T must be an integral type");
+    switch (type()) {
+      case VALUE_IS_BOOLEAN:
+        return content_.asBoolean;
+      case VALUE_IS_UNSIGNED_INTEGER:
+        return convertNumber<T>(content_.asUnsignedInteger);
+      case VALUE_IS_SIGNED_INTEGER:
+        return convertNumber<T>(content_.asSignedInteger);
+      case VALUE_IS_LINKED_STRING:
+        return parseNumber<T>(content_.asLinkedString);
+      case VALUE_IS_OWNED_STRING:
+        return parseNumber<T>(content_.asOwnedString->data);
+      case VALUE_IS_FLOAT:
+        return convertNumber<T>(content_.asFloat);
+      default:
+        return 0;
+    }
+  }
+
+  ObjectData* asObject() {
+    return isObject() ? &content_.asObject : 0;
+  }
+
+  const ObjectData* asObject() const {
     return const_cast<VariantData*>(this)->asObject();
   }
 
-  bool copyFrom(const VariantData& src, MemoryPool* pool);
+  JsonString asRawString() const {
+    switch (type()) {
+      case VALUE_IS_RAW_STRING:
+        return JsonString(content_.asOwnedString->data,
+                          content_.asOwnedString->length, JsonString::Copied);
+      default:
+        return JsonString();
+    }
+  }
+
+  JsonString asString() const {
+    switch (type()) {
+      case VALUE_IS_LINKED_STRING:
+        return JsonString(content_.asLinkedString, JsonString::Linked);
+      case VALUE_IS_OWNED_STRING:
+        return JsonString(content_.asOwnedString->data,
+                          content_.asOwnedString->length, JsonString::Copied);
+      default:
+        return JsonString();
+    }
+  }
+
+  VariantData* getElement(size_t index,
+                          const ResourceManager* resources) const {
+    return ArrayData::getElement(asArray(), index, resources);
+  }
+
+  static VariantData* getElement(const VariantData* var, size_t index,
+                                 const ResourceManager* resources) {
+    return var != 0 ? var->getElement(index, resources) : 0;
+  }
+
+  template <typename TAdaptedString>
+  VariantData* getMember(TAdaptedString key,
+                         const ResourceManager* resources) const {
+    return ObjectData::getMember(asObject(), key, resources);
+  }
+
+  template <typename TAdaptedString>
+  static VariantData* getMember(const VariantData* var, TAdaptedString key,
+                                const ResourceManager* resources) {
+    if (!var)
+      return 0;
+    return var->getMember(key, resources);
+  }
+
+  VariantData* getOrAddElement(size_t index, ResourceManager* resources) {
+    auto array = isNull() ? &toArray() : asArray();
+    if (!array)
+      return nullptr;
+    return array->getOrAddElement(index, resources);
+  }
+
+  template <typename TAdaptedString>
+  VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources) {
+    if (key.isNull())
+      return nullptr;
+    auto obj = isNull() ? &toObject() : asObject();
+    if (!obj)
+      return nullptr;
+    return obj->getOrAddMember(key, resources);
+  }
 
   bool isArray() const {
     return (flags_ & VALUE_IS_ARRAY) != 0;
@@ -105,6 +253,10 @@ class VariantData {
     return (flags_ & COLLECTION_MASK) != 0;
   }
 
+  bool isFloat() const {
+    return (flags_ & NUMBER_BIT) != 0;
+  }
+
   template <typename T>
   bool isInteger() const {
     switch (type()) {
@@ -119,35 +271,65 @@ class VariantData {
     }
   }
 
-  bool isFloat() const {
-    return (flags_ & NUMBER_BIT) != 0;
+  bool isNull() const {
+    return type() == VALUE_IS_NULL;
   }
 
-  bool isString() const {
-    return type() == VALUE_IS_LINKED_STRING || type() == VALUE_IS_OWNED_STRING;
+  static bool isNull(const VariantData* var) {
+    if (!var)
+      return true;
+    return var->isNull();
   }
 
   bool isObject() const {
     return (flags_ & VALUE_IS_OBJECT) != 0;
   }
 
-  bool isNull() const {
-    return type() == VALUE_IS_NULL;
+  bool isString() const {
+    return type() == VALUE_IS_LINKED_STRING || type() == VALUE_IS_OWNED_STRING;
+  }
+
+  size_t nesting(const ResourceManager* resources) const {
+    auto collection = asCollection();
+    if (collection)
+      return collection->nesting(resources);
+    else
+      return 0;
   }
 
-  bool isEnclosed() const {
-    return !isFloat();
+  static size_t nesting(const VariantData* var,
+                        const ResourceManager* resources) {
+    if (!var)
+      return 0;
+    return var->nesting(resources);
   }
 
-  void remove(size_t index) {
-    if (isArray())
-      content_.asCollection.removeElement(index);
+  void removeElement(size_t index, ResourceManager* resources) {
+    ArrayData::removeElement(asArray(), index, resources);
+  }
+
+  static void removeElement(VariantData* var, size_t index,
+                            ResourceManager* resources) {
+    if (!var)
+      return;
+    var->removeElement(index, resources);
+  }
+
+  template <typename TAdaptedString>
+  void removeMember(TAdaptedString key, ResourceManager* resources) {
+    ObjectData::removeMember(asObject(), key, resources);
   }
 
   template <typename TAdaptedString>
-  void remove(TAdaptedString key) {
-    if (isObject())
-      content_.asCollection.removeMember(key);
+  static void removeMember(VariantData* var, TAdaptedString key,
+                           ResourceManager* resources) {
+    if (!var)
+      return;
+    var->removeMember(key, resources);
+  }
+
+  void reset() {
+    flags_ = VALUE_IS_NULL;
   }
 
   void setBoolean(bool value) {
@@ -155,168 +337,175 @@ class VariantData {
     content_.asBoolean = value;
   }
 
+  void setBoolean(bool value, ResourceManager* resources) {
+    release(resources);
+    setBoolean(value);
+  }
+
   void setFloat(JsonFloat value) {
     setType(VALUE_IS_FLOAT);
     content_.asFloat = value;
   }
 
-  void setLinkedRaw(SerializedValue<const char*> value) {
-    if (value.data()) {
-      setType(VALUE_IS_LINKED_RAW);
-      content_.asString.data = value.data();
-      content_.asString.size = value.size();
-    } else {
-      setType(VALUE_IS_NULL);
-    }
+  void setFloat(JsonFloat value, ResourceManager* resources) {
+    release(resources);
+    setFloat(value);
   }
 
   template <typename T>
-  bool storeOwnedRaw(SerializedValue<T> value, MemoryPool* pool) {
-    const char* dup = pool->saveString(adaptString(value.data(), value.size()));
-    if (dup) {
-      setType(VALUE_IS_OWNED_RAW);
-      content_.asString.data = dup;
-      content_.asString.size = value.size();
-      return true;
-    } else {
-      setType(VALUE_IS_NULL);
-      return false;
-    }
+  enable_if_t<is_signed<T>::value> setInteger(T value) {
+    setType(VALUE_IS_SIGNED_INTEGER);
+    content_.asSignedInteger = value;
   }
 
   template <typename T>
-  typename enable_if<is_unsigned<T>::value>::type setInteger(T value) {
+  enable_if_t<is_unsigned<T>::value> setInteger(T value) {
     setType(VALUE_IS_UNSIGNED_INTEGER);
     content_.asUnsignedInteger = static_cast<JsonUInt>(value);
   }
 
   template <typename T>
-  typename enable_if<is_signed<T>::value>::type setInteger(T value) {
-    setType(VALUE_IS_SIGNED_INTEGER);
-    content_.asSignedInteger = value;
+  void setInteger(T value, ResourceManager* resources) {
+    release(resources);
+    setInteger(value);
   }
 
   void setNull() {
     setType(VALUE_IS_NULL);
   }
 
-  void setString(JsonString s) {
+  void setNull(ResourceManager* resources) {
+    release(resources);
+    setNull();
+  }
+
+  static void setNull(VariantData* var, ResourceManager* resources) {
+    if (!var)
+      return;
+    var->setNull(resources);
+  }
+
+  void setRawString(StringNode* s) {
     ARDUINOJSON_ASSERT(s);
-    if (s.isLinked())
-      setType(VALUE_IS_LINKED_STRING);
-    else
-      setType(VALUE_IS_OWNED_STRING);
-    content_.asString.data = s.c_str();
-    content_.asString.size = s.size();
+    setType(VALUE_IS_RAW_STRING);
+    content_.asOwnedString = s;
   }
 
-  CollectionData& toArray() {
-    setType(VALUE_IS_ARRAY);
-    content_.asCollection.clear();
-    return content_.asCollection;
+  template <typename T>
+  void setRawString(SerializedValue<T> value, ResourceManager* resources) {
+    release(resources);
+    auto dup = resources->saveString(adaptString(value.data(), value.size()));
+    if (dup)
+      setRawString(dup);
+    else
+      setNull();
   }
 
-  CollectionData& toObject() {
-    setType(VALUE_IS_OBJECT);
-    content_.asCollection.clear();
-    return content_.asCollection;
+  template <typename T>
+  static void setRawString(VariantData* var, SerializedValue<T> value,
+                           ResourceManager* resources) {
+    if (!var)
+      return;
+    var->setRawString(value, resources);
   }
 
-  size_t memoryUsage() const {
-    switch (type()) {
-      case VALUE_IS_OWNED_STRING:
-      case VALUE_IS_OWNED_RAW:
-        // We always add a zero at the end: the deduplication function uses it
-        // to detect the beginning of the next string.
-        return content_.asString.size + 1;
-      case VALUE_IS_OBJECT:
-      case VALUE_IS_ARRAY:
-        return content_.asCollection.memoryUsage();
-      default:
-        return 0;
+  template <typename TAdaptedString>
+  void setString(TAdaptedString value, ResourceManager* resources) {
+    setNull(resources);
+
+    if (value.isNull())
+      return;
+
+    if (value.isLinked()) {
+      setLinkedString(value.data());
+      return;
     }
+
+    auto dup = resources->saveString(value);
+    if (dup)
+      setOwnedString(dup);
   }
 
-  size_t size() const {
-    return isCollection() ? content_.asCollection.size() : 0;
+  template <typename TAdaptedString>
+  static void setString(VariantData* var, TAdaptedString value,
+                        ResourceManager* resources) {
+    if (!var)
+      return;
+    var->setString(value, resources);
   }
 
-  VariantData* addElement(MemoryPool* pool) {
-    if (isNull())
-      toArray();
-    if (!isArray())
-      return 0;
-    return content_.asCollection.addElement(pool);
+  void setLinkedString(const char* s) {
+    ARDUINOJSON_ASSERT(s);
+    setType(VALUE_IS_LINKED_STRING);
+    content_.asLinkedString = s;
   }
 
-  VariantData* getElement(size_t index) const {
-    const CollectionData* col = asArray();
-    return col ? col->getElement(index) : 0;
+  void setOwnedString(StringNode* s) {
+    ARDUINOJSON_ASSERT(s);
+    setType(VALUE_IS_OWNED_STRING);
+    content_.asOwnedString = s;
   }
 
-  VariantData* getOrAddElement(size_t index, MemoryPool* pool) {
-    if (isNull())
-      toArray();
-    if (!isArray())
-      return 0;
-    return content_.asCollection.getOrAddElement(index, pool);
+  size_t size(const ResourceManager* resources) const {
+    return isCollection() ? content_.asCollection.size(resources) : 0;
   }
 
-  template <typename TAdaptedString>
-  VariantData* getMember(TAdaptedString key) const {
-    const CollectionData* col = asObject();
-    return col ? col->getMember(key) : 0;
+  static size_t size(const VariantData* var, const ResourceManager* resources) {
+    return var != 0 ? var->size(resources) : 0;
   }
 
-  template <typename TAdaptedString>
-  VariantData* getOrAddMember(TAdaptedString key, MemoryPool* pool) {
-    if (isNull())
-      toObject();
-    if (!isObject())
+  ArrayData& toArray() {
+    setType(VALUE_IS_ARRAY);
+    new (&content_.asArray) ArrayData();
+    return content_.asArray;
+  }
+
+  ArrayData& toArray(ResourceManager* resources) {
+    release(resources);
+    return toArray();
+  }
+
+  static ArrayData* toArray(VariantData* var, ResourceManager* resources) {
+    if (!var)
       return 0;
-    return content_.asCollection.getOrAddMember(key, pool);
+    return &var->toArray(resources);
   }
 
-  void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance) {
-    if (flags_ & OWNED_VALUE_BIT)
-      content_.asString.data += stringDistance;
-    if (flags_ & COLLECTION_MASK)
-      content_.asCollection.movePointers(stringDistance, variantDistance);
+  ObjectData& toObject() {
+    setType(VALUE_IS_OBJECT);
+    new (&content_.asObject) ObjectData();
+    return content_.asObject;
+  }
+
+  ObjectData& toObject(ResourceManager* resources) {
+    release(resources);
+    return toObject();
+  }
+
+  static ObjectData* toObject(VariantData* var, ResourceManager* resources) {
+    if (!var)
+      return 0;
+    return &var->toObject(resources);
   }
 
   uint8_t type() const {
     return flags_ & VALUE_MASK;
   }
 
-  template <typename TAdaptedString>
-  inline bool setString(TAdaptedString value, MemoryPool* pool) {
-    if (value.isNull()) {
-      setNull();
-      return true;
-    }
+ private:
+  void release(ResourceManager* resources) {
+    if (flags_ & OWNED_VALUE_BIT)
+      resources->dereferenceString(content_.asOwnedString->data);
 
-    return storeString(pool, value, VariantStringSetter(this));
+    auto collection = asCollection();
+    if (collection)
+      collection->clear(resources);
   }
 
- private:
   void setType(uint8_t t) {
     flags_ &= OWNED_KEY_BIT;
     flags_ |= t;
   }
-
-  struct VariantStringSetter {
-    VariantStringSetter(VariantData* instance) : instance_(instance) {}
-
-    template <typename TStoredString>
-    void operator()(TStoredString s) {
-      if (s)
-        instance_->setString(s);
-      else
-        instance_->setNull();
-    }
-
-    VariantData* instance_;
-  };
 };
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantDataVisitor.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantDataVisitor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..631bce7ca34aa0696a2583dc2520a04879cb28a0
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantDataVisitor.hpp
@@ -0,0 +1,24 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Array/ArrayData.hpp>
+#include <ArduinoJson/Numbers/JsonFloat.hpp>
+#include <ArduinoJson/Numbers/JsonInteger.hpp>
+#include <ArduinoJson/Object/ObjectData.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+template <typename TResult>
+struct VariantDataVisitor {
+  typedef TResult result_type;
+
+  template <typename T>
+  TResult visit(const T&) {
+    return TResult();
+  }
+};
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantFunctions.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantFunctions.hpp
deleted file mode 100644
index c0c91957709c27c4ce0ac23d1d24ee4e0c75c1a1..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantFunctions.hpp
+++ /dev/null
@@ -1,113 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Polyfills/attributes.hpp>
-#include <ArduinoJson/Strings/StoragePolicy.hpp>
-#include <ArduinoJson/Variant/VariantData.hpp>
-#include <ArduinoJson/Variant/Visitor.hpp>
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-template <typename TVisitor>
-inline typename TVisitor::result_type variantAccept(const VariantData* var,
-                                                    TVisitor& visitor) {
-  if (var != 0)
-    return var->accept(visitor);
-  else
-    return visitor.visitNull();
-}
-
-inline bool variantCopyFrom(VariantData* dst, const VariantData* src,
-                            MemoryPool* pool) {
-  if (!dst)
-    return false;
-  if (!src) {
-    dst->setNull();
-    return true;
-  }
-  return dst->copyFrom(*src, pool);
-}
-
-inline void variantSetNull(VariantData* var) {
-  if (!var)
-    return;
-  var->setNull();
-}
-
-template <typename TAdaptedString>
-inline bool variantSetString(VariantData* var, TAdaptedString value,
-                             MemoryPool* pool) {
-  return var != 0 ? var->setString(value, pool) : 0;
-}
-
-inline size_t variantSize(const VariantData* var) {
-  return var != 0 ? var->size() : 0;
-}
-
-inline CollectionData* variantToArray(VariantData* var) {
-  if (!var)
-    return 0;
-  return &var->toArray();
-}
-
-inline CollectionData* variantToObject(VariantData* var) {
-  if (!var)
-    return 0;
-  return &var->toObject();
-}
-
-inline VariantData* variantGetElement(const VariantData* var, size_t index) {
-  return var != 0 ? var->getElement(index) : 0;
-}
-
-inline NO_INLINE VariantData* variantAddElement(VariantData* var,
-                                                MemoryPool* pool) {
-  return var != 0 ? var->addElement(pool) : 0;
-}
-
-inline NO_INLINE VariantData* variantGetOrAddElement(VariantData* var,
-                                                     size_t index,
-                                                     MemoryPool* pool) {
-  return var != 0 ? var->getOrAddElement(index, pool) : 0;
-}
-
-template <typename TAdaptedString>
-VariantData* variantGetMember(const VariantData* var, TAdaptedString key) {
-  if (!var)
-    return 0;
-  return var->getMember(key);
-}
-
-template <typename TAdaptedString>
-VariantData* variantGetOrAddMember(VariantData* var, TAdaptedString key,
-                                   MemoryPool* pool) {
-  if (!var)
-    return 0;
-  return var->getOrAddMember(key, pool);
-}
-
-inline bool variantIsNull(const VariantData* var) {
-  return var == 0 || var->isNull();
-}
-
-inline size_t variantNesting(const VariantData* var) {
-  if (!var)
-    return 0;
-
-  const CollectionData* collection = var->asCollection();
-  if (!collection)
-    return 0;
-
-  size_t maxChildNesting = 0;
-  for (const VariantSlot* s = collection->head(); s; s = s->next()) {
-    size_t childNesting = variantNesting(s->data());
-    if (childNesting > maxChildNesting)
-      maxChildNesting = childNesting;
-  }
-  return maxChildNesting + 1;
-}
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp
deleted file mode 100644
index abeed0dc4eb3d77339a62857a906dd25146da8ff..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp
+++ /dev/null
@@ -1,183 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Array/JsonArray.hpp>
-#include <ArduinoJson/Configuration.hpp>
-#include <ArduinoJson/Numbers/convertNumber.hpp>
-#include <ArduinoJson/Numbers/parseNumber.hpp>
-#include <ArduinoJson/Object/JsonObject.hpp>
-#include <ArduinoJson/Variant/JsonVariant.hpp>
-
-#include <string.h>  // for strcmp
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-template <typename T>
-inline T VariantData::asIntegral() const {
-  switch (type()) {
-    case VALUE_IS_BOOLEAN:
-      return content_.asBoolean;
-    case VALUE_IS_UNSIGNED_INTEGER:
-      return convertNumber<T>(content_.asUnsignedInteger);
-    case VALUE_IS_SIGNED_INTEGER:
-      return convertNumber<T>(content_.asSignedInteger);
-    case VALUE_IS_LINKED_STRING:
-    case VALUE_IS_OWNED_STRING:
-      return parseNumber<T>(content_.asString.data);
-    case VALUE_IS_FLOAT:
-      return convertNumber<T>(content_.asFloat);
-    default:
-      return 0;
-  }
-}
-
-inline bool VariantData::asBoolean() const {
-  switch (type()) {
-    case VALUE_IS_BOOLEAN:
-      return content_.asBoolean;
-    case VALUE_IS_SIGNED_INTEGER:
-    case VALUE_IS_UNSIGNED_INTEGER:
-      return content_.asUnsignedInteger != 0;
-    case VALUE_IS_FLOAT:
-      return content_.asFloat != 0;
-    case VALUE_IS_NULL:
-      return false;
-    default:
-      return true;
-  }
-}
-
-// T = float/double
-template <typename T>
-inline T VariantData::asFloat() const {
-  switch (type()) {
-    case VALUE_IS_BOOLEAN:
-      return static_cast<T>(content_.asBoolean);
-    case VALUE_IS_UNSIGNED_INTEGER:
-      return static_cast<T>(content_.asUnsignedInteger);
-    case VALUE_IS_SIGNED_INTEGER:
-      return static_cast<T>(content_.asSignedInteger);
-    case VALUE_IS_LINKED_STRING:
-    case VALUE_IS_OWNED_STRING:
-      return parseNumber<T>(content_.asString.data);
-    case VALUE_IS_FLOAT:
-      return static_cast<T>(content_.asFloat);
-    default:
-      return 0;
-  }
-}
-
-inline JsonString VariantData::asString() const {
-  switch (type()) {
-    case VALUE_IS_LINKED_STRING:
-      return JsonString(content_.asString.data, content_.asString.size,
-                        JsonString::Linked);
-    case VALUE_IS_OWNED_STRING:
-      return JsonString(content_.asString.data, content_.asString.size,
-                        JsonString::Copied);
-    default:
-      return JsonString();
-  }
-}
-
-inline bool VariantData::copyFrom(const VariantData& src, MemoryPool* pool) {
-  switch (src.type()) {
-    case VALUE_IS_ARRAY:
-      return toArray().copyFrom(src.content_.asCollection, pool);
-    case VALUE_IS_OBJECT:
-      return toObject().copyFrom(src.content_.asCollection, pool);
-    case VALUE_IS_OWNED_STRING: {
-      JsonString value = src.asString();
-      return setString(adaptString(value), pool);
-    }
-    case VALUE_IS_OWNED_RAW:
-      return storeOwnedRaw(
-          serialized(src.content_.asString.data, src.content_.asString.size),
-          pool);
-    default:
-      setType(src.type());
-      content_ = src.content_;
-      return true;
-  }
-}
-
-template <typename TDerived>
-inline JsonVariant VariantRefBase<TDerived>::add() const {
-  return JsonVariant(getPool(),
-                     variantAddElement(getOrCreateData(), getPool()));
-}
-
-template <typename TDerived>
-template <typename T>
-inline typename enable_if<ConverterNeedsWriteableRef<T>::value, T>::type
-VariantRefBase<TDerived>::as() const {
-  return Converter<T>::fromJson(getVariant());
-}
-
-template <typename TDerived>
-inline JsonVariant VariantRefBase<TDerived>::getVariant() const {
-  return JsonVariant(getPool(), getData());
-}
-
-template <typename TDerived>
-inline JsonVariant VariantRefBase<TDerived>::getOrCreateVariant() const {
-  return JsonVariant(getPool(), getOrCreateData());
-}
-
-template <typename TDerived>
-template <typename T>
-inline typename enable_if<ConverterNeedsWriteableRef<T>::value, bool>::type
-VariantRefBase<TDerived>::is() const {
-  return Converter<T>::checkJson(getVariant());
-}
-
-template <typename TDerived>
-template <typename T>
-inline bool VariantRefBase<TDerived>::set(const T& value) const {
-  Converter<typename detail::remove_cv<T>::type>::toJson(value,
-                                                         getOrCreateVariant());
-  MemoryPool* pool = getPool();
-  return pool && !pool->overflowed();
-}
-
-template <typename TDerived>
-template <typename T>
-inline bool VariantRefBase<TDerived>::set(T* value) const {
-  Converter<T*>::toJson(value, getOrCreateVariant());
-  MemoryPool* pool = getPool();
-  return pool && !pool->overflowed();
-}
-
-template <typename TDerived>
-template <typename T>
-inline typename enable_if<is_same<T, JsonArray>::value, JsonArray>::type
-VariantRefBase<TDerived>::to() const {
-  return JsonArray(getPool(), variantToArray(getOrCreateData()));
-}
-
-template <typename TDerived>
-template <typename T>
-typename enable_if<is_same<T, JsonObject>::value, JsonObject>::type
-VariantRefBase<TDerived>::to() const {
-  return JsonObject(getPool(), variantToObject(getOrCreateData()));
-}
-
-template <typename TDerived>
-template <typename T>
-typename enable_if<is_same<T, JsonVariant>::value, JsonVariant>::type
-VariantRefBase<TDerived>::to() const {
-  auto data = getOrCreateData();
-  variantSetNull(data);
-  return JsonVariant(getPool(), data);
-}
-
-template <typename TDerived>
-inline void convertToJson(const VariantRefBase<TDerived>& src,
-                          JsonVariant dst) {
-  dst.set(src.template as<JsonVariantConst>());
-}
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantOperators.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantOperators.hpp
index a352f0c41a59e2f05cbbd2dc75e7608ad0627962..b06d46f646bd50f52469ae894207da870d134d01 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantOperators.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantOperators.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -31,9 +31,8 @@ struct VariantOperators : VariantOperatorTag {
   // float operator|(JsonVariant, float)
   // bool operator|(JsonVariant, bool)
   template <typename T>
-  friend
-      typename enable_if<!IsVariant<T>::value && !is_array<T>::value, T>::type
-      operator|(const TVariant& variant, const T& defaultValue) {
+  friend enable_if_t<!IsVariant<T>::value && !is_array<T>::value, T> operator|(
+      const TVariant& variant, const T& defaultValue) {
     if (variant.template is<T>())
       return variant.template as<T>();
     else
@@ -51,8 +50,8 @@ struct VariantOperators : VariantOperatorTag {
   //
   // JsonVariant operator|(JsonVariant, JsonVariant)
   template <typename T>
-  friend typename enable_if<IsVariant<T>::value, JsonVariantConst>::type
-  operator|(const TVariant& variant, T defaultValue) {
+  friend enable_if_t<IsVariant<T>::value, JsonVariantConst> operator|(
+      const TVariant& variant, T defaultValue) {
     if (variant)
       return variant;
     else
@@ -75,9 +74,8 @@ struct VariantOperators : VariantOperatorTag {
     return compare(lhs, rhs) == COMPARE_RESULT_EQUAL;
   }
   template <typename T>
-  friend
-      typename enable_if<!is_base_of<VariantOperatorTag, T>::value, bool>::type
-      operator==(TVariant lhs, const T& rhs) {
+  friend enable_if_t<!is_base_of<VariantOperatorTag, T>::value, bool>
+  operator==(TVariant lhs, const T& rhs) {
     return compare(lhs, rhs) == COMPARE_RESULT_EQUAL;
   }
 
@@ -97,9 +95,8 @@ struct VariantOperators : VariantOperatorTag {
     return compare(lhs, rhs) != COMPARE_RESULT_EQUAL;
   }
   template <typename T>
-  friend
-      typename enable_if<!is_base_of<VariantOperatorTag, T>::value, bool>::type
-      operator!=(TVariant lhs, const T& rhs) {
+  friend enable_if_t<!is_base_of<VariantOperatorTag, T>::value, bool>
+  operator!=(TVariant lhs, const T& rhs) {
     return compare(lhs, rhs) != COMPARE_RESULT_EQUAL;
   }
 
@@ -119,9 +116,8 @@ struct VariantOperators : VariantOperatorTag {
     return compare(lhs, rhs) == COMPARE_RESULT_LESS;
   }
   template <typename T>
-  friend
-      typename enable_if<!is_base_of<VariantOperatorTag, T>::value, bool>::type
-      operator<(TVariant lhs, const T& rhs) {
+  friend enable_if_t<!is_base_of<VariantOperatorTag, T>::value, bool> operator<(
+      TVariant lhs, const T& rhs) {
     return compare(lhs, rhs) == COMPARE_RESULT_LESS;
   }
 
@@ -141,9 +137,8 @@ struct VariantOperators : VariantOperatorTag {
     return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0;
   }
   template <typename T>
-  friend
-      typename enable_if<!is_base_of<VariantOperatorTag, T>::value, bool>::type
-      operator<=(TVariant lhs, const T& rhs) {
+  friend enable_if_t<!is_base_of<VariantOperatorTag, T>::value, bool>
+  operator<=(TVariant lhs, const T& rhs) {
     return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0;
   }
 
@@ -163,9 +158,8 @@ struct VariantOperators : VariantOperatorTag {
     return compare(lhs, rhs) == COMPARE_RESULT_GREATER;
   }
   template <typename T>
-  friend
-      typename enable_if<!is_base_of<VariantOperatorTag, T>::value, bool>::type
-      operator>(TVariant lhs, const T& rhs) {
+  friend enable_if_t<!is_base_of<VariantOperatorTag, T>::value, bool> operator>(
+      TVariant lhs, const T& rhs) {
     return compare(lhs, rhs) == COMPARE_RESULT_GREATER;
   }
 
@@ -185,9 +179,8 @@ struct VariantOperators : VariantOperatorTag {
     return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0;
   }
   template <typename T>
-  friend
-      typename enable_if<!is_base_of<VariantOperatorTag, T>::value, bool>::type
-      operator>=(TVariant lhs, const T& rhs) {
+  friend enable_if_t<!is_base_of<VariantOperatorTag, T>::value, bool>
+  operator>=(TVariant lhs, const T& rhs) {
     return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0;
   }
 };
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp
index 3f29608a47aa463794e174cbe54f14ea154dd93d..3966f36510d4117b90615061aea1fc4e76459091 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
@@ -27,232 +27,230 @@ class VariantRefBase : public VariantTag {
 
  public:
   // Sets the value to null.
-  // ⚠️ Doesn't release the memory associated with the previous value.
-  // https://arduinojson.org/v6/api/jsonvariant/clear/
-  FORCE_INLINE void clear() const {
-    variantSetNull(getData());
+  // https://arduinojson.org/v7/api/jsonvariant/clear/
+  void clear() const {
+    VariantData::setNull(getOrCreateData(), getResourceManager());
   }
 
   // Returns true if the value is null or the reference is unbound.
-  // https://arduinojson.org/v6/api/jsonvariant/isnull/
-  FORCE_INLINE bool isNull() const {
-    return variantIsNull(getData());
+  // https://arduinojson.org/v7/api/jsonvariant/isnull/
+  bool isNull() const {
+    return VariantData::isNull(getData());
   }
 
   // Returns true if the reference is unbound.
-  FORCE_INLINE bool isUnbound() const {
+  bool isUnbound() const {
     return !getData();
   }
 
   // Casts the value to the specified type.
-  // https://arduinojson.org/v6/api/jsonvariant/as/
+  // https://arduinojson.org/v7/api/jsonvariant/as/
   template <typename T>
-  FORCE_INLINE
-      typename enable_if<!ConverterNeedsWriteableRef<T>::value, T>::type
-      as() const {
-    return Converter<T>::fromJson(getVariantConst());
-  }
+  T as() const;
 
-  // Casts the value to the specified type.
-  // https://arduinojson.org/v6/api/jsonvariant/as/
-  template <typename T>
-  FORCE_INLINE typename enable_if<ConverterNeedsWriteableRef<T>::value, T>::type
-  as() const;
-
-  template <typename T,
-            typename = typename enable_if<!is_same<T, TDerived>::value>::type>
-  FORCE_INLINE operator T() const {
+  template <typename T, typename = enable_if_t<!is_same<T, TDerived>::value>>
+  operator T() const {
     return as<T>();
   }
 
   // Sets the value to an empty array.
-  // ⚠️ Doesn't release the memory associated with the previous value.
-  // https://arduinojson.org/v6/api/jsonvariant/to/
+  // https://arduinojson.org/v7/api/jsonvariant/to/
   template <typename T>
-  typename enable_if<is_same<T, JsonArray>::value, JsonArray>::type to() const;
+  enable_if_t<is_same<T, JsonArray>::value, JsonArray> to() const;
 
   // Sets the value to an empty object.
-  // ⚠️ Doesn't release the memory associated with the previous value.
-  // https://arduinojson.org/v6/api/jsonvariant/to/
+  // https://arduinojson.org/v7/api/jsonvariant/to/
   template <typename T>
-  typename enable_if<is_same<T, JsonObject>::value, JsonObject>::type to()
-      const;
+  enable_if_t<is_same<T, JsonObject>::value, JsonObject> to() const;
 
   // Sets the value to null.
-  // ⚠️ Doesn't release the memory associated with the previous value.
-  // https://arduinojson.org/v6/api/jsonvariant/to/
+  // https://arduinojson.org/v7/api/jsonvariant/to/
   template <typename T>
-  typename enable_if<is_same<T, JsonVariant>::value, JsonVariant>::type to()
-      const;
+  enable_if_t<is_same<T, JsonVariant>::value, JsonVariant> to() const;
 
   // Returns true if the value is of the specified type.
-  // https://arduinojson.org/v6/api/jsonvariant/is/
+  // https://arduinojson.org/v7/api/jsonvariant/is/
   template <typename T>
-  FORCE_INLINE
-      typename enable_if<ConverterNeedsWriteableRef<T>::value, bool>::type
-      is() const;
-
-  // Returns true if the value is of the specified type.
-  // https://arduinojson.org/v6/api/jsonvariant/is/
-  template <typename T>
-  FORCE_INLINE typename enable_if<!ConverterNeedsWriteableRef<T>::value &&
-                                      !is_same<T, char*>::value &&
-                                      !is_same<T, char>::value,
-                                  bool>::type
-  is() const {
-    return Converter<T>::checkJson(getVariantConst());
-  }
-
-  // Shallow copies the specified value.
-  // https://arduinojson.org/v6/api/jsonvariant/shallowcopy/
-  FORCE_INLINE void shallowCopy(ArduinoJson::JsonVariantConst target) {
-    VariantData* data = getOrCreateData();
-    if (!data)
-      return;
-    const VariantData* targetData = VariantAttorney::getData(target);
-    if (targetData)
-      *data = *targetData;
-    else
-      data->setNull();
-  }
+  FORCE_INLINE bool is() const;
 
   // Copies the specified value.
-  // https://arduinojson.org/v6/api/jsonvariant/set/
+  // https://arduinojson.org/v7/api/jsonvariant/set/
   template <typename T>
-  FORCE_INLINE bool set(const T& value) const;
+  bool set(const T& value) const {
+    return doSet<Converter<remove_cv_t<T>>>(value);
+  }
 
   // Copies the specified value.
-  // https://arduinojson.org/v6/api/jsonvariant/set/
+  // https://arduinojson.org/v7/api/jsonvariant/set/
   template <typename T>
-  FORCE_INLINE bool set(T* value) const;
+  bool set(T* value) const {
+    return doSet<Converter<T*>>(value);
+  }
 
   // Returns the size of the array or object.
-  // https://arduinojson.org/v6/api/jsonvariant/size/
-  FORCE_INLINE size_t size() const {
-    return variantSize(getData());
+  // https://arduinojson.org/v7/api/jsonvariant/size/
+  size_t size() const {
+    return VariantData::size(getData(), getResourceManager());
   }
 
-  // Returns the number of bytes occupied by the value.
-  // https://arduinojson.org/v6/api/jsonvariant/memoryusage/
-  FORCE_INLINE size_t memoryUsage() const {
-    VariantData* data = getData();
-    return data ? data->memoryUsage() : 0;
+  // Returns the depth (nesting level) of the value.
+  // https://arduinojson.org/v7/api/jsonvariant/nesting/
+  size_t nesting() const {
+    return VariantData::nesting(getData(), getResourceManager());
   }
 
-  // Returns the depth (nesting level) of the value.
-  // https://arduinojson.org/v6/api/jsonvariant/nesting/
-  FORCE_INLINE size_t nesting() const {
-    return variantNesting(getData());
+  // Appends a new (empty) element to the array.
+  // Returns a reference to the new element.
+  // https://arduinojson.org/v7/api/jsonvariant/add/
+  template <typename T>
+  enable_if_t<!is_same<T, JsonVariant>::value, T> add() const {
+    return add<JsonVariant>().template to<T>();
   }
 
   // Appends a new (null) element to the array.
   // Returns a reference to the new element.
-  // https://arduinojson.org/v6/api/jsonvariant/add/
-  FORCE_INLINE JsonVariant add() const;
+  // https://arduinojson.org/v7/api/jsonvariant/add/
+  template <typename T>
+  enable_if_t<is_same<T, JsonVariant>::value, T> add() const;
 
   // Appends a value to the array.
-  // https://arduinojson.org/v6/api/jsonvariant/add/
+  // https://arduinojson.org/v7/api/jsonvariant/add/
   template <typename T>
-  FORCE_INLINE bool add(const T& value) const {
-    return add().set(value);
+  bool add(const T& value) const {
+    return detail::VariantData::addValue(getOrCreateData(), value,
+                                         getResourceManager());
   }
 
   // Appends a value to the array.
-  // https://arduinojson.org/v6/api/jsonvariant/add/
+  // https://arduinojson.org/v7/api/jsonvariant/add/
   template <typename T>
-  FORCE_INLINE bool add(T* value) const {
-    return add().set(value);
+  bool add(T* value) const {
+    return detail::VariantData::addValue(getOrCreateData(), value,
+                                         getResourceManager());
   }
 
   // Removes an element of the array.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsonvariant/remove/
-  FORCE_INLINE void remove(size_t index) const {
-    VariantData* data = getData();
-    if (data)
-      data->remove(index);
+  // https://arduinojson.org/v7/api/jsonvariant/remove/
+  void remove(size_t index) const {
+    VariantData::removeElement(getData(), index, getResourceManager());
   }
 
   // Removes a member of the object.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsonvariant/remove/
+  // https://arduinojson.org/v7/api/jsonvariant/remove/
   template <typename TChar>
-  FORCE_INLINE typename enable_if<IsString<TChar*>::value>::type remove(
-      TChar* key) const {
-    VariantData* data = getData();
-    if (data)
-      data->remove(adaptString(key));
+  enable_if_t<IsString<TChar*>::value> remove(TChar* key) const {
+    VariantData::removeMember(getData(), adaptString(key),
+                              getResourceManager());
   }
 
   // Removes a member of the object.
-  // ⚠️ Doesn't release the memory associated with the removed element.
-  // https://arduinojson.org/v6/api/jsonvariant/remove/
+  // https://arduinojson.org/v7/api/jsonvariant/remove/
   template <typename TString>
-  FORCE_INLINE typename enable_if<IsString<TString>::value>::type remove(
-      const TString& key) const {
-    VariantData* data = getData();
-    if (data)
-      data->remove(adaptString(key));
+  enable_if_t<IsString<TString>::value> remove(const TString& key) const {
+    VariantData::removeMember(getData(), adaptString(key),
+                              getResourceManager());
   }
 
-  // Creates an array and appends it to the array.
-  // https://arduinojson.org/v6/api/jsonvariant/createnestedarray/
-  FORCE_INLINE JsonArray createNestedArray() const;
-
-  // Creates an object and appends it to the array.
-  // https://arduinojson.org/v6/api/jsonvariant/createnestedobject/
-  FORCE_INLINE JsonObject createNestedObject() const;
+  // Removes a member of the object or an element of the array.
+  // https://arduinojson.org/v7/api/jsonvariant/remove/
+  template <typename TVariant>
+  enable_if_t<IsVariant<TVariant>::value> remove(const TVariant& key) const {
+    if (key.template is<size_t>())
+      remove(key.template as<size_t>());
+    else
+      remove(key.template as<const char*>());
+  }
 
   // Gets or sets an array element.
-  // https://arduinojson.org/v6/api/jsonvariant/subscript/
-  FORCE_INLINE ElementProxy<TDerived> operator[](size_t index) const;
+  // https://arduinojson.org/v7/api/jsonvariant/subscript/
+  ElementProxy<TDerived> operator[](size_t index) const;
 
   // Returns true if the object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonvariant/containskey/
+  // https://arduinojson.org/v7/api/jsonvariant/containskey/
   template <typename TString>
-  FORCE_INLINE typename enable_if<IsString<TString>::value, bool>::type
-  containsKey(const TString& key) const;
+  enable_if_t<IsString<TString>::value, bool> containsKey(
+      const TString& key) const;
 
   // Returns true if the object contains the specified key.
-  // https://arduinojson.org/v6/api/jsonvariant/containskey/
+  // https://arduinojson.org/v7/api/jsonvariant/containskey/
   template <typename TChar>
-  FORCE_INLINE typename enable_if<IsString<TChar*>::value, bool>::type
-  containsKey(TChar* key) const;
+  enable_if_t<IsString<TChar*>::value, bool> containsKey(TChar* key) const;
+
+  // Returns true if the object contains the specified key.
+  // https://arduinojson.org/v7/api/jsonvariant/containskey/
+  template <typename TVariant>
+  enable_if_t<IsVariant<TVariant>::value, bool> containsKey(
+      const TVariant& key) const;
 
   // Gets or sets an object member.
-  // https://arduinojson.org/v6/api/jsonvariant/subscript/
+  // https://arduinojson.org/v7/api/jsonvariant/subscript/
   template <typename TString>
-  FORCE_INLINE typename enable_if<IsString<TString>::value,
-                                  MemberProxy<TDerived, TString>>::type
-  operator[](const TString& key) const;
+  FORCE_INLINE
+      enable_if_t<IsString<TString>::value, MemberProxy<TDerived, TString>>
+      operator[](const TString& key) const;
 
   // Gets or sets an object member.
-  // https://arduinojson.org/v6/api/jsonvariant/subscript/
+  // https://arduinojson.org/v7/api/jsonvariant/subscript/
   template <typename TChar>
-  FORCE_INLINE typename enable_if<IsString<TChar*>::value,
-                                  MemberProxy<TDerived, TChar*>>::type
-  operator[](TChar* key) const;
+  FORCE_INLINE
+      enable_if_t<IsString<TChar*>::value, MemberProxy<TDerived, TChar*>>
+      operator[](TChar* key) const;
+
+  // Gets an object member or an array element.
+  // https://arduinojson.org/v7/api/jsonvariant/subscript/
+  template <typename TVariant>
+  enable_if_t<IsVariant<TVariant>::value, JsonVariantConst> operator[](
+      const TVariant& key) const {
+    if (key.template is<size_t>())
+      return operator[](key.template as<size_t>());
+    else
+      return operator[](key.template as<const char*>());
+  }
 
-  // Creates an array and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonvariant/createnestedarray/
-  template <typename TString>
-  FORCE_INLINE JsonArray createNestedArray(const TString& key) const;
+  // DEPRECATED: use add<JsonVariant>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonVariant>() instead")
+  JsonVariant add() const;
 
-  // Creates an array and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonvariant/createnestedarray/
+  // DEPRECATED: use add<JsonArray>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonArray>() instead")
+  JsonArray createNestedArray() const;
+
+  // DEPRECATED: use var[key].to<JsonArray>() instead
   template <typename TChar>
-  FORCE_INLINE JsonArray createNestedArray(TChar* key) const;
+  ARDUINOJSON_DEPRECATED("use var[key].to<JsonArray>() instead")
+  JsonArray createNestedArray(TChar* key) const;
 
-  // Creates an object and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonvariant/createnestedobject/
+  // DEPRECATED: use var[key].to<JsonArray>() instead
   template <typename TString>
-  JsonObject createNestedObject(const TString& key) const;
+  ARDUINOJSON_DEPRECATED("use var[key].to<JsonArray>() instead")
+  JsonArray createNestedArray(const TString& key) const;
+
+  // DEPRECATED: use add<JsonObject>() instead
+  ARDUINOJSON_DEPRECATED("use add<JsonObject>() instead")
+  JsonObject createNestedObject() const;
 
-  // Creates an object and adds it to the object.
-  // https://arduinojson.org/v6/api/jsonvariant/createnestedobject/
+  // DEPRECATED: use var[key].to<JsonObject>() instead
   template <typename TChar>
+  ARDUINOJSON_DEPRECATED("use var[key].to<JsonObject>() instead")
   JsonObject createNestedObject(TChar* key) const;
 
+  // DEPRECATED: use var[key].to<JsonObject>() instead
+  template <typename TString>
+  ARDUINOJSON_DEPRECATED("use var[key].to<JsonObject>() instead")
+  JsonObject createNestedObject(const TString& key) const;
+
+  // DEPRECATED: always returns zero
+  ARDUINOJSON_DEPRECATED("always returns zero")
+  size_t memoryUsage() const {
+    return 0;
+  }
+
+  // DEPRECATED: performs a deep copy
+  ARDUINOJSON_DEPRECATED("performs a deep copy")
+  void shallowCopy(JsonVariantConst src) const {
+    set(src);
+  }
+
  private:
   TDerived& derived() {
     return static_cast<TDerived&>(*this);
@@ -262,26 +260,52 @@ class VariantRefBase : public VariantTag {
     return static_cast<const TDerived&>(*this);
   }
 
-  FORCE_INLINE MemoryPool* getPool() const {
-    return VariantAttorney::getPool(derived());
+  ResourceManager* getResourceManager() const {
+    return VariantAttorney::getResourceManager(derived());
   }
 
-  FORCE_INLINE VariantData* getData() const {
+  VariantData* getData() const {
     return VariantAttorney::getData(derived());
   }
 
-  FORCE_INLINE VariantData* getOrCreateData() const {
+  VariantData* getOrCreateData() const {
     return VariantAttorney::getOrCreateData(derived());
   }
 
- private:
   FORCE_INLINE ArduinoJson::JsonVariant getVariant() const;
 
   FORCE_INLINE ArduinoJson::JsonVariantConst getVariantConst() const {
-    return ArduinoJson::JsonVariantConst(getData());
+    return ArduinoJson::JsonVariantConst(getData(), getResourceManager());
+  }
+
+  template <typename T>
+  FORCE_INLINE enable_if_t<is_same<T, JsonVariantConst>::value, T> getVariant()
+      const {
+    return getVariantConst();
+  }
+
+  template <typename T>
+  FORCE_INLINE enable_if_t<is_same<T, JsonVariant>::value, T> getVariant()
+      const {
+    return getVariant();
+  }
+
+  template <typename TConverter, typename T>
+  bool doSet(T&& value) const {
+    return doSet<TConverter>(
+        detail::forward<T>(value),
+        is_same<typename function_traits<
+                    decltype(&TConverter::toJson)>::return_type,
+                bool>{});
   }
 
-  FORCE_INLINE ArduinoJson::JsonVariant getOrCreateVariant() const;
+  template <typename TConverter, typename T>
+  bool doSet(T&& value, false_type) const;
+
+  template <typename TConverter, typename T>
+  bool doSet(T&& value, true_type) const;
+
+  ArduinoJson::JsonVariant getOrCreateVariant() const;
 };
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..023dfd520962d610538024336bfa13eb8eee931e
--- /dev/null
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp
@@ -0,0 +1,181 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2024, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Array/JsonArray.hpp>
+#include <ArduinoJson/Object/JsonObject.hpp>
+#include <ArduinoJson/Variant/VariantRefBase.hpp>
+
+ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
+
+template <typename TDerived>
+inline JsonVariant VariantRefBase<TDerived>::add() const {
+  return add<JsonVariant>();
+}
+
+template <typename TDerived>
+template <typename T>
+inline T VariantRefBase<TDerived>::as() const {
+  using variant_type =  // JsonVariantConst or JsonVariant?
+      typename function_traits<decltype(&Converter<T>::fromJson)>::arg1_type;
+  return Converter<T>::fromJson(getVariant<variant_type>());
+}
+
+template <typename TDerived>
+inline JsonArray VariantRefBase<TDerived>::createNestedArray() const {
+  return add<JsonArray>();
+}
+
+template <typename TDerived>
+template <typename TChar>
+inline JsonArray VariantRefBase<TDerived>::createNestedArray(TChar* key) const {
+  return operator[](key).template to<JsonArray>();
+}
+
+template <typename TDerived>
+template <typename TString>
+inline JsonArray VariantRefBase<TDerived>::createNestedArray(
+    const TString& key) const {
+  return operator[](key).template to<JsonArray>();
+}
+
+template <typename TDerived>
+inline JsonObject VariantRefBase<TDerived>::createNestedObject() const {
+  return add<JsonObject>();
+}
+
+template <typename TDerived>
+template <typename TChar>
+inline JsonObject VariantRefBase<TDerived>::createNestedObject(
+    TChar* key) const {
+  return operator[](key).template to<JsonObject>();
+}
+
+template <typename TDerived>
+template <typename TString>
+inline JsonObject VariantRefBase<TDerived>::createNestedObject(
+    const TString& key) const {
+  return operator[](key).template to<JsonObject>();
+}
+
+template <typename TDerived>
+inline void convertToJson(const VariantRefBase<TDerived>& src,
+                          JsonVariant dst) {
+  dst.set(src.template as<JsonVariantConst>());
+}
+
+template <typename TDerived>
+template <typename T>
+inline enable_if_t<is_same<T, JsonVariant>::value, T>
+VariantRefBase<TDerived>::add() const {
+  return JsonVariant(
+      detail::VariantData::addElement(getOrCreateData(), getResourceManager()),
+      getResourceManager());
+}
+
+template <typename TDerived>
+template <typename TString>
+inline enable_if_t<IsString<TString>::value, bool>
+VariantRefBase<TDerived>::containsKey(const TString& key) const {
+  return VariantData::getMember(getData(), adaptString(key),
+                                getResourceManager()) != 0;
+}
+
+template <typename TDerived>
+template <typename TChar>
+inline enable_if_t<IsString<TChar*>::value, bool>
+VariantRefBase<TDerived>::containsKey(TChar* key) const {
+  return VariantData::getMember(getData(), adaptString(key),
+                                getResourceManager()) != 0;
+}
+
+template <typename TDerived>
+template <typename TVariant>
+inline enable_if_t<IsVariant<TVariant>::value, bool>
+VariantRefBase<TDerived>::containsKey(const TVariant& key) const {
+  return containsKey(key.template as<const char*>());
+}
+
+template <typename TDerived>
+inline JsonVariant VariantRefBase<TDerived>::getVariant() const {
+  return JsonVariant(getData(), getResourceManager());
+}
+
+template <typename TDerived>
+inline JsonVariant VariantRefBase<TDerived>::getOrCreateVariant() const {
+  return JsonVariant(getOrCreateData(), getResourceManager());
+}
+
+template <typename TDerived>
+template <typename T>
+inline bool VariantRefBase<TDerived>::is() const {
+  using variant_type =  // JsonVariantConst or JsonVariant?
+      typename function_traits<decltype(&Converter<T>::checkJson)>::arg1_type;
+  return Converter<T>::checkJson(getVariant<variant_type>());
+}
+
+template <typename TDerived>
+inline ElementProxy<TDerived> VariantRefBase<TDerived>::operator[](
+    size_t index) const {
+  return ElementProxy<TDerived>(derived(), index);
+}
+
+template <typename TDerived>
+template <typename TString>
+inline enable_if_t<IsString<TString*>::value, MemberProxy<TDerived, TString*>>
+VariantRefBase<TDerived>::operator[](TString* key) const {
+  return MemberProxy<TDerived, TString*>(derived(), key);
+}
+
+template <typename TDerived>
+template <typename TString>
+inline enable_if_t<IsString<TString>::value, MemberProxy<TDerived, TString>>
+VariantRefBase<TDerived>::operator[](const TString& key) const {
+  return MemberProxy<TDerived, TString>(derived(), key);
+}
+
+template <typename TDerived>
+template <typename TConverter, typename T>
+inline bool VariantRefBase<TDerived>::doSet(T&& value, false_type) const {
+  TConverter::toJson(value, getOrCreateVariant());
+  auto resources = getResourceManager();
+  return resources && !resources->overflowed();
+}
+
+template <typename TDerived>
+template <typename TConverter, typename T>
+inline bool VariantRefBase<TDerived>::doSet(T&& value, true_type) const {
+  return TConverter::toJson(value, getOrCreateVariant());
+}
+
+template <typename TDerived>
+template <typename T>
+inline enable_if_t<is_same<T, JsonArray>::value, JsonArray>
+VariantRefBase<TDerived>::to() const {
+  return JsonArray(
+      VariantData::toArray(getOrCreateData(), getResourceManager()),
+      getResourceManager());
+}
+
+template <typename TDerived>
+template <typename T>
+enable_if_t<is_same<T, JsonObject>::value, JsonObject>
+VariantRefBase<TDerived>::to() const {
+  return JsonObject(
+      VariantData::toObject(getOrCreateData(), getResourceManager()),
+      getResourceManager());
+}
+
+template <typename TDerived>
+template <typename T>
+enable_if_t<is_same<T, JsonVariant>::value, JsonVariant>
+VariantRefBase<TDerived>::to() const {
+  auto data = getOrCreateData();
+  auto resources = getResourceManager();
+  detail::VariantData::setNull(data, resources);
+  return JsonVariant(data, resources);
+}
+
+ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantSlot.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantSlot.hpp
index f88e35badc37f566c020c0c82b4b754fd683cfb6..35107fd268497f5547a796816230813263630cef 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantSlot.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantSlot.hpp
@@ -1,17 +1,17 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
-#include <ArduinoJson/Polyfills/integer.hpp>
+#include <ArduinoJson/Memory/ResourceManager.hpp>
 #include <ArduinoJson/Polyfills/limits.hpp>
 #include <ArduinoJson/Polyfills/type_traits.hpp>
 #include <ArduinoJson/Variant/VariantContent.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
-typedef int_t<ARDUINOJSON_SLOT_OFFSET_SIZE * 8>::type VariantSlotDiff;
+struct StringNode;
 
 class VariantSlot {
   // CAUTION: same layout as VariantData
@@ -19,15 +19,18 @@ class VariantSlot {
   // (+20% on ESP8266 for example)
   VariantContent content_;
   uint8_t flags_;
-  VariantSlotDiff next_;
+  SlotId next_;
   const char* key_;
 
  public:
-  // Must be a POD!
-  // - no constructor
-  // - no destructor
-  // - no virtual
-  // - no inheritance
+  // Placement new
+  static void* operator new(size_t, void* p) noexcept {
+    return p;
+  }
+
+  static void operator delete(void*, void*) noexcept {}
+
+  VariantSlot() : flags_(0), next_(NULL_SLOT), key_(0) {}
 
   VariantData* data() {
     return reinterpret_cast<VariantData*>(&content_);
@@ -37,52 +40,24 @@ class VariantSlot {
     return reinterpret_cast<const VariantData*>(&content_);
   }
 
-  VariantSlot* next() {
-    return next_ ? this + next_ : 0;
-  }
-
-  const VariantSlot* next() const {
-    return const_cast<VariantSlot*>(this)->next();
-  }
-
-  VariantSlot* next(size_t distance) {
-    VariantSlot* slot = this;
-    while (distance--) {
-      if (!slot->next_)
-        return 0;
-      slot += slot->next_;
-    }
-    return slot;
+  SlotId next() const {
+    return next_;
   }
 
-  const VariantSlot* next(size_t distance) const {
-    return const_cast<VariantSlot*>(this)->next(distance);
+  void setNext(SlotId slot) {
+    next_ = slot;
   }
 
-  void setNext(VariantSlot* slot) {
-    ARDUINOJSON_ASSERT(!slot || slot - this >=
-                                    numeric_limits<VariantSlotDiff>::lowest());
-    ARDUINOJSON_ASSERT(!slot || slot - this <=
-                                    numeric_limits<VariantSlotDiff>::highest());
-    next_ = VariantSlotDiff(slot ? slot - this : 0);
-  }
-
-  void setNextNotNull(VariantSlot* slot) {
-    ARDUINOJSON_ASSERT(slot != 0);
-    ARDUINOJSON_ASSERT(slot - this >=
-                       numeric_limits<VariantSlotDiff>::lowest());
-    ARDUINOJSON_ASSERT(slot - this <=
-                       numeric_limits<VariantSlotDiff>::highest());
-    next_ = VariantSlotDiff(slot - this);
+  void setKey(const char* k) {
+    ARDUINOJSON_ASSERT(k);
+    flags_ &= VALUE_MASK;
+    key_ = k;
   }
 
-  void setKey(JsonString k) {
+  void setKey(StringNode* k) {
     ARDUINOJSON_ASSERT(k);
-    if (k.isLinked())
-      flags_ &= VALUE_MASK;
-    else
-      flags_ |= OWNED_KEY_BIT;
-    key_ = k.c_str();
+    flags_ |= OWNED_KEY_BIT;
+    key_ = k->data;
   }
 
   const char* key() const {
@@ -92,21 +67,20 @@ class VariantSlot {
   bool ownsKey() const {
     return (flags_ & OWNED_KEY_BIT) != 0;
   }
+};
 
-  void clear() {
-    next_ = 0;
-    flags_ = 0;
-    key_ = 0;
-  }
+inline VariantData* slotData(VariantSlot* slot) {
+  return reinterpret_cast<VariantData*>(slot);
+}
 
-  void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance) {
-    if (flags_ & OWNED_KEY_BIT)
-      key_ += stringDistance;
-    if (flags_ & OWNED_VALUE_BIT)
-      content_.asString.data += stringDistance;
-    if (flags_ & COLLECTION_MASK)
-      content_.asCollection.movePointers(stringDistance, variantDistance);
-  }
-};
+// Returns the size (in bytes) of an array with n elements.
+constexpr size_t sizeofArray(size_t n) {
+  return n * sizeof(VariantSlot);
+}
+
+// Returns the size (in bytes) of an object with n members.
+constexpr size_t sizeofObject(size_t n) {
+  return n * sizeof(VariantSlot);
+}
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTag.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTag.hpp
index 0c5ee40db76e4889bdf944d78ca1bc86f770e306..23f360f0297c043d0761de80135b3787110e8fc3 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTag.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTag.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTo.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTo.hpp
index 0ca56df60300cf7622eb64e4041bee1b2f66c76b..c533040dbe91d24dbaa7072a1a7b53a972504bc1 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTo.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/VariantTo.hpp
@@ -1,5 +1,5 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/Visitor.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/Visitor.hpp
deleted file mode 100644
index f1c784165d468333ba1634a276aebd90a34b680c..0000000000000000000000000000000000000000
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/Variant/Visitor.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-// ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
-// MIT License
-
-#pragma once
-
-#include <ArduinoJson/Collection/CollectionData.hpp>
-#include <ArduinoJson/Numbers/JsonFloat.hpp>
-#include <ArduinoJson/Numbers/JsonInteger.hpp>
-
-ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
-
-template <typename TResult>
-struct Visitor {
-  typedef TResult result_type;
-
-  TResult visitArray(const CollectionData&) {
-    return TResult();
-  }
-
-  TResult visitBoolean(bool) {
-    return TResult();
-  }
-
-  TResult visitFloat(JsonFloat) {
-    return TResult();
-  }
-
-  TResult visitSignedInteger(JsonInteger) {
-    return TResult();
-  }
-
-  TResult visitNull() {
-    return TResult();
-  }
-
-  TResult visitObject(const CollectionData&) {
-    return TResult();
-  }
-
-  TResult visitUnsignedInteger(JsonUInt) {
-    return TResult();
-  }
-
-  TResult visitRawJson(const char*, size_t) {
-    return TResult();
-  }
-
-  TResult visitString(const char*, size_t) {
-    return TResult();
-  }
-};
-
-ARDUINOJSON_END_PRIVATE_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/compatibility.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/compatibility.hpp
index 85769487d84470e625609855d859c8099e9da061..6b99c4248bc8d01f387378256aa8db7a1e3f917c 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/compatibility.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/compatibility.hpp
@@ -1,9 +1,19 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 //
 // clang-format off
 
+#include <ArduinoJson/Namespace.hpp>
+
+#ifdef ARDUINOJSON_SLOT_OFFSET_SIZE
+#error ARDUINOJSON_SLOT_OFFSET_SIZE has been removed, use ARDUINOJSON_SLOT_ID_SIZE instead
+#endif
+
+#ifdef ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
+#warning "ARDUINOJSON_ENABLE_STRING_DEDUPLICATION has been removed, string deduplication is now always enabled"
+#endif
+
 #ifdef __GNUC__
 
 #define ARDUINOJSON_PRAGMA(x) _Pragma(#x)
@@ -13,7 +23,7 @@
 #define ARDUINOJSON_STRINGIFY(S) #S
 
 #define ARDUINOJSON_DEPRECATION_ERROR(X, Y) \
-  ARDUINOJSON_COMPILE_ERROR(ARDUINOJSON_STRINGIFY(X is a Y from ArduinoJson 5. Please see https:/\/arduinojson.org/upgrade to learn how to upgrade your program to ArduinoJson version 6))
+  ARDUINOJSON_COMPILE_ERROR(ARDUINOJSON_STRINGIFY(X is a Y from ArduinoJson 5. Please see https:/\/arduinojson.org/v7/upgrade-from-v5/ to learn how to upgrade to ArduinoJson 7))
 
 #define StaticJsonBuffer ARDUINOJSON_DEPRECATION_ERROR(StaticJsonBuffer, class)
 #define DynamicJsonBuffer ARDUINOJSON_DEPRECATION_ERROR(DynamicJsonBuffer, class)
@@ -22,4 +32,113 @@
 
 #define ARDUINOJSON_NAMESPACE _Pragma ("GCC warning \"ARDUINOJSON_NAMESPACE is deprecated, use ArduinoJson instead\"") ArduinoJson
 
+// DEPRECATED: you don't need to compute the size anymore
+#define JSON_ARRAY_SIZE(N) _Pragma ("GCC warning \"JSON_ARRAY_SIZE is deprecated, you don't need to compute the size anymore\"") (ArduinoJson::detail::sizeofArray(N))
+
+// DEPRECATED: you don't need to compute the size anymore
+#define JSON_OBJECT_SIZE(N) _Pragma ("GCC warning \"JSON_OBJECT_SIZE is deprecated, you don't need to compute the size anymore\"") (ArduinoJson::detail::sizeofObject(N))
+
+// DEPRECATED: you don't need to compute the size anymore
+#define JSON_STRING_SIZE(N) _Pragma ("GCC warning \"JSON_STRING_SIZE is deprecated, you don't need to compute the size anymore\"") (N+1)
+
+#else
+
+// DEPRECATED: you don't need to compute the size anymore
+#define JSON_ARRAY_SIZE(N) (ArduinoJson::detail::sizeofArray(N))
+
+// DEPRECATED: you don't need to compute the size anymore
+#define JSON_OBJECT_SIZE(N) (ArduinoJson::detail::sizeofObject(N))
+
+// DEPRECATED: you don't need to compute the size anymore
+#define JSON_STRING_SIZE(N) (N+1)
+
 #endif
+
+// clang-format on
+
+ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
+
+// DEPRECATED: use JsonDocument instead
+template <size_t N>
+class ARDUINOJSON_DEPRECATED("use JsonDocument instead") StaticJsonDocument
+    : public JsonDocument {
+ public:
+  using JsonDocument::JsonDocument;
+
+  size_t capacity() const {
+    return N;
+  }
+};
+
+namespace detail {
+template <typename TAllocator>
+class AllocatorAdapter : public Allocator {
+ public:
+  AllocatorAdapter(const AllocatorAdapter&) = delete;
+  AllocatorAdapter& operator=(const AllocatorAdapter&) = delete;
+
+  void* allocate(size_t size) override {
+    return _allocator.allocate(size);
+  }
+
+  void deallocate(void* ptr) override {
+    _allocator.deallocate(ptr);
+  }
+
+  void* reallocate(void* ptr, size_t new_size) override {
+    return _allocator.reallocate(ptr, new_size);
+  }
+
+  static Allocator* instance() {
+    static AllocatorAdapter instance;
+    return &instance;
+  }
+
+ private:
+  AllocatorAdapter() = default;
+  ~AllocatorAdapter() = default;
+
+  TAllocator _allocator;
+};
+}  // namespace detail
+
+// DEPRECATED: use JsonDocument instead
+template <typename TAllocator>
+class ARDUINOJSON_DEPRECATED("use JsonDocument instead") BasicJsonDocument
+    : public JsonDocument {
+ public:
+  BasicJsonDocument(size_t capacity)
+      : JsonDocument(detail::AllocatorAdapter<TAllocator>::instance()),
+        _capacity(capacity) {}
+
+  size_t capacity() const {
+    return _capacity;
+  }
+
+  void garbageCollect() {}
+
+ private:
+  size_t _capacity;
+};
+
+// DEPRECATED: use JsonDocument instead
+class ARDUINOJSON_DEPRECATED("use JsonDocument instead") DynamicJsonDocument
+    : public JsonDocument {
+ public:
+  DynamicJsonDocument(size_t capacity) : _capacity(capacity) {}
+
+  size_t capacity() const {
+    return _capacity;
+  }
+
+  void garbageCollect() {}
+
+ private:
+  size_t _capacity;
+};
+
+inline JsonObject JsonArray::createNestedObject() const {
+  return add<JsonObject>();
+}
+
+ARDUINOJSON_END_PUBLIC_NAMESPACE
diff --git a/neosensor/libraries/ArduinoJson/src/ArduinoJson/version.hpp b/neosensor/libraries/ArduinoJson/src/ArduinoJson/version.hpp
index 160980e99a8ae593c01bda4e04107e30f9b2ed0a..7750092874b997bfab22d413c3127abbe90474dd 100644
--- a/neosensor/libraries/ArduinoJson/src/ArduinoJson/version.hpp
+++ b/neosensor/libraries/ArduinoJson/src/ArduinoJson/version.hpp
@@ -1,11 +1,11 @@
 // ArduinoJson - https://arduinojson.org
-// Copyright © 2014-2023, Benoit BLANCHON
+// Copyright © 2014-2024, Benoit BLANCHON
 // MIT License
 
 #pragma once
 
-#define ARDUINOJSON_VERSION "6.21.5"
-#define ARDUINOJSON_VERSION_MAJOR 6
-#define ARDUINOJSON_VERSION_MINOR 21
-#define ARDUINOJSON_VERSION_REVISION 5
-#define ARDUINOJSON_VERSION_MACRO V6215
+#define ARDUINOJSON_VERSION "7.1.0"
+#define ARDUINOJSON_VERSION_MAJOR 7
+#define ARDUINOJSON_VERSION_MINOR 1
+#define ARDUINOJSON_VERSION_REVISION 0
+#define ARDUINOJSON_VERSION_MACRO V710
diff --git a/neosensor/libraries/ArduinoJson/src/CMakeLists.txt b/neosensor/libraries/ArduinoJson/src/CMakeLists.txt
index 10aef8089eaf40b65207e7752dc50b69e1bfa8d8..cc782e7028dbdff733d940686920cffd9a4600dd 100644
--- a/neosensor/libraries/ArduinoJson/src/CMakeLists.txt
+++ b/neosensor/libraries/ArduinoJson/src/CMakeLists.txt
@@ -1,5 +1,5 @@
 # ArduinoJson - https://arduinojson.org
-# Copyright © 2014-2023, Benoit BLANCHON
+# Copyright © 2014-2024, Benoit BLANCHON
 # MIT License
 
 # I have no idea what this is about, I simply followed the instructions from:
diff --git a/neosensor/libraries/WiFiManager/CMakeLists.txt b/neosensor/libraries/WiFiManager/CMakeLists.txt
index 7bd408a5e7dcade6c5dc75fd4eb842c328addab9..c87bb2035b4ff37f338b49d9f843991cb52155fd 100644
--- a/neosensor/libraries/WiFiManager/CMakeLists.txt
+++ b/neosensor/libraries/WiFiManager/CMakeLists.txt
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.5)
 idf_component_register(
                        SRCS "WiFiManager.cpp"
                        INCLUDE_DIRS "."
-                       REQUIRES arduino
+                       PRIV_REQUIRES arduino
 )
 
 project(WiFiManager)
diff --git a/neosensor/libraries/WiFiManager/README.md b/neosensor/libraries/WiFiManager/README.md
index a5c912a96b94dcf26dbd1e93622a32557b6d56c7..29976c6133de2165f3a0903d46b3114c737fa00f 100644
--- a/neosensor/libraries/WiFiManager/README.md
+++ b/neosensor/libraries/WiFiManager/README.md
@@ -1,12 +1,12 @@
 
 # WiFiManager
-## DEVELOPMENT Version
 
 Espressif ESPx WiFi Connection manager with fallback web configuration portal
 
 :warning: This Documentation is out of date, see notes below
 
-![Release](https://img.shields.io/github/v/release/tzapu/WiFiManager?include_prereleases)
+<a name="release"></a>
+[![Release](https://img.shields.io/github/v/release/tzapu/WiFiManager?include_prereleases)](#release)
 
 [![Build CI Status](https://github.com/tzapu/WiFiManager/actions/workflows/compile_library.yml/badge.svg)](https://github.com/tzapu/WiFiManager/actions/workflows/compile_library.yml)
 
@@ -21,6 +21,7 @@ Espressif ESPx WiFi Connection manager with fallback web configuration portal
 [![ESP32](https://img.shields.io/badge/ESP-32-000000.svg?longCache=true&style=flat&colorA=CC101F)](https://www.espressif.com/en/products/socs/esp32)
 [![ESP32](https://img.shields.io/badge/ESP-32S2-000000.svg?longCache=true&style=flat&colorA=CC101F)](https://www.espressif.com/en/products/socs/esp32-s2)
 [![ESP32](https://img.shields.io/badge/ESP-32C3-000000.svg?longCache=true&style=flat&colorA=CC101F)](https://www.espressif.com/en/products/socs/esp32-c3)
+[![ESP32](https://img.shields.io/badge/ESP-32S3-000000.svg?longCache=true&style=flat&colorA=CC101F)](https://www.espressif.com/en/products/socs/esp32-S3)
 
 Member to Member Support / Chat
 
@@ -29,15 +30,10 @@ Member to Member Support / Chat
 [![Discord](https://img.shields.io/badge/Discord-WiFiManager-%237289da.svg?logo=discord)](https://discord.gg/nS5WGkaQH5)
 The configuration portal is of the captive variety, so on various devices it will present the configuration dialogue as soon as you connect to the created access point.
 
-**This works with the ESP8266 Arduino platform**
-
-[https://github.com/esp8266/Arduino](https://github.com/esp8266/Arduino)
-
-**This works with the ESP32 Arduino platform** 
-
-[https://github.com/espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)
+Works with the [ESP8266 Arduino](https://github.com/esp8266/Arduino) and [ESP32 Arduino](https://github.com/espressif/arduino-esp32) platforms.
 
 ### Known Issues
+
 * Documentation needs to be updated, see [https://github.com/tzapu/WiFiManager/issues/500](https://github.com/tzapu/WiFiManager/issues/500)
 -------
 
diff --git a/neosensor/libraries/WiFiManager/WiFiManager.cpp b/neosensor/libraries/WiFiManager/WiFiManager.cpp
index edd1ed3ad2fca1c83280b342df05cf6aee16e38a..ca2f1da29557da9a70a74cd461820fd32c48e397 100644
--- a/neosensor/libraries/WiFiManager/WiFiManager.cpp
+++ b/neosensor/libraries/WiFiManager/WiFiManager.cpp
@@ -31,7 +31,7 @@ WiFiManagerParameter::WiFiManagerParameter() {
 WiFiManagerParameter::WiFiManagerParameter(const char *custom) {
   _id             = NULL;
   _label          = NULL;
-  _length         = 1;
+  _length         = 0;
   _value          = nullptr;
   _labelPlacement = WFM_LABEL_DEFAULT;
   _customHTML     = custom;
@@ -58,7 +58,7 @@ void WiFiManagerParameter::init(const char *id, const char *label, const char *d
   _label          = label;
   _labelPlacement = labelPlacement;
   _customHTML     = custom;
-  _length         = 1;
+  _length         = 0;
   _value          = nullptr;
   setValue(defaultValue,length);
 }
@@ -88,7 +88,7 @@ void WiFiManagerParameter::setValue(const char *defaultValue, int length) {
   //   // return false; //@todo bail 
   // }
 
-  if(_length != length){
+  if(_length != length || _value == nullptr){
     _length = length;
     if( _value != nullptr){
       delete[] _value;
@@ -137,7 +137,7 @@ bool WiFiManager::addParameter(WiFiManagerParameter *p) {
     for (size_t i = 0; i < strlen(p->getID()); i++){
        if(!(isAlphaNumeric(p->getID()[i])) && !(p->getID()[i]=='_')){
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_ERROR,F("[ERROR] parameter IDs can only contain alpha numeric chars"));
+        DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] parameter IDs can only contain alpha numeric chars"));
         #endif
         return false;
        }
@@ -147,7 +147,7 @@ bool WiFiManager::addParameter(WiFiManagerParameter *p) {
   // init params if never malloc
   if(_params == NULL){
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("allocating params bytes:"),_max_params * sizeof(WiFiManagerParameter*));        
+    DEBUG_WM(WM_DEBUG_DEV,F("allocating params bytes:"),_max_params * sizeof(WiFiManagerParameter*));        
     #endif
     _params = (WiFiManagerParameter**)malloc(_max_params * sizeof(WiFiManagerParameter*));
   }
@@ -156,8 +156,8 @@ bool WiFiManager::addParameter(WiFiManagerParameter *p) {
   if(_paramsCount == _max_params){
     _max_params += WIFI_MANAGER_MAX_PARAMS;
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("Updated _max_params:"),_max_params);
-    DEBUG_WM(DEBUG_DEV,F("re-allocating params bytes:"),_max_params * sizeof(WiFiManagerParameter*));    
+    DEBUG_WM(WM_DEBUG_DEV,F("Updated _max_params:"),_max_params);
+    DEBUG_WM(WM_DEBUG_DEV,F("re-allocating params bytes:"),_max_params * sizeof(WiFiManagerParameter*));    
     #endif
     WiFiManagerParameter** new_params = (WiFiManagerParameter**)realloc(_params, _max_params * sizeof(WiFiManagerParameter*));
     #ifdef WM_DEBUG_LEVEL
@@ -169,7 +169,7 @@ bool WiFiManager::addParameter(WiFiManagerParameter *p) {
       _params = new_params;
     } else {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_ERROR,F("[ERROR] failed to realloc params, size not increased!"));
+      DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] failed to realloc params, size not increased!"));
       #endif
       return false;
     }
@@ -179,7 +179,7 @@ bool WiFiManager::addParameter(WiFiManagerParameter *p) {
   _paramsCount++;
   
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("Added Parameter:"),p->getID());
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("Added Parameter:"),p->getID());
   #endif
   return true;
 }
@@ -217,7 +217,7 @@ WiFiManager::WiFiManager() {
 
 void WiFiManager::WiFiManagerInit(){
   setMenu(_menuIdsDefault);
-  if(_debug && _debugLevel >= DEBUG_DEV) debugPlatformInfo();
+  if(_debug && _debugLevel >= WM_DEBUG_DEV) debugPlatformInfo();
   _max_params = WIFI_MANAGER_MAX_PARAMS;
 }
 
@@ -228,7 +228,7 @@ WiFiManager::~WiFiManager() {
   // @todo below belongs to wifimanagerparameter
   if (_params != NULL){
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("freeing allocated params!"));
+    DEBUG_WM(WM_DEBUG_DEV,F("freeing allocated params!"));
     #endif
     free(_params);
     _params = NULL;
@@ -241,7 +241,7 @@ WiFiManager::~WiFiManager() {
   #endif
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("unloading"));
+  DEBUG_WM(WM_DEBUG_DEV,F("unloading"));
   #endif
 }
 
@@ -281,6 +281,7 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
   #endif
 
   // bool wifiIsSaved = getWiFiIsSaved();
+  bool wifiIsSaved = true; // workaround until I can check esp32 wifiisinit and has nvs
 
   #ifdef ESP32
   setupHostname(true);
@@ -300,7 +301,7 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
 
   // check if wifi is saved, (has autoconnect) to speed up cp start
   // NOT wifi init safe
-  // if(wifiIsSaved){
+  if(wifiIsSaved){
      _startconn = millis();
     _begin();
 
@@ -308,7 +309,7 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
     if(!WiFi.enableSTA(true)){
       // handle failure mode Brownout detector etc.
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_ERROR,F("[FATAL] Unable to enable wifi!"));
+      DEBUG_WM(WM_DEBUG_ERROR,F("[FATAL] Unable to enable wifi!"));
       #endif
       return false;
     }
@@ -350,7 +351,7 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
       //connected
       #ifdef WM_DEBUG_LEVEL
       DEBUG_WM(F("AutoConnect: SUCCESS"));
-      DEBUG_WM(DEBUG_VERBOSE,F("Connected in"),(String)((millis()-_startconn)) + " ms");
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("Connected in"),(String)((millis()-_startconn)) + " ms");
       DEBUG_WM(F("STA IP Address:"),WiFi.localIP());
       #endif
       // Serial.println("Connected in " + (String)((millis()-_startconn)) + " ms");
@@ -358,26 +359,26 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
 
       if(_hostname != ""){
         #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_DEV,F("hostname: STA: "),getWiFiHostname());
+          DEBUG_WM(WM_DEBUG_DEV,F("hostname: STA: "),getWiFiHostname());
         #endif
       }
       return true; // connected success
     }
 
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(F("AutoConnect: FAILED"));
+    DEBUG_WM(F("AutoConnect: FAILED for "),(String)((millis()-_startconn)) + " ms");
     #endif
-  // }
-  // else {
-    // #ifdef WM_DEBUG_LEVEL
-    // DEBUG_WM(F("No Credentials are Saved, skipping connect"));
-    // #endif
-  // } 
+  }
+  else {
+    #ifdef WM_DEBUG_LEVEL
+    DEBUG_WM(F("No Credentials are Saved, skipping connect"));
+    #endif
+  }
 
   // possibly skip the config portal
   if (!_enableConfigPortal) {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("enableConfigPortal: FALSE, skipping "));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("enableConfigPortal: FALSE, skipping "));
     #endif
 
     return false; // not connected and not cp
@@ -391,25 +392,25 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
 bool WiFiManager::setupHostname(bool restart){
   if(_hostname == "") {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("No Hostname to set"));
+    DEBUG_WM(WM_DEBUG_DEV,F("No Hostname to set"));
     #endif
     return false;
   } 
   else {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Setting Hostnames: "),_hostname);
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Setting Hostnames: "),_hostname);
     #endif
   }
   bool res = true;
   #ifdef ESP8266
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Setting WiFi hostname"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Setting WiFi hostname"));
     #endif
     res = WiFi.hostname(_hostname.c_str());
     // #ifdef ESP8266MDNS_H
     #ifdef WM_MDNS
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("Setting MDNS hostname, tcp 80"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("Setting MDNS hostname, tcp 80"));
       #endif
       if(MDNS.begin(_hostname.c_str())){
         MDNS.addService("http", "tcp", 80);
@@ -421,7 +422,7 @@ bool WiFiManager::setupHostname(bool restart){
     // same for S2, must set it before mode(STA) now
   
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Setting WiFi hostname"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Setting WiFi hostname"));
     #endif
 
     res = WiFi.setHostname(_hostname.c_str());
@@ -435,7 +436,7 @@ bool WiFiManager::setupHostname(bool restart){
     // #ifdef ESP32MDNS_H
       #ifdef WM_MDNS
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("Setting MDNS hostname, tcp 80"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("Setting MDNS hostname, tcp 80"));
         #endif
       if(MDNS.begin(_hostname.c_str())){
         MDNS.addService("http", "tcp", 80);
@@ -444,12 +445,12 @@ bool WiFiManager::setupHostname(bool restart){
   #endif
 
   #ifdef WM_DEBUG_LEVEL
-  if(!res)DEBUG_WM(DEBUG_ERROR,F("[ERROR] hostname: set failed!"));
+  if(!res)DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] hostname: set failed!"));
   #endif
 
   if(restart && (WiFi.status() == WL_CONNECTED)){
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("reconnecting to set new hostname"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("reconnecting to set new hostname"));
     #endif
     // WiFi.reconnect(); // This does not reset dhcp
     WiFi_Disconnect();
@@ -470,7 +471,7 @@ bool WiFiManager::startAP(){
     // @bug workaround for bug #4372 https://github.com/esp8266/Arduino/issues/4372
     if(!WiFi.enableAP(true)) {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_ERROR,F("[ERROR] enableAP failed!"));
+      DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] enableAP failed!"));
       #endif
       return false;
     }
@@ -484,7 +485,7 @@ bool WiFiManager::startAP(){
     #endif
     if(!WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn)){
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_ERROR,F("[ERROR] softAPConfig failed!"));
+      DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] softAPConfig failed!"));
       #endif
     }
   }
@@ -498,7 +499,7 @@ bool WiFiManager::startAP(){
 
   if(channel>0){
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Starting AP on channel:"),channel);
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Starting AP on channel:"),channel);
     #endif
   }
 
@@ -513,7 +514,7 @@ bool WiFiManager::startAP(){
     }
   } else {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("AP has anonymous access!"));    
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("AP has anonymous access!"));    
     #endif
     if(channel>0){
       ret = WiFi.softAP(_apName.c_str(),"",channel,_apHidden);
@@ -523,13 +524,13 @@ bool WiFiManager::startAP(){
     }  
   }
 
-  if(_debugLevel >= DEBUG_DEV) debugSoftAPConfig();
+  if(_debugLevel >= WM_DEBUG_DEV) debugSoftAPConfig();
 
   // @todo add softAP retry here to dela with unknown failures
   
   delay(500); // slight delay to make sure we get an AP IP
   #ifdef WM_DEBUG_LEVEL
-  if(!ret) DEBUG_WM(DEBUG_ERROR,F("[ERROR] There was a problem starting the AP"));
+  if(!ret) DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] There was a problem starting the AP"));
   DEBUG_WM(F("AP IP address:"),WiFi.softAPIP());
   #endif
 
@@ -538,9 +539,9 @@ bool WiFiManager::startAP(){
     if(ret && _hostname != ""){
       bool res =  WiFi.softAPsetHostname(_hostname.c_str());
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("setting softAP Hostname:"),_hostname);
-      if(!res)DEBUG_WM(DEBUG_ERROR,F("[ERROR] hostname: AP set failed!"));
-      DEBUG_WM(DEBUG_DEV,F("hostname: AP: "),WiFi.softAPgetHostname());
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("setting softAP Hostname:"),_hostname);
+      if(!res)DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] hostname: AP set failed!"));
+      DEBUG_WM(WM_DEBUG_DEV,F("hostname: AP: "),WiFi.softAPgetHostname());
       #endif
    }
   #endif
@@ -568,7 +569,7 @@ void WiFiManager::startWebPortal() {
 void WiFiManager::stopWebPortal() {
   if(!configPortalActive && !webPortalActive) return;
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("Stopping Web Portal"));  
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("Stopping Web Portal"));  
   #endif
   webPortalActive = false;
   shutdownConfigPortal();
@@ -584,7 +585,7 @@ boolean WiFiManager::configPortalHasTimeout(){
       if(millis() - timer > logintvl){
         timer = millis();
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("NUM CLIENTS: "),(String)WiFi_softap_num_stations());
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("NUM CLIENTS: "),(String)WiFi_softap_num_stations());
         #endif
       }
       _configPortalStart = millis(); // kludge, bump configportal start time to skew timeouts
@@ -606,7 +607,7 @@ boolean WiFiManager::configPortalHasTimeout(){
       if((millis() - timer) > logintvl){
         timer = millis();
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("Portal Timeout In"),(String)((_configPortalStart + _configPortalTimeout-millis())/1000) + (String)F(" seconds"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("Portal Timeout In"),(String)((_configPortalStart + _configPortalTimeout-millis())/1000) + (String)F(" seconds"));
         #endif
       }
     }
@@ -622,7 +623,7 @@ void WiFiManager::setupHTTPServer(){
 
   if(_httpPort != 80) {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("http server started with custom port: "),_httpPort); // @todo not showing ip
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("http server started with custom port: "),_httpPort); // @todo not showing ip
     #endif
   }
 
@@ -631,7 +632,7 @@ void WiFiManager::setupHTTPServer(){
 
   if ( _webservercallback != NULL) {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("[CB] _webservercallback calling"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] _webservercallback calling"));
     #endif
     _webservercallback(); // @CALLBACK
   }
@@ -659,7 +660,7 @@ void WiFiManager::setupHTTPServer(){
   
   server->begin(); // Web server start
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("HTTP server started"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("HTTP server started"));
   #endif
 }
 
@@ -670,7 +671,7 @@ void WiFiManager::setupDNSD(){
   dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
   #ifdef WM_DEBUG_LEVEL
   // DEBUG_WM("dns server started port: ",DNS_PORT);
-  DEBUG_WM(DEBUG_DEV,F("dns server started with ip: "),WiFi.softAPIP()); // @todo not showing ip
+  DEBUG_WM(WM_DEBUG_DEV,F("dns server started with ip: "),WiFi.softAPIP()); // @todo not showing ip
   #endif
   dnsServer->start(DNS_PORT, F("*"), WiFi.softAPIP());
 }
@@ -698,7 +699,7 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
 
   if(configPortalActive){
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Starting Config Portal FAILED, is already running"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Starting Config Portal FAILED, is already running"));
     #endif    
     return false;
   }
@@ -708,7 +709,7 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
   _apPassword = apPassword;
   
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("Starting Config Portal"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("Starting Config Portal"));
   #endif
 
   if(_apName == "") _apName = getDefaultAPName();
@@ -724,7 +725,7 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
     WiFi_Disconnect();
     WiFi_enableSTA(false);
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Disabling STA"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Disabling STA"));
     #endif
   }
   else {
@@ -740,7 +741,7 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
 
   // start access point
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("Enabling AP"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("Enabling AP"));
   #endif
   startAP();
   WiFiSetCountry();
@@ -748,27 +749,27 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
   // do AP callback if set
   if ( _apcallback != NULL) {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("[CB] _apcallback calling"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] _apcallback calling"));
     #endif
     _apcallback(this);
   }
 
   // init configportal
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("setupConfigPortal"));
+  DEBUG_WM(WM_DEBUG_DEV,F("setupConfigPortal"));
   #endif
   setupConfigPortal();
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("setupDNSD"));
+  DEBUG_WM(WM_DEBUG_DEV,F("setupDNSD"));
   #endif  
   setupDNSD();
   
 
   if(!_configPortalIsBlocking){
     #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("Config Portal Running, non blocking (processing)"));
-      if(_configPortalTimeout > 0) DEBUG_WM(DEBUG_VERBOSE,F("Portal Timeout In"),(String)(_configPortalTimeout/1000) + (String)F(" seconds"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("Config Portal Running, non blocking (processing)"));
+      if(_configPortalTimeout > 0) DEBUG_WM(WM_DEBUG_VERBOSE,F("Portal Timeout In"),(String)(_configPortalTimeout/1000) + (String)F(" seconds"));
     #endif
     return result; // skip blocking loop
   }
@@ -776,8 +777,8 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
   // enter blocking loop, waiting for config
   
   #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Config Portal Running, blocking, waiting for clients..."));
-    if(_configPortalTimeout > 0) DEBUG_WM(DEBUG_VERBOSE,F("Portal Timeout In"),(String)(_configPortalTimeout/1000) + (String)F(" seconds"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Config Portal Running, blocking, waiting for clients..."));
+    if(_configPortalTimeout > 0) DEBUG_WM(WM_DEBUG_VERBOSE,F("Portal Timeout In"),(String)(_configPortalTimeout/1000) + (String)F(" seconds"));
   #endif
 
   while(1){
@@ -785,13 +786,13 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
     // if timed out or abort, break
     if(configPortalHasTimeout() || abort){
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_DEV,F("configportal loop abort"));
+      DEBUG_WM(WM_DEBUG_DEV,F("configportal loop abort"));
       #endif
       shutdownConfigPortal();
       result = abort ? portalAbortResult : portalTimeoutResult; // false, false
       if (_configportaltimeoutcallback != NULL) {
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("[CB] config portal timeout callback"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] config portal timeout callback"));
         #endif
         _configportaltimeoutcallback();  // @CALLBACK
       }
@@ -805,7 +806,7 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
     // I think.. this is to detect autoconnect by esp in background, there are also many open issues about autoreconnect not working
     if(state != WL_IDLE_STATUS){
         result = (state == WL_CONNECTED); // true if connected
-        DEBUG_WM(DEBUG_DEV,F("configportal loop break"));
+        DEBUG_WM(WM_DEBUG_DEV,F("configportal loop break"));
         break;
     }
 
@@ -815,7 +816,7 @@ boolean  WiFiManager::startConfigPortal(char const *apName, char const *apPasswo
   }
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_NOTIFY,F("config portal exiting"));
+  DEBUG_WM(WM_DEBUG_NOTIFY,F("config portal exiting"));
   #endif
   return result;
 }
@@ -835,13 +836,13 @@ boolean WiFiManager::process(){
       // if timed out or abort, break
       if(_allowExit && (configPortalHasTimeout() || abort)){
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_DEV,F("process loop abort"));
+        DEBUG_WM(WM_DEBUG_DEV,F("process loop abort"));
         #endif
         webPortalActive = false;
         shutdownConfigPortal();
         if (_configportaltimeoutcallback != NULL) {
           #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_VERBOSE,F("[CB] config portal timeout callback"));
+          DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] config portal timeout callback"));
           #endif
           _configportaltimeoutcallback();  // @CALLBACK
         }
@@ -874,14 +875,14 @@ uint8_t WiFiManager::processConfigPortal(){
     if(connect) {
       connect = false;
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("processing save"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("processing save"));
       #endif
       if(_enableCaptivePortal) delay(_cpclosedelay); // keeps the captiveportal from closing to fast.
 
       // skip wifi if no ssid
       if(_ssid == ""){
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("No ssid, skipping wifi save"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("No ssid, skipping wifi save"));
         #endif
       }
       else{
@@ -900,7 +901,7 @@ uint8_t WiFiManager::processConfigPortal(){
 
           if ( _savewificallback != NULL) {
             #ifdef WM_DEBUG_LEVEL
-            DEBUG_WM(DEBUG_VERBOSE,F("[CB] _savewificallback calling"));
+            DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] _savewificallback calling"));
             #endif
             _savewificallback(); // @CALLBACK
           }
@@ -909,7 +910,7 @@ uint8_t WiFiManager::processConfigPortal(){
           return WL_CONNECTED; // CONNECT SUCCESS
         }
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_ERROR,F("[ERROR] Connect to new AP Failed"));
+        DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] Connect to new AP Failed"));
         #endif
       }
  
@@ -920,7 +921,7 @@ uint8_t WiFiManager::processConfigPortal(){
         // confirm or verify data was saved to make this more accurate callback
         if ( _savewificallback != NULL) {
           #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_VERBOSE,F("[CB] WiFi/Param save callback"));
+          DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] WiFi/Param save callback"));
           #endif
           _savewificallback(); // @CALLBACK
         }
@@ -935,12 +936,12 @@ uint8_t WiFiManager::processConfigPortal(){
         WiFi_Disconnect();
         WiFi_enableSTA(false);
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("Processing - Disabling STA"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("Processing - Disabling STA"));
         #endif
       }
       else{
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("Portal is non blocking - remaining open"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("Portal is non blocking - remaining open"));
         #endif        
       }
     }
@@ -955,7 +956,7 @@ uint8_t WiFiManager::processConfigPortal(){
  */
 bool WiFiManager::shutdownConfigPortal(){
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("shutdownConfigPortal"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("shutdownConfigPortal"));
   #endif
 
   if(webPortalActive) return false;
@@ -990,23 +991,23 @@ bool WiFiManager::shutdownConfigPortal(){
   ret = WiFi.softAPdisconnect(false);
   
   #ifdef WM_DEBUG_LEVEL
-  if(!ret)DEBUG_WM(DEBUG_ERROR,F("[ERROR] disconnect configportal - softAPdisconnect FAILED"));
-  DEBUG_WM(DEBUG_VERBOSE,F("restoring usermode"),getModeString(_usermode));
+  if(!ret)DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] disconnect configportal - softAPdisconnect FAILED"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("restoring usermode"),getModeString(_usermode));
   #endif
   delay(1000);
   WiFi_Mode(_usermode); // restore users wifi mode, BUG https://github.com/esp8266/Arduino/issues/4372
   if(WiFi.status()==WL_IDLE_STATUS){
     WiFi.reconnect(); // restart wifi since we disconnected it in startconfigportal
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("WiFi Reconnect, was idle"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Reconnect, was idle"));
     #endif
   }
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("wifi status:"),getWLStatusString(WiFi.status()));
-  DEBUG_WM(DEBUG_VERBOSE,F("wifi mode:"),getModeString(WiFi.getMode()));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("wifi status:"),getWLStatusString(WiFi.status()));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("wifi mode:"),getModeString(WiFi.getMode()));
   #endif
   configPortalActive = false;
-  DEBUG_WM(DEBUG_VERBOSE,F("configportal closed"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("configportal closed"));
   _end();
   return ret;
 }
@@ -1016,7 +1017,7 @@ bool WiFiManager::shutdownConfigPortal(){
 // clean up, flow is convoluted, and causes bugs
 uint8_t WiFiManager::connectWifi(String ssid, String pass, bool connect) {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("Connecting as wifi client..."));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("Connecting as wifi client..."));
   #endif
   uint8_t retry = 1;
   uint8_t connRes = (uint8_t)WL_NO_SSID_AVAIL;
@@ -1041,6 +1042,7 @@ uint8_t WiFiManager::connectWifi(String ssid, String pass, bool connect) {
       #endif
   }
   // if ssid argument provided connect to that
+  // NOTE: this also catches preload() _defaultssid @todo rework
   if (ssid != "") {
     wifiConnectNew(ssid,pass,connect);
     // @todo connect=false seems to disconnect sta in begin() so not sure if _connectonsave is useful at all
@@ -1050,7 +1052,7 @@ uint8_t WiFiManager::connectWifi(String ssid, String pass, bool connect) {
         connRes = waitForConnectResult(_saveTimeout); // use default save timeout for saves to prevent bugs in esp->waitforconnectresult loop
       }
       else {
-         connRes = waitForConnectResult(0);
+         connRes = waitForConnectResult();
       }
     // }
   }
@@ -1068,7 +1070,7 @@ uint8_t WiFiManager::connectWifi(String ssid, String pass, bool connect) {
   }
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("Connection result:"),getWLStatusString(connRes));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("Connection result:"),getWLStatusString(connRes));
   #endif
   retry++;
 }
@@ -1102,16 +1104,16 @@ uint8_t WiFiManager::connectWifi(String ssid, String pass, bool connect) {
 bool WiFiManager::wifiConnectNew(String ssid, String pass,bool connect){
   bool ret = false;
   #ifdef WM_DEBUG_LEVEL
-  // DEBUG_WM(DEBUG_DEV,F("CONNECTED: "),WiFi.status() == WL_CONNECTED ? "Y" : "NO");
+  // DEBUG_WM(WM_DEBUG_DEV,F("CONNECTED: "),WiFi.status() == WL_CONNECTED ? "Y" : "NO");
   DEBUG_WM(F("Connecting to NEW AP:"),ssid);
-  DEBUG_WM(DEBUG_DEV,F("Using Password:"),pass);
+  DEBUG_WM(WM_DEBUG_DEV,F("Using Password:"),pass);
   #endif
   WiFi_enableSTA(true,storeSTAmode); // storeSTAmode will also toggle STA on in default opmode (persistent) if true (default)
   WiFi.persistent(true);
   ret = WiFi.begin(ssid.c_str(), pass.c_str(), 0, NULL, connect);
   WiFi.persistent(false);
   #ifdef WM_DEBUG_LEVEL
-  if(!ret) DEBUG_WM(DEBUG_ERROR,F("[ERROR] wifi begin failed"));
+  if(!ret) DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] wifi begin failed"));
   #endif
   return ret;
 }
@@ -1126,21 +1128,21 @@ bool WiFiManager::wifiConnectDefault(){
 
   #ifdef WM_DEBUG_LEVEL
   DEBUG_WM(F("Connecting to SAVED AP:"),WiFi_SSID(true));
-  DEBUG_WM(DEBUG_DEV,F("Using Password:"),WiFi_psk(true));
+  DEBUG_WM(WM_DEBUG_DEV,F("Using Password:"),WiFi_psk(true));
   #endif
 
   ret = WiFi_enableSTA(true,storeSTAmode);
   delay(500); // THIS DELAY ?
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("Mode after delay: "),getModeString(WiFi.getMode()));
-  if(!ret) DEBUG_WM(DEBUG_ERROR,F("[ERROR] wifi enableSta failed"));
+  DEBUG_WM(WM_DEBUG_DEV,F("Mode after delay: "),getModeString(WiFi.getMode()));
+  if(!ret) DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] wifi enableSta failed"));
   #endif
 
   ret = WiFi.begin();
 
   #ifdef WM_DEBUG_LEVEL
-  if(!ret) DEBUG_WM(DEBUG_ERROR,F("[ERROR] wifi begin failed"));
+  if(!ret) DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] wifi begin failed"));
   #endif
 
   return ret;
@@ -1154,34 +1156,34 @@ bool WiFiManager::wifiConnectDefault(){
  */
 bool WiFiManager::setSTAConfig(){
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("STA static IP:"),_sta_static_ip);  
+  DEBUG_WM(WM_DEBUG_DEV,F("STA static IP:"),_sta_static_ip);  
   #endif
   bool ret = true;
   if (_sta_static_ip) {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("Custom static IP/GW/Subnet/DNS"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("Custom static IP/GW/Subnet/DNS"));
       #endif
     if(_sta_static_dns) {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("Custom static DNS"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("Custom static DNS"));
       #endif
       ret = WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn, _sta_static_dns);
     }
     else {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("Custom STA IP/GW/Subnet"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("Custom STA IP/GW/Subnet"));
       #endif
       ret = WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn);
     }
 
       #ifdef WM_DEBUG_LEVEL
-      if(!ret) DEBUG_WM(DEBUG_ERROR,F("[ERROR] wifi config failed"));
+      if(!ret) DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] wifi config failed"));
       else DEBUG_WM(F("STA IP set:"),WiFi.localIP());
       #endif
   } 
   else {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("setSTAConfig static ip not set, skipping"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("setSTAConfig static ip not set, skipping"));
       #endif
   }
   return ret;
@@ -1201,21 +1203,21 @@ void WiFiManager::updateConxResult(uint8_t status){
       // if(_lastconxresult == WL_CONNECT_FAILED){
       if(_lastconxresult == WL_CONNECT_FAILED || _lastconxresult == WL_DISCONNECTED){
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_DEV,F("lastconxresulttmp:"),getWLStatusString(_lastconxresulttmp));            
+        DEBUG_WM(WM_DEBUG_DEV,F("lastconxresulttmp:"),getWLStatusString(_lastconxresulttmp));            
         #endif
         if(_lastconxresulttmp != WL_IDLE_STATUS){
           _lastconxresult    = _lastconxresulttmp;
           // _lastconxresulttmp = WL_IDLE_STATUS;
         }
       }
-    DEBUG_WM(DEBUG_DEV,F("lastconxresult:"),getWLStatusString(_lastconxresult));
+    DEBUG_WM(WM_DEBUG_DEV,F("lastconxresult:"),getWLStatusString(_lastconxresult));
     #endif
 }
 
  
 uint8_t WiFiManager::waitForConnectResult() {
   #ifdef WM_DEBUG_LEVEL
-  if(_connectTimeout > 0) DEBUG_WM(DEBUG_DEV,_connectTimeout,F("ms connectTimeout set")); 
+  if(_connectTimeout > 0) DEBUG_WM(WM_DEBUG_DEV,_connectTimeout,F("ms connectTimeout set")); 
   #endif
   return waitForConnectResult(_connectTimeout);
 }
@@ -1235,7 +1237,7 @@ uint8_t WiFiManager::waitForConnectResult(uint32_t timeout) {
 
   unsigned long timeoutmillis = millis() + timeout;
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,timeout,F("ms timeout, waiting for connect..."));
+  DEBUG_WM(WM_DEBUG_VERBOSE,timeout,F("ms timeout, waiting for connect..."));
   #endif
   uint8_t status = WiFi.status();
   
@@ -1246,7 +1248,7 @@ uint8_t WiFiManager::waitForConnectResult(uint32_t timeout) {
       return status;
     }
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM (DEBUG_VERBOSE,F("."));
+    DEBUG_WM (WM_DEBUG_VERBOSE,F("."));
     #endif
     delay(100);
   }
@@ -1290,7 +1292,7 @@ String WiFiManager::getHTTPHead(String title){
   return page;
 }
 
-void WiFiManager::HTTPSend(String content){
+void WiFiManager::HTTPSend(const String &content){
   server->send(200, FPSTR(HTTP_HEAD_CT), content);
 }
 
@@ -1311,13 +1313,13 @@ void WiFiManager::handleRequest() {
   bool testauth = false;
   if(!testauth) return;
   
-  DEBUG_WM(DEBUG_DEV,F("DOING AUTH"));
+  DEBUG_WM(WM_DEBUG_DEV,F("DOING AUTH"));
   bool res = server->authenticate("admin","12345");
   if(!res){
     #ifndef WM_NOAUTH
     server->requestAuthentication(HTTPAuthMethod::BASIC_AUTH); // DIGEST_AUTH
     #endif
-    DEBUG_WM(DEBUG_DEV,F("AUTH FAIL"));
+    DEBUG_WM(WM_DEBUG_DEV,F("AUTH FAIL"));
   }
 }
 
@@ -1326,7 +1328,7 @@ void WiFiManager::handleRequest() {
  */
 void WiFiManager::handleRoot() {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP Root"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Root"));
   #endif
   if (captivePortal()) return; // If captive portal redirect instead of displaying the page
   handleRequest();
@@ -1352,13 +1354,13 @@ void WiFiManager::handleRoot() {
  */
 void WiFiManager::handleWifi(boolean scan) {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP Wifi"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Wifi"));
   #endif
   handleRequest();
   String page = getHTTPHead(FPSTR(S_titlewifi)); // @token titlewifi
   if (scan) {
     #ifdef WM_DEBUG_LEVEL
-    // DEBUG_WM(DEBUG_DEV,"refresh flag:",server->hasArg(F("refresh")));
+    // DEBUG_WM(WM_DEBUG_DEV,"refresh flag:",server->hasArg(F("refresh")));
     #endif
     WiFi_scanNetworks(server->hasArg(F("refresh")),false); //wifiscan, force if arg refresh
     page += getScanItemOut();
@@ -1399,7 +1401,7 @@ void WiFiManager::handleWifi(boolean scan) {
   HTTPSend(page);
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("Sent config page"));
+  DEBUG_WM(WM_DEBUG_DEV,F("Sent config page"));
   #endif
 }
 
@@ -1408,7 +1410,7 @@ void WiFiManager::handleWifi(boolean scan) {
  */
 void WiFiManager::handleParam(){
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP Param"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Param"));
   #endif
   handleRequest();
   String page = getHTTPHead(FPSTR(S_titleparam)); // @token titlewifi
@@ -1428,7 +1430,7 @@ void WiFiManager::handleParam(){
   HTTPSend(page);
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("Sent param page"));
+  DEBUG_WM(WM_DEBUG_DEV,F("Sent param page"));
   #endif
 }
 
@@ -1443,6 +1445,7 @@ String WiFiManager::getMenuOut(){
       continue;
     }
     page += HTTP_PORTAL_MENU[menuId];
+    delay(0);
   }
 
   return page;
@@ -1457,8 +1460,8 @@ void WiFiManager::WiFi_scanComplete(int networksFound){
   _lastscan = millis();
   _numNetworks = networksFound;
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("WiFi Scan ASYNC completed"), "in "+(String)(_lastscan - _startscan)+" ms");  
-  DEBUG_WM(DEBUG_VERBOSE,F("WiFi Scan ASYNC found:"),_numNetworks);
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC completed"), "in "+(String)(_lastscan - _startscan)+" ms");  
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC found:"),_numNetworks);
   #endif
 }
 
@@ -1474,15 +1477,15 @@ bool WiFiManager::WiFi_scanNetworks(unsigned int cachetime){
 }
 bool WiFiManager::WiFi_scanNetworks(bool force,bool async){
     #ifdef WM_DEBUG_LEVEL
-    // DEBUG_WM(DEBUG_DEV,"scanNetworks async:",async == true);
-    // DEBUG_WM(DEBUG_DEV,_numNetworks,(millis()-_lastscan ));
-    // DEBUG_WM(DEBUG_DEV,"scanNetworks force:",force == true);
+    // DEBUG_WM(WM_DEBUG_DEV,"scanNetworks async:",async == true);
+    // DEBUG_WM(WM_DEBUG_DEV,_numNetworks,(millis()-_lastscan ));
+    // DEBUG_WM(WM_DEBUG_DEV,"scanNetworks force:",force == true);
     #endif
 
     // if 0 networks, rescan @note this was a kludge, now disabling to test real cause ( maybe wifi not init etc)
     // enable only if preload failed? 
     if(_numNetworks == 0 && _autoforcerescan){
-      DEBUG_WM(DEBUG_DEV,"NO APs found forcing new scan");
+      DEBUG_WM(WM_DEBUG_DEV,"NO APs found forcing new scan");
       force = true;
     }
 
@@ -1498,38 +1501,38 @@ bool WiFiManager::WiFi_scanNetworks(bool force,bool async){
         #ifdef ESP8266
           #ifndef WM_NOASYNC // no async available < 2.4.0
           #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
+          DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
           #endif
           using namespace std::placeholders; // for `_1`
           WiFi.scanNetworksAsync(std::bind(&WiFiManager::WiFi_scanComplete,this,_1));
           #else
-          DEBUG_WM(DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
+          DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
           res = WiFi.scanNetworks();
           #endif
         #else
         #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
+          DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
           #endif
           res = WiFi.scanNetworks(true);
         #endif
         return false;
       }
       else{
-        DEBUG_WM(DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
         res = WiFi.scanNetworks();
       }
       if(res == WIFI_SCAN_FAILED){
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_ERROR,F("[ERROR] scan failed"));
+        DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan failed"));
         #endif
       }  
       else if(res == WIFI_SCAN_RUNNING){
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_ERROR,F("[ERROR] scan waiting"));
+        DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan waiting"));
         #endif
         while(WiFi.scanComplete() == WIFI_SCAN_RUNNING){
           #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_ERROR,".");
+          DEBUG_WM(WM_DEBUG_ERROR,".");
           #endif
           delay(100);
         }
@@ -1538,13 +1541,13 @@ bool WiFiManager::WiFi_scanNetworks(bool force,bool async){
       else if(res >=0 ) _numNetworks = res;
       _lastscan = millis();
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("WiFi Scan completed"), "in "+(String)(_lastscan - _startscan)+" ms");
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan completed"), "in "+(String)(_lastscan - _startscan)+" ms");
       #endif
       return true;
     }
     else {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("Scan is cached"),(String)(millis()-_lastscan )+" ms ago");
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("Scan is cached"),(String)(millis()-_lastscan )+" ms ago");
       #endif
     }
     return false;
@@ -1598,7 +1601,7 @@ String WiFiManager::WiFiManager::getScanItemOut(){
           for (int j = i + 1; j < n; j++) {
             if (cssid == WiFi.SSID(indices[j])) {
               #ifdef WM_DEBUG_LEVEL
-              DEBUG_WM(DEBUG_VERBOSE,F("DUP AP:"),WiFi.SSID(indices[j]));
+              DEBUG_WM(WM_DEBUG_VERBOSE,F("DUP AP:"),WiFi.SSID(indices[j]));
               #endif
               indices[j] = -1; // set dup aps to index -1
             }
@@ -1627,7 +1630,7 @@ String WiFiManager::WiFiManager::getScanItemOut(){
         if (indices[i] == -1) continue; // skip dups
 
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("AP: "),(String)WiFi.RSSI(indices[i]) + " " + (String)WiFi.SSID(indices[i]));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("AP: "),(String)WiFi.RSSI(indices[i]) + " " + (String)WiFi.SSID(indices[i]));
         #endif
 
         int rssiperc = getRSSIasQuality(WiFi.RSSI(indices[i]));
@@ -1653,13 +1656,13 @@ String WiFiManager::WiFiManager::getScanItemOut(){
             }
           }
           #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_DEV,item);
+          DEBUG_WM(WM_DEBUG_DEV,item);
           #endif
           page += item;
           delay(0);
         } else {
           #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_VERBOSE,F("Skipping , does not meet _minimumQuality"));
+          DEBUG_WM(WM_DEBUG_VERBOSE,F("Skipping , does not meet _minimumQuality"));
           #endif
         }
 
@@ -1688,7 +1691,7 @@ String WiFiManager::getStaticOut(){
   String page;
   if ((_staShowStaticFields || _sta_static_ip) && _staShowStaticFields>=0) {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("_staShowStaticFields"));
+    DEBUG_WM(WM_DEBUG_DEV,F("_staShowStaticFields"));
     #endif
     page += FPSTR(HTTP_FORM_STATIC_HEAD);
     // @todo how can we get these accurate settings from memory , wifi_get_ip_info does not seem to reveal if struct ip_info is static or not
@@ -1713,7 +1716,7 @@ String WiFiManager::getParamOut(){
   String page;
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("getParamOut"),_paramsCount);
+  DEBUG_WM(WM_DEBUG_DEV,F("getParamOut"),_paramsCount);
   #endif
 
   if(_paramsCount > 0){
@@ -1733,10 +1736,10 @@ String WiFiManager::getParamOut(){
 
     for (int i = 0; i < _paramsCount; i++) {
       //Serial.println((String)_params[i]->_length);
-      if (_params[i] == NULL || _params[i]->_length == 0 || _params[i]->_length > 99999) {
+      if (_params[i] == NULL || _params[i]->_length > 99999) {
         // try to detect param scope issues, doesnt always catch but works ok
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope"));
+        DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope"));
         #endif
         return "";
       }
@@ -1787,7 +1790,7 @@ String WiFiManager::getParamOut(){
 
 void WiFiManager::handleWiFiStatus(){
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP WiFi status "));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP WiFi status "));
   #endif
   handleRequest();
   String page;
@@ -1803,8 +1806,8 @@ void WiFiManager::handleWiFiStatus(){
  */
 void WiFiManager::handleWifiSave() {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP WiFi save "));
-  DEBUG_WM(DEBUG_DEV,F("Method:"),server->method() == HTTP_GET  ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP WiFi save "));
+  DEBUG_WM(WM_DEBUG_DEV,F("Method:"),server->method() == HTTP_GET  ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST));
   #endif
   handleRequest();
 
@@ -1812,34 +1815,57 @@ void WiFiManager::handleWifiSave() {
   _ssid = server->arg(F("s")).c_str();
   _pass = server->arg(F("p")).c_str();
 
+  if(_ssid == "" && _pass != ""){
+    _ssid = WiFi_SSID(true); // password change, placeholder ssid, @todo compare pass to old?, confirm ssid is clean
+    #ifdef WM_DEBUG_LEVEL
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Detected WiFi password change"));
+    #endif    
+  }
+
+  #ifdef WM_DEBUG_LEVEL
+  String requestinfo = "SERVER_REQUEST\n----------------\n";
+  requestinfo += "URI: ";
+  requestinfo += server->uri();
+  requestinfo += "\nMethod: ";
+  requestinfo += (server->method() == HTTP_GET) ? "GET" : "POST";
+  requestinfo += "\nArguments: ";
+  requestinfo += server->args();
+  requestinfo += "\n";
+  for (uint8_t i = 0; i < server->args(); i++) {
+    requestinfo += " " + server->argName(i) + ": " + server->arg(i) + "\n";
+  }
+
+  DEBUG_WM(WM_DEBUG_MAX,requestinfo);
+  #endif
+
   // set static ips from server args
   if (server->arg(FPSTR(S_ip)) != "") {
     //_sta_static_ip.fromString(server->arg(FPSTR(S_ip));
     String ip = server->arg(FPSTR(S_ip));
     optionalIPFromString(&_sta_static_ip, ip.c_str());
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("static ip:"),ip);
+    DEBUG_WM(WM_DEBUG_DEV,F("static ip:"),ip);
     #endif
   }
   if (server->arg(FPSTR(S_gw)) != "") {
     String gw = server->arg(FPSTR(S_gw));
     optionalIPFromString(&_sta_static_gw, gw.c_str());
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("static gateway:"),gw);
+    DEBUG_WM(WM_DEBUG_DEV,F("static gateway:"),gw);
     #endif
   }
   if (server->arg(FPSTR(S_sn)) != "") {
     String sn = server->arg(FPSTR(S_sn));
     optionalIPFromString(&_sta_static_sn, sn.c_str());
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("static netmask:"),sn);
+    DEBUG_WM(WM_DEBUG_DEV,F("static netmask:"),sn);
     #endif
   }
   if (server->arg(FPSTR(S_dns)) != "") {
     String dns = server->arg(FPSTR(S_dns));
     optionalIPFromString(&_sta_static_dns, dns.c_str());
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("static DNS:"),dns);
+    DEBUG_WM(WM_DEBUG_DEV,F("static DNS:"),dns);
     #endif
   }
 
@@ -1867,7 +1893,7 @@ void WiFiManager::handleWifiSave() {
   HTTPSend(page);
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("Sent wifi save page"));
+  DEBUG_WM(WM_DEBUG_DEV,F("Sent wifi save page"));
   #endif
 
   connect = true; //signal ready to connect/reset process in processConfigPortal
@@ -1876,10 +1902,10 @@ void WiFiManager::handleWifiSave() {
 void WiFiManager::handleParamSave() {
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP Param save "));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Param save "));
   #endif
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("Method:"),server->method() == HTTP_GET  ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST));
+  DEBUG_WM(WM_DEBUG_DEV,F("Method:"),server->method() == HTTP_GET  ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST));
   #endif
   handleRequest();
 
@@ -1893,7 +1919,7 @@ void WiFiManager::handleParamSave() {
   HTTPSend(page);
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("Sent param save page"));
+  DEBUG_WM(WM_DEBUG_DEV,F("Sent param save page"));
   #endif
 }
 
@@ -1906,14 +1932,14 @@ void WiFiManager::doParamSave(){
   //parameters
   if(_paramsCount > 0){
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Parameters"));
-    DEBUG_WM(DEBUG_VERBOSE,FPSTR(D_HR));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Parameters"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,FPSTR(D_HR));
     #endif
 
     for (int i = 0; i < _paramsCount; i++) {
-      if (_params[i] == NULL || _params[i]->_length == 0) {
+      if (_params[i] == NULL || _params[i]->_length > 99999) {
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope"));
+        DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope"));
         #endif
         break; // @todo might not be needed anymore
       }
@@ -1929,11 +1955,11 @@ void WiFiManager::doParamSave(){
       //store it in params array
       value.toCharArray(_params[i]->_value, _params[i]->_length+1); // length+1 null terminated
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,(String)_params[i]->getID() + ":",value);
+      DEBUG_WM(WM_DEBUG_VERBOSE,(String)_params[i]->getID() + ":",value);
       #endif
     }
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,FPSTR(D_HR));
+    DEBUG_WM(WM_DEBUG_VERBOSE,FPSTR(D_HR));
     #endif
   }
 
@@ -1948,7 +1974,7 @@ void WiFiManager::doParamSave(){
  */
 void WiFiManager::handleInfo() {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP Info"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Info"));
   #endif
   handleRequest();
   String page = getHTTPHead(FPSTR(S_titleinfo)); // @token titleinfo
@@ -2007,6 +2033,7 @@ void WiFiManager::handleInfo() {
       F("memsmeter"),      
       F("lastreset"),
       F("temp"),
+      // F("hall"),
       F("wifihead"),
       F("conx"),
       F("stassid"),
@@ -2049,7 +2076,7 @@ void WiFiManager::handleInfo() {
   HTTPSend(page);
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_DEV,F("Sent info page"));
+  DEBUG_WM(WM_DEBUG_DEV,F("Sent info page"));
   #endif
 }
 
@@ -2248,10 +2275,12 @@ String WiFiManager::getInfoData(String id){
     // temperature is not calibrated, varying large offsets are present, use for relative temp changes only
     p = FPSTR(HTTP_INFO_temp);
     p.replace(FPSTR(T_1),(String)temperatureRead());
-    p.replace(FPSTR(T_2),(String)((temperatureRead()+32)*1.8));
-    // p.replace(FPSTR(T_3),(String)hallRead());
-    // p.replace(FPSTR(T_3),"NA"); // removed hall sensor as reads can cause issues with adcs
+    p.replace(FPSTR(T_2),(String)((temperatureRead()+32)*1.8f));
   }
+  // else if(id==F("hall")){ 
+  //   p = FPSTR(HTTP_INFO_hall);
+  //   p.replace(FPSTR(T_1),(String)hallRead()); // hall sensor reads can cause issues with adcs
+  // }
   #endif
   else if(id==F("aboutver")){
     p = FPSTR(HTTP_INFO_aboutver);
@@ -2290,7 +2319,7 @@ String WiFiManager::getInfoData(String id){
  */
 void WiFiManager::handleExit() {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP Exit"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Exit"));
   #endif
   handleRequest();
   String page = getHTTPHead(FPSTR(S_titleexit)); // @token titleexit
@@ -2307,7 +2336,7 @@ void WiFiManager::handleExit() {
  */
 void WiFiManager::handleReset() {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP Reset"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Reset"));
   #endif
   handleRequest();
   String page = getHTTPHead(FPSTR(S_titlereset)); //@token titlereset
@@ -2332,7 +2361,7 @@ void WiFiManager::handleReset() {
 // }
 void WiFiManager::handleErase(boolean opt) {
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_NOTIFY,F("<- HTTP Erase"));
+  DEBUG_WM(WM_DEBUG_NOTIFY,F("<- HTTP Erase"));
   #endif
   handleRequest();
   String page = getHTTPHead(FPSTR(S_titleerase)); // @token titleerase
@@ -2343,7 +2372,7 @@ void WiFiManager::handleErase(boolean opt) {
   else {
     page += FPSTR(S_error); // @token erroroccur
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_ERROR,F("[ERROR] WiFi EraseConfig failed"));
+    DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] WiFi EraseConfig failed"));
     #endif
   }
 
@@ -2366,16 +2395,20 @@ void WiFiManager::handleNotFound() {
   if (captivePortal()) return; // If captive portal redirect instead of displaying the page
   handleRequest();
   String message = FPSTR(S_notfound); // @token notfound
-  message += FPSTR(S_uri); // @token uri
-  message += server->uri();
-  message += FPSTR(S_method); // @token method
-  message += ( server->method() == HTTP_GET ) ? FPSTR(S_GET) : FPSTR(S_POST);
-  message += FPSTR(S_args); // @token args
-  message += server->args();
-  message += F("\n");
 
-  for ( uint8_t i = 0; i < server->args(); i++ ) {
-    message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n";
+  bool verbose404 = false; // show info in 404 body, uri,method, args
+  if(verbose404){
+    message += FPSTR(S_uri); // @token uri
+    message += server->uri();
+    message += FPSTR(S_method); // @token method
+    message += ( server->method() == HTTP_GET ) ? FPSTR(S_GET) : FPSTR(S_POST);
+    message += FPSTR(S_args); // @token args
+    message += server->args();
+    message += F("\n");
+
+    for ( uint8_t i = 0; i < server->args(); i++ ) {
+      message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n";
+    }
   }
   server->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); // @HTTPHEAD send cache
   server->sendHeader(F("Pragma"), F("no-cache"));
@@ -2389,20 +2422,31 @@ void WiFiManager::handleNotFound() {
  * Return true in that case so the page handler do not try to handle the request again. 
  */
 boolean WiFiManager::captivePortal() {
-  #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_MAX,"-> " + server->hostHeader());
-  #endif
   
-  if(!_enableCaptivePortal) return false; // skip redirections, @todo maybe allow redirection even when no cp ? might be useful
+  if(!_enableCaptivePortal || !configPortalActive) return false; // skip redirections if cp not enabled or not in ap mode
   
   String serverLoc =  toStringIp(server->client().localIP());
+
+  #ifdef WM_DEBUG_LEVEL
+  DEBUG_WM(WM_DEBUG_DEV,"-> " + server->hostHeader());
+  DEBUG_WM(WM_DEBUG_DEV,"serverLoc " + serverLoc);
+  #endif
+
+  // fallback for ipv6 bug
+  if(serverLoc == "0.0.0.0"){
+    if ((WiFi.status()) != WL_CONNECTED)
+      serverLoc = toStringIp(WiFi.softAPIP());
+    else
+      serverLoc = toStringIp(WiFi.localIP());
+  }
+  
   if(_httpPort != 80) serverLoc += ":" + (String)_httpPort; // add port if not default
   bool doredirect = serverLoc != server->hostHeader(); // redirect if hostheader not server ip, prevent redirect loops
-  // doredirect = !isIp(server->hostHeader()) // old check
   
   if (doredirect) {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("<- Request redirected to captive portal"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("<- Request redirected to captive portal"));
+    DEBUG_WM(WM_DEBUG_DEV,"serverLoc " + serverLoc);
     #endif
     server->sendHeader(F("Location"), (String)F("http://") + serverLoc, true); // @HTTPHEAD send redirect
     server->send ( 302, FPSTR(HTTP_HEAD_CT2), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
@@ -2419,10 +2463,10 @@ void WiFiManager::stopCaptivePortal(){
 
 // HTTPD CALLBACK, handle close,  stop captive portal, if not enabled undefined
 void WiFiManager::handleClose(){
-  DEBUG_WM(DEBUG_VERBOSE,F("Disabling Captive Portal"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("Disabling Captive Portal"));
   stopCaptivePortal();
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("<- HTTP close"));
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP close"));
   #endif
   handleRequest();
   String page = getHTTPHead(FPSTR(S_titleclose)); // @token titleclose
@@ -2432,8 +2476,8 @@ void WiFiManager::handleClose(){
 
 void WiFiManager::reportStatus(String &page){
   // updateConxResult(WiFi.status()); // @todo: this defeats the purpose of last result, update elsewhere or add logic here
-  DEBUG_WM(DEBUG_DEV,F("[WIFI] reportStatus prev:"),getWLStatusString(_lastconxresult));
-  DEBUG_WM(DEBUG_DEV,F("[WIFI] reportStatus current:"),getWLStatusString(WiFi.status()));
+  DEBUG_WM(WM_DEBUG_DEV,F("[WIFI] reportStatus prev:"),getWLStatusString(_lastconxresult));
+  DEBUG_WM(WM_DEBUG_DEV,F("[WIFI] reportStatus current:"),getWLStatusString(WiFi.status()));
   String str;
   if (WiFi_SSID() != ""){
     if (WiFi.status()==WL_CONNECTED){
@@ -2459,6 +2503,11 @@ void WiFiManager::reportStatus(String &page){
         str.replace(FPSTR(T_c),"D");
         str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFFAIL));
       }
+      else if(_lastconxresult == WL_CONNECTION_LOST){
+        // connect failed, MOST likely 4WAY_HANDSHAKE_TIMEOUT/incorrect password, state is ambiguous however
+        str.replace(FPSTR(T_c),"D");
+        str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFFAIL));
+      }
       else{
         str.replace(FPSTR(T_c),"");
         str.replace(FPSTR(T_r),"");
@@ -2500,7 +2549,7 @@ bool WiFiManager::stopConfigPortal(){
 bool WiFiManager::disconnect(){
   if(WiFi.status() != WL_CONNECTED){
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("Disconnecting: Not connected"));
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("Disconnecting: Not connected"));
     #endif
     return false;
   }  
@@ -2543,11 +2592,11 @@ bool WiFiManager::erase(bool opt){
       esp_err_t err;
       err = nvs_flash_init();
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("nvs_flash_init: "),err!=ESP_OK ? (String)err : "Success");
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("nvs_flash_init: "),err!=ESP_OK ? (String)err : "Success");
       #endif
       err = nvs_flash_erase();
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("nvs_flash_erase: "), err!=ESP_OK ? (String)err : "Success");
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("nvs_flash_erase: "), err!=ESP_OK ? (String)err : "Success");
       #endif
       return err == ESP_OK;
     }
@@ -2560,7 +2609,7 @@ bool WiFiManager::erase(bool opt){
         #endif
         bool ret = SPIFFS.format();
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("spiffs erase: "),ret ? "Success" : "ERROR");
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("spiffs erase: "),ret ? "Success" : "ERROR");
         #endif
       } else{
       #ifdef WM_DEBUG_LEVEL
@@ -2678,7 +2727,8 @@ void WiFiManager::setSaveConnect(bool connect) {
  */
 void WiFiManager::setDebugOutput(boolean debug) {
   _debug = debug;
-  if(_debug && _debugLevel == DEBUG_DEV) debugPlatformInfo();
+  if(_debug && _debugLevel == WM_DEBUG_DEV) debugPlatformInfo();
+  if(_debug && _debugLevel >= WM_DEBUG_NOTIFY)DEBUG_WM((__FlashStringHelper *)WM_VERSION_STR," D:"+String(_debugLevel));
 }
 
 void WiFiManager::setDebugOutput(boolean debug, String prefix) {
@@ -2686,6 +2736,13 @@ void WiFiManager::setDebugOutput(boolean debug, String prefix) {
   setDebugOutput(debug);
 }
 
+void WiFiManager::setDebugOutput(boolean debug, wm_debuglevel_t level) {
+  _debugLevel = level;
+  // _debugPrefix = prefix;
+  setDebugOutput(debug);
+}
+
+
 /**
  * [setAPStaticIPConfig description]
  * @access public
@@ -3100,16 +3157,18 @@ void WiFiManager::setTitle(String title){
  */
 void WiFiManager::setMenu(const char * menu[], uint8_t size){
 #ifdef WM_DEBUG_LEVEL
-  // DEBUG_WM(DEBUG_DEV,"setmenu array");
+  // DEBUG_WM(WM_DEBUG_DEV,"setmenu array");
   #endif
   _menuIds.clear();
   for(size_t i = 0; i < size; i++){
     for(size_t j = 0; j < _nummenutokens; j++){
-      if(menu[i] == _menutokens[j]){
+      if((String)menu[i] == (__FlashStringHelper *)(_menutokens[j])){
         if((String)menu[i] == "param") _paramsInWifi = false; // param auto flag
         _menuIds.push_back(j);
       }
+      delay(0);
     }
+    delay(0);
   }
   #ifdef WM_DEBUG_LEVEL
   // DEBUG_WM(getMenuOut());
@@ -3127,19 +3186,19 @@ void WiFiManager::setMenu(const char * menu[], uint8_t size){
  */
 void WiFiManager::setMenu(std::vector<const char *>& menu){
 #ifdef WM_DEBUG_LEVEL
-  // DEBUG_WM(DEBUG_DEV,"setmenu vector");
+  // DEBUG_WM(WM_DEBUG_DEV,"setmenu vector");
   #endif
   _menuIds.clear();
   for(auto menuitem : menu ){
     for(size_t j = 0; j < _nummenutokens; j++){
-      if(menuitem == _menutokens[j]){
+      if((String)menuitem == (__FlashStringHelper *)(_menutokens[j])){
         if((String)menuitem == "param") _paramsInWifi = false; // param auto flag
         _menuIds.push_back(j);
       }
     }
   }
   #ifdef WM_DEBUG_LEVEL
-  // DEBUG_WM(DEBUG_DEV,getMenuOut());
+  // DEBUG_WM(WM_DEBUG_DEV,getMenuOut());
   #endif
 }
 
@@ -3269,7 +3328,7 @@ String WiFiManager::getWiFiPass(bool persistent){
 // @todo fix DEBUG_WM(0,0);
 template <typename Generic>
 void WiFiManager::DEBUG_WM(Generic text) {
-  DEBUG_WM(DEBUG_NOTIFY,text,"");
+  DEBUG_WM(WM_DEBUG_NOTIFY,text,"");
 }
 
 template <typename Generic>
@@ -3279,14 +3338,14 @@ void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text) {
 
 template <typename Generic, typename Genericb>
 void WiFiManager::DEBUG_WM(Generic text,Genericb textb) {
-  DEBUG_WM(DEBUG_NOTIFY,text,textb);
+  DEBUG_WM(WM_DEBUG_NOTIFY,text,textb);
 }
 
 template <typename Generic, typename Genericb>
 void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text,Genericb textb) {
   if(!_debug || _debugLevel < level) return;
 
-  if(_debugLevel >= DEBUG_MAX){
+  if(_debugLevel >= WM_DEBUG_MAX){
     #ifdef ESP8266
     // uint32_t free;
     // uint16_t max;
@@ -3309,6 +3368,7 @@ void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text,Genericb textb) {
     _debugPort.printf("[MEM] free: %5d | max: %5d | frag: %3d%% \n", free, max, frag);    
     #endif
   }
+
   _debugPort.print(_debugPrefix);
   if(_debugLevel >= debugLvlShow) _debugPort.print("["+(String)level+"] ");
   _debugPort.print(text);
@@ -3377,7 +3437,7 @@ void WiFiManager::debugPlatformInfo(){
     #endif
   #elif defined(ESP32)
   #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(F("[SYS] WM version: "),      WM_VERSION_STR);
+    DEBUG_WM(F("[SYS] WM version: "), String((__FlashStringHelper *)WM_VERSION_STR) +" D:"+String(_debugLevel));
     DEBUG_WM(F("[SYS] Arduino version: "), VER_ARDUINO_STR);
     DEBUG_WM(F("[SYS] ESP SDK version: "), ESP.getSdkVersion());
     DEBUG_WM(F("[SYS] Free heap:       "), ESP.getFreeHeap());
@@ -3438,8 +3498,8 @@ boolean WiFiManager::validApPassword(){
       return false; // @todo FATAL or fallback to empty , currently fatal, fail secure.
     }
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,F("AccessPoint set password is VALID"));
-    DEBUG_WM(DEBUG_DEV,"ap pass",_apPassword);
+    DEBUG_WM(WM_DEBUG_VERBOSE,F("AccessPoint set password is VALID"));
+    DEBUG_WM(WM_DEBUG_DEV,"ap pass",_apPassword);
     #endif
   }
   return true;
@@ -3498,7 +3558,7 @@ bool WiFiManager::WiFiSetCountry(){
   if(_wificountry == "") return false; // skip not set
 
   #ifdef WM_DEBUG_LEVEL
-  DEBUG_WM(DEBUG_VERBOSE,F("WiFiSetCountry to"),_wificountry);
+  DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFiSetCountry to"),_wificountry);
   #endif
 
 /*
@@ -3517,7 +3577,7 @@ bool WiFiManager::WiFiSetCountry(){
   esp_err_t err = ESP_OK;
   // @todo check if wifi is init, no idea how, doesnt seem to be exposed atm ( check again it might be now! )
   if(WiFi.getMode() == WIFI_MODE_NULL){
-      DEBUG_WM(DEBUG_ERROR,"[ERROR] cannot set country, wifi not init");        
+      DEBUG_WM(WM_DEBUG_ERROR,"[ERROR] cannot set country, wifi not init");        
   } // exception if wifi not init!
   // Assumes that _wificountry is set to one of the supported country codes : "01"(world safe mode) "AT","AU","BE","BG","BR",
   //               "CA","CH","CN","CY","CZ","DE","DK","EE","ES","FI","FR","GB","GR","HK","HR","HU",
@@ -3530,15 +3590,15 @@ bool WiFiManager::WiFiSetCountry(){
     #ifndef WM_NOCOUNTRY
     err = esp_wifi_set_country_code(_wificountry.c_str(), true);
     #else
-    DEBUG_WM(DEBUG_ERROR,"[ERROR] esp wifi set country is not available");
+    DEBUG_WM(WM_DEBUG_ERROR,"[ERROR] esp wifi set country is not available");
     err = true;
     #endif
   }
   #ifdef WM_DEBUG_LEVEL
     if(err){
-      if(err == ESP_ERR_WIFI_NOT_INIT) DEBUG_WM(DEBUG_ERROR,"[ERROR] ESP_ERR_WIFI_NOT_INIT");
-      else if(err == ESP_ERR_INVALID_ARG) DEBUG_WM(DEBUG_ERROR,"[ERROR] ESP_ERR_WIFI_ARG (invalid country code)");
-      else if(err != ESP_OK)DEBUG_WM(DEBUG_ERROR,"[ERROR] unknown error",(String)err);
+      if(err == ESP_ERR_WIFI_NOT_INIT) DEBUG_WM(WM_DEBUG_ERROR,"[ERROR] ESP_ERR_WIFI_NOT_INIT");
+      else if(err == ESP_ERR_INVALID_ARG) DEBUG_WM(WM_DEBUG_ERROR,"[ERROR] ESP_ERR_WIFI_ARG (invalid country code)");
+      else if(err != ESP_OK)DEBUG_WM(WM_DEBUG_ERROR,"[ERROR] unknown error",(String)err);
     }
   #endif
   ret = err == ESP_OK;
@@ -3549,13 +3609,13 @@ bool WiFiManager::WiFiSetCountry(){
   else if(_wificountry == "JP") ret = wifi_set_country((wifi_country_t*)&WM_COUNTRY_JP);
   else if(_wificountry == "CN") ret = wifi_set_country((wifi_country_t*)&WM_COUNTRY_CN);
   #ifdef WM_DEBUG_LEVEL
-  else DEBUG_WM(DEBUG_ERROR,F("[ERROR] country code not found"));
+  else DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] country code not found"));
   #endif
   #endif
   
   #ifdef WM_DEBUG_LEVEL
-  if(ret) DEBUG_WM(DEBUG_VERBOSE,F("[OK] esp_wifi_set_country: "),_wificountry);
-  else DEBUG_WM(DEBUG_ERROR,F("[ERROR] esp_wifi_set_country failed"));  
+  if(ret) DEBUG_WM(WM_DEBUG_VERBOSE,F("[OK] esp_wifi_set_country: "),_wificountry);
+  else DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] esp_wifi_set_country failed"));  
   #endif
   return ret;
 }
@@ -3589,7 +3649,7 @@ bool WiFiManager::WiFi_Disconnect() {
       if((WiFi.getMode() & WIFI_STA) != 0) {
           bool ret;
           #ifdef WM_DEBUG_LEVEL
-          DEBUG_WM(DEBUG_DEV,F("WiFi station disconnect"));
+          DEBUG_WM(WM_DEBUG_DEV,F("WiFi station disconnect"));
           #endif
           ETS_UART_INTR_DISABLE(); // @todo possibly not needed
           ret = wifi_station_disconnect();
@@ -3598,7 +3658,7 @@ bool WiFiManager::WiFi_Disconnect() {
       }
     #elif defined(ESP32)
     #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_DEV,F("WiFi station disconnect"));
+      DEBUG_WM(WM_DEBUG_DEV,F("WiFi station disconnect"));
       #endif
       return WiFi.disconnect(); // not persistent atm
     #endif
@@ -3608,7 +3668,7 @@ bool WiFiManager::WiFi_Disconnect() {
 // toggle STA without persistent
 bool WiFiManager::WiFi_enableSTA(bool enable,bool persistent) {
 #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("WiFi_enableSTA"),(String) enable? "enable" : "disable");
+    DEBUG_WM(WM_DEBUG_DEV,F("WiFi_enableSTA"),(String) enable? "enable" : "disable");
     #endif
     #ifdef ESP8266
       WiFiMode_t newMode;
@@ -3620,7 +3680,7 @@ bool WiFiManager::WiFi_enableSTA(bool enable,bool persistent) {
       if((isEnabled != enable) || persistent) {
           if(enable) {
           #ifdef WM_DEBUG_LEVEL
-          	if(persistent) DEBUG_WM(DEBUG_DEV,F("enableSTA PERSISTENT ON"));
+          	if(persistent) DEBUG_WM(WM_DEBUG_DEV,F("enableSTA PERSISTENT ON"));
             #endif
               return WiFi_Mode(newMode,persistent);
           }
@@ -3645,7 +3705,7 @@ bool WiFiManager::WiFi_enableSTA(bool enable) {
 
 bool WiFiManager::WiFi_eraseConfig() {
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_DEV,F("WiFi_eraseConfig"));
+    DEBUG_WM(WM_DEBUG_DEV,F("WiFi_eraseConfig"));
     #endif
 
     #ifdef ESP8266
@@ -3701,6 +3761,8 @@ String WiFiManager::WiFi_SSID(bool persistent) const{
     return String(reinterpret_cast<char*>(tmp));
     
     #elif defined(ESP32)
+    // bool res = WiFi.wifiLowLevelInit(true); // @todo fix for S3, not found
+    // wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
     if(persistent){
       wifi_config_t conf;
       esp_wifi_get_config(WIFI_IF_STA, &conf);
@@ -3753,7 +3815,7 @@ String WiFiManager::WiFi_psk(bool persistent) const {
   #endif
     if(!_hasBegun){
       #ifdef WM_DEBUG_LEVEL
-        // DEBUG_WM(DEBUG_VERBOSE,"[ERROR] WiFiEvent, not ready");
+        // DEBUG_WM(WM_DEBUG_VERBOSE,"[ERROR] WiFiEvent, not ready");
       #endif
       // Serial.println(F("\n[EVENT] WiFiEvent logging (wm debug not available)"));
       // Serial.print(F("[EVENT] ID: "));
@@ -3761,25 +3823,25 @@ String WiFiManager::WiFi_psk(bool persistent) const {
       return;
     }
     #ifdef WM_DEBUG_LEVEL
-    // DEBUG_WM(DEBUG_VERBOSE,"[EVENT]",event);
+    // DEBUG_WM(WM_DEBUG_VERBOSE,"[EVENT]",event);
     #endif
     if(event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED){
     #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("[EVENT] WIFI_REASON: "),info.wifi_sta_disconnected.reason);
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("[EVENT] WIFI_REASON: "),info.wifi_sta_disconnected.reason);
       #endif
       if(info.wifi_sta_disconnected.reason == WIFI_REASON_AUTH_EXPIRE || info.wifi_sta_disconnected.reason == WIFI_REASON_AUTH_FAIL){
         _lastconxresulttmp = 7; // hack in wrong password internally, sdk emit WIFI_REASON_AUTH_EXPIRE on some routers on auth_fail
       } else _lastconxresulttmp = WiFi.status();
       #ifdef WM_DEBUG_LEVEL
-      if(info.wifi_sta_disconnected.reason == WIFI_REASON_NO_AP_FOUND) DEBUG_WM(DEBUG_VERBOSE,F("[EVENT] WIFI_REASON: NO_AP_FOUND"));
+      if(info.wifi_sta_disconnected.reason == WIFI_REASON_NO_AP_FOUND) DEBUG_WM(WM_DEBUG_VERBOSE,F("[EVENT] WIFI_REASON: NO_AP_FOUND"));
       if(info.wifi_sta_disconnected.reason == WIFI_REASON_ASSOC_FAIL){
-        if(_aggresiveReconn) _connectRetries+=4;
-        DEBUG_WM(DEBUG_VERBOSE,F("[EVENT] WIFI_REASON: AUTH FAIL"));
+        if(_aggresiveReconn && _connectRetries<4) _connectRetries=4;
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("[EVENT] WIFI_REASON: AUTH FAIL"));
       }  
       #endif
       #ifdef esp32autoreconnect
       #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_VERBOSE,F("[Event] SYSTEM_EVENT_STA_DISCONNECTED, reconnecting"));
+        DEBUG_WM(WM_DEBUG_VERBOSE,F("[Event] SYSTEM_EVENT_STA_DISCONNECTED, reconnecting"));
         #endif
         WiFi.reconnect();
       #endif
@@ -3798,7 +3860,7 @@ void WiFiManager::WiFi_autoReconnect(){
     // if(_wifiAutoReconnect){
       // @todo move to seperate method, used for event listener now
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("ESP32 event handler enabled"));
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("ESP32 event handler enabled"));
       #endif
       using namespace std::placeholders;
       if(wm_event_id == 0) wm_event_id = WiFi.onEvent(std::bind(&WiFiManager::WiFiEvent,this,_1,_2));
@@ -3809,7 +3871,7 @@ void WiFiManager::WiFi_autoReconnect(){
 // Called when /update is requested
 void WiFiManager::handleUpdate() {
   #ifdef WM_DEBUG_LEVEL
-	DEBUG_WM(DEBUG_VERBOSE,F("<- Handle update"));
+	DEBUG_WM(WM_DEBUG_VERBOSE,F("<- Handle update"));
   #endif
 	if (captivePortal()) return; // If captive portal redirect instead of displaying the page
 	String page = getHTTPHead(_title); // @token options
@@ -3867,7 +3929,7 @@ void WiFiManager::handleUpdating(){
     #endif
 
     #ifdef WM_DEBUG_LEVEL
-    DEBUG_WM(DEBUG_VERBOSE,"[OTA] Update file: ", upload.filename.c_str());
+    DEBUG_WM(WM_DEBUG_VERBOSE,"[OTA] Update file: ", upload.filename.c_str());
     #endif
 
     // Update.onProgress(THandlerFunction_Progress fn);
@@ -3877,7 +3939,7 @@ void WiFiManager::handleUpdating(){
 
   	if (!Update.begin(maxSketchSpace)) { // start with max available size
         #ifdef WM_DEBUG_LEVEL
-        DEBUG_WM(DEBUG_ERROR,F("[ERROR] OTA Update ERROR"), Update.getError());
+        DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] OTA Update ERROR"), Update.getError());
         #endif
         error = true;
         Update.end(); // Not sure the best way to abort, I think client will keep sending..
@@ -3888,7 +3950,7 @@ void WiFiManager::handleUpdating(){
 		// Serial.print(".");
 		if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_ERROR,F("[ERROR] OTA Update WRITE ERROR"), Update.getError());
+      DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] OTA Update WRITE ERROR"), Update.getError());
 			//Update.printError(Serial); // write failure
       #endif
       error = true;
@@ -3898,7 +3960,7 @@ void WiFiManager::handleUpdating(){
   else if (upload.status == UPLOAD_FILE_END) {
 		if (Update.end(true)) { // true to set the size to the current progress
       #ifdef WM_DEBUG_LEVEL
-      DEBUG_WM(DEBUG_VERBOSE,F("\n\n[OTA] OTA FILE END bytes: "), upload.totalSize);
+      DEBUG_WM(WM_DEBUG_VERBOSE,F("\n\n[OTA] OTA FILE END bytes: "), upload.totalSize);
 			// Serial.printf("Updated: %u bytes\r\nRebooting...\r\n", upload.totalSize);
       #endif
 		}
@@ -3919,7 +3981,7 @@ void WiFiManager::handleUpdating(){
 
 // upload and ota done, show status
 void WiFiManager::handleUpdateDone() {
-	DEBUG_WM(DEBUG_VERBOSE, F("<- Handle update done"));
+	DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Handle update done"));
 	// if (captivePortal()) return; // If captive portal redirect instead of displaying the page
 
 	String page = getHTTPHead(FPSTR(S_options)); // @token options
diff --git a/neosensor/libraries/WiFiManager/WiFiManager.h b/neosensor/libraries/WiFiManager/WiFiManager.h
index 02286e4763e75393f80a8187a26ff8fc2c451ed8..f911beb0d114174b45baeb3385a635583bd18c09 100644
--- a/neosensor/libraries/WiFiManager/WiFiManager.h
+++ b/neosensor/libraries/WiFiManager/WiFiManager.h
@@ -51,10 +51,10 @@
 // #warning ESP32S3
 // #endif
 
-#if defined(ARDUINO_ESP32S3_DEV) || defined(CONFIG_IDF_TARGET_ESP32S3)
-#warning "WM_NOTEMP"
-#define WM_NOTEMP // disabled temp sensor, have to determine which chip we are on
-#endif
+// #if defined(ARDUINO_ESP32S3_DEV) || defined(CONFIG_IDF_TARGET_ESP32S3)
+// #warning "WM_NOTEMP"
+// #define WM_NOTEMP // disabled temp sensor, have to determine which chip we are on
+// #endif
 
 // #include "soc/efuse_reg.h" // include to add efuse chip rev to info, getChipRevision() is almost always the same though, so not sure why it matters.
 
@@ -131,13 +131,19 @@
 
 #include <DNSServer.h>
 #include <memory>
-#include "strings_en.h"
+
+
+// Include wm strings vars
+// Pass in strings env override via WM_STRINGS_FILE
+#ifndef WM_STRINGS_FILE
+#define WM_STRINGS_FILE "wm_strings_en.h" // this includes constants as dependency
+#endif
+#include WM_STRINGS_FILE
 
 // prep string concat vars
 #define WM_STRING2(x) #x
 #define WM_STRING(x) WM_STRING2(x)    
 
-
 // #include <esp_idf_version.h>
 #ifdef ESP_IDF_VERSION
     // #pragma message "ESP_IDF_VERSION_MAJOR = " WM_STRING(ESP_IDF_VERSION_MAJOR)
@@ -157,19 +163,26 @@
         // #pragma message "ESP_ARDUINO_VERSION_MAJOR = " WM_STRING(ESP_ARDUINO_VERSION_MAJOR)
         // #pragma message "ESP_ARDUINO_VERSION_MINOR = " WM_STRING(ESP_ARDUINO_VERSION_MINOR)
         // #pragma message "ESP_ARDUINO_VERSION_PATCH = " WM_STRING(ESP_ARDUINO_VERSION_PATCH)
+        #ifdef ESP_ARDUINO_VERSION_MAJOR
         #define VER_ARDUINO_STR WM_STRING(ESP_ARDUINO_VERSION_MAJOR)  "."  WM_STRING(ESP_ARDUINO_VERSION_MINOR)  "."  WM_STRING(ESP_ARDUINO_VERSION_PATCH)
+        #else
+        #define VER_ARDUINO_STR "Unknown"
+        #endif
     #else
         #include <core_version.h>
         // #pragma message "ESP_ARDUINO_VERSION_GIT  = " WM_STRING(ARDUINO_ESP32_GIT_VER)//  0x46d5afb1
         // #pragma message "ESP_ARDUINO_VERSION_DESC = " WM_STRING(ARDUINO_ESP32_GIT_DESC) //  1.0.6
         // #pragma message "ESP_ARDUINO_VERSION_REL  = " WM_STRING(ARDUINO_ESP32_RELEASE) //"1_0_6"
+        #ifdef ESP_ARDUINO_VERSION_MAJOR
         #define VER_ARDUINO_STR WM_STRING(ESP_ARDUINO_VERSION_MAJOR)  "."  WM_STRING(ESP_ARDUINO_VERSION_MINOR)  "."  WM_STRING(ESP_ARDUINO_VERSION_PATCH)
+        #else
+        #define VER_ARDUINO_STR "Unknown"
+        #endif
     #endif
 #else 
 #define VER_ARDUINO_STR "Unknown"
 #endif
 
-
 // #pragma message "VER_IDF_STR = " WM_STRING(VER_IDF_STR)
 // #pragma message "VER_ARDUINO_STR = " WM_STRING(VER_ARDUINO_STR)
 
@@ -209,19 +222,28 @@ class WiFiManagerParameter {
   protected:
     void init(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement);
 
-  private:
     WiFiManagerParameter& operator=(const WiFiManagerParameter&);
     const char *_id;
     const char *_label;
     char       *_value;
     int         _length;
     int         _labelPlacement;
-  protected:
+  
     const char *_customHTML;
     friend class WiFiManager;
 };
 
 
+    // debugging
+    typedef enum {
+        WM_DEBUG_SILENT    = 0, // debug OFF but still compiled for runtime
+        WM_DEBUG_ERROR     = 1, // error only
+        WM_DEBUG_NOTIFY    = 2, // default stable,INFO
+        WM_DEBUG_VERBOSE   = 3, // move verbose info
+        WM_DEBUG_DEV       = 4, // development useful debugging info
+        WM_DEBUG_MAX       = 5  // MAX extra dev auditing, var dumps etc (MAX+1 will print timing,mem and frag info)
+    } wm_debuglevel_t;
+
 class WiFiManager
 {
   public:
@@ -326,6 +348,7 @@ class WiFiManager
     // toggle debug output
     void          setDebugOutput(boolean debug);
     void          setDebugOutput(boolean debug, String prefix); // log line prefix, default "*wm:"
+    void          setDebugOutput(boolean debug, wm_debuglevel_t level ); // log line prefix, default "*wm:"
 
     //set min quality percentage to include in scan, defaults to 8% if not specified
     void          setMinimumSignalQuality(int quality = 8);
@@ -485,7 +508,7 @@ class WiFiManager
     
     std::unique_ptr<WM_WebServer> server;
 
-  private:
+  protected:
     // vars
     std::vector<uint8_t> _menuIds;
     std::vector<const char *> _menuIdsParams  = {"wifi","param","info","exit"};
@@ -536,7 +559,7 @@ class WiFiManager
     uint16_t      _httpPort               = 80; // port for webserver
     // uint8_t       _retryCount             = 0; // counter for retries, probably not needed if synchronous
     uint8_t       _connectRetries         = 1; // number of sta connect retries, force reconnect, wait loop (connectimeout) does not always work and first disconnect bails
-    bool          _aggresiveReconn        = true; // use an agrressive reconnect strategy, WILL delay conxs
+    bool          _aggresiveReconn        = false; // use an agrressive reconnect strategy, WILL delay conxs
                                                    // on some conn failure modes will add delays and many retries to work around esp and ap bugs, ie, anti de-auth protections
                                                    // https://github.com/tzapu/WiFiManager/issues/1067
     bool          _allowExit              = true; // allow exit in nonblocking, else user exit/abort calls will be ignored including cptimeout
@@ -594,9 +617,15 @@ class WiFiManager
     // but not limited to, we could run continuous background scans on various page hits, or xhr hits
     // which would be better coupled with asyncscan
     // atm preload is only done on root hit and startcp
-    boolean       _preloadwifiscan        = true; // preload wifiscan if true
+    // 
+    // preload scanning causes AP to delay showing for users, but also caches and lets the cp load faster once its open
+    //  my scan takes 7-10 seconds
+public:
+    boolean       _preloadwifiscan        = false; // preload wifiscan if true
     unsigned int  _scancachetime          = 30000; // ms cache time for preload scans
-    boolean       _asyncScan              = true; // perform wifi network scan async
+    boolean       _asyncScan              = false; // perform wifi network scan async
+    
+protected:
 
     boolean       _autoforcerescan        = false;  // automatically force rescan if scan networks is 0, ignoring cache
     
@@ -633,13 +662,16 @@ class WiFiManager
     void          updateConxResult(uint8_t status);
 
     // webserver handlers
-    void          HTTPSend(String content);
+public:
+    void          handleNotFound();
+protected:
+    void          HTTPSend(const String &content);
     void          handleRoot();
     void          handleWifi(boolean scan);
     void          handleWifiSave();
     void          handleInfo();
     void          handleReset();
-    void          handleNotFound();
+
     void          handleExit();
     void          handleClose();
     // void          handleErase();
@@ -734,8 +766,15 @@ class WiFiManager
     boolean       abort               = false;
     boolean       reset               = false;
     boolean       configPortalActive  = false;
+
+
+    // these are state flags for portal mode, we are either in webportal mode(STA) or configportal mode(AP)
+    // these are mutually exclusive as STA+AP mode is not supported due to channel restrictions and stability
+    // if we decide to support this, these checks will need to be replaced with something client aware to check if client origin is ap or web
+    // These state checks are critical and used for internal function checks
     boolean       webPortalActive     = false;
     boolean       portalTimeoutResult = false;
+
     boolean       portalAbortResult   = false;
     boolean       storeSTAmode        = true; // option store persistent STA mode in connectwifi 
     int           timer               = 0;    // timer for debug throttle for numclients, and portal timeout messages
@@ -745,26 +784,17 @@ class WiFiManager
     int         _max_params;
     WiFiManagerParameter** _params    = NULL;
 
-    // debugging
-    typedef enum {
-        DEBUG_ERROR     = 0,
-        DEBUG_NOTIFY    = 1, // default stable
-        DEBUG_VERBOSE   = 2,
-        DEBUG_DEV       = 3, // default dev
-        DEBUG_MAX       = 4
-    } wm_debuglevel_t;
-
     boolean _debug  = true;
     String _debugPrefix = FPSTR(S_debugPrefix);
 
-    wm_debuglevel_t debugLvlShow = DEBUG_VERBOSE; // at which level start showing [n] level tags
+    wm_debuglevel_t debugLvlShow = WM_DEBUG_VERBOSE; // at which level start showing [n] level tags
 
     // build debuglevel support
     // @todo use DEBUG_ESP_x?
     
     // Set default debug level
     #ifndef WM_DEBUG_LEVEL
-    #define WM_DEBUG_LEVEL DEBUG_VERBOSE // development default, not release
+    #define WM_DEBUG_LEVEL WM_DEBUG_NOTIFY
     #endif
 
     // override debug level OFF
@@ -775,7 +805,7 @@ class WiFiManager
     #ifdef WM_DEBUG_LEVEL
     uint8_t _debugLevel = (uint8_t)WM_DEBUG_LEVEL;
     #else 
-    uint8_t _debugLevel = DEBUG_VERBOSE; // default debug level
+    uint8_t _debugLevel = 0; // default debug level
     #endif
 
     // @todo use DEBUG_ESP_PORT ?
diff --git a/neosensor/libraries/WiFiManager/examples/Super/OnDemandConfigPortal/OnDemandConfigPortal.ino b/neosensor/libraries/WiFiManager/examples/Super/OnDemandConfigPortal/OnDemandConfigPortal.ino
index a648366f629c8c517292d9ae6013242a808ae6de..7962d626b6cb4b0252c3ed857a5fd8951fb1f60e 100644
--- a/neosensor/libraries/WiFiManager/examples/Super/OnDemandConfigPortal/OnDemandConfigPortal.ino
+++ b/neosensor/libraries/WiFiManager/examples/Super/OnDemandConfigPortal/OnDemandConfigPortal.ino
@@ -31,6 +31,8 @@ bool ALLOWONDEMAND   = true; // enable on demand
 int  ONDDEMANDPIN    = 0; // gpio for button
 bool WMISBLOCKING    = true; // use blocking or non blocking mode, non global params wont work in non blocking
 
+uint8_t BUTTONFUNC   = 1; // 0 resetsettings, 1 configportal, 2 autoconnect
+
 // char ssid[] = "*************";  //  your network SSID (name)
 // char pass[] = "********";       // your network password
 
@@ -72,15 +74,24 @@ void saveParamCallback(){
 }
 
 void bindServerCallback(){
-  wm.server->on("/custom",handleRoute); // this is now crashing esp32 for some reason
-  // wm.server->on("/info",handleRoute); // you can override wm!
+  wm.server->on("/custom",handleRoute);
+
+  // you can override wm route endpoints, I have not found a way to remove handlers, but this would let you disable them or add auth etc.
+  // wm.server->on("/info",handleNotFound);
+  // wm.server->on("/update",handleNotFound);
+  wm.server->on("/erase",handleNotFound); // disable erase
 }
 
 void handleRoute(){
-  Serial.println("[HTTP] handle route");
+  Serial.println("[HTTP] handle custom route");
   wm.server->send(200, "text/plain", "hello from user code");
 }
 
+void handleNotFound(){
+  Serial.println("[HTTP] override handle route");
+  wm.handleNotFound();
+}
+
 void handlePreOtaUpdateCallback(){
   Update.onProgress([](unsigned int progress, unsigned int total) {
         Serial.printf("CUSTOM Progress: %u%%\r", (progress / (total / 100)));
@@ -92,9 +103,11 @@ void setup() {
   
   // put your setup code here, to run once:
   Serial.begin(115200);
-
+  delay(3000);
   // Serial.setDebugOutput(true);
 
+  // WiFi.setTxPower(WIFI_POWER_8_5dBm);
+
   Serial.println("\n Starting");
   // WiFi.setSleepMode(WIFI_NONE_SLEEP); // disable sleep, can improve ap stability
 
@@ -105,7 +118,11 @@ void setup() {
   Serial.println("[INFORMATION] TEST");  
 
 
-  wm.setDebugOutput(true);
+  // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // wifi_scan_method_t scanMethod
+  // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // wifi_sort_method_t sortMethod - WIFI_CONNECT_AP_BY_SIGNAL,WIFI_CONNECT_AP_BY_SECURITY
+  // WiFi.setMinSecurity(WIFI_AUTH_WPA2_PSK);
+
+  wm.setDebugOutput(true, WM_DEBUG_DEV);
   wm.debugPlatformInfo();
 
   //reset settings - for testing
@@ -123,7 +140,7 @@ void setup() {
   WiFiManagerParameter custom_input_type("input_pwd", "input pass", "", 15,"type='password'"); // custom input attrs (ip mask)
 
   const char _customHtml_checkbox[] = "type=\"checkbox\""; 
-  WiFiManagerParameter custom_checkbox("my_checkbox", "My Checkbox", "T", 2, _customHtml_checkbox,WFM_LABEL_AFTER);
+  WiFiManagerParameter custom_checkbox("my_checkbox", "My Checkbox", "T", 2, _customHtml_checkbox, WFM_LABEL_AFTER);
 
   const char *bufferStr = R"(
   <!-- INPUT CHOICE -->
@@ -201,7 +218,7 @@ void setup() {
 */
 
   std::vector<const char *> menu = {"wifi","wifinoscan","info","param","custom","close","sep","erase","update","restart","exit"};
-  wm.setMenu(menu); // custom menu, pass vector
+  // wm.setMenu(menu); // custom menu, pass vector
   
   // wm.setParamsPage(true); // move params to seperate page, not wifi, do not combine with setmenu!
 
@@ -222,7 +239,7 @@ void setup() {
 
   // set Hostname
 
-  wm.setHostname(("WM_"+wm.getDefaultAPName()).c_str());
+  // wm.setHostname(("WM_"+wm.getDefaultAPName()).c_str());
   // wm.setHostname("WM_RANDO_1234");
 
   // set custom channel
@@ -241,9 +258,10 @@ void setup() {
     wm.setConfigPortalBlocking(false);
   }
 
+
   //sets timeout until configuration portal gets turned off
   //useful to make it all retry or go to sleep in seconds
-  wm.setConfigPortalTimeout(120);
+  wm.setConfigPortalTimeout(TESP_CP_TIMEOUT);
   
   // set min quality to show in web list, default 8%
   // wm.setMinimumSignalQuality(50);
@@ -309,12 +327,13 @@ void setup() {
 
 void wifiInfo(){
   // can contain gargbage on esp32 if wifi is not ready yet
-  Serial.println("[WIFI] WIFI INFO DEBUG");
-  // WiFi.printDiag(Serial);
+  Serial.println("[WIFI] WIFI_INFO DEBUG");
+  WiFi.printDiag(Serial);
+  Serial.println("[WIFI] MODE: " + (String)(wm.getModeString(WiFi.getMode())));
   Serial.println("[WIFI] SAVED: " + (String)(wm.getWiFiIsSaved() ? "YES" : "NO"));
   Serial.println("[WIFI] SSID: " + (String)wm.getWiFiSSID());
   Serial.println("[WIFI] PASS: " + (String)wm.getWiFiPass());
-  Serial.println("[WIFI] HOSTNAME: " + (String)WiFi.getHostname());
+  // Serial.println("[WIFI] HOSTNAME: " + (String)WiFi.getHostname());
 }
 
 void loop() {
@@ -323,31 +342,40 @@ void loop() {
     wm.process();
   }
 
+
   #ifdef USEOTA
   ArduinoOTA.handle();
   #endif
   // is configuration portal requested?
   if (ALLOWONDEMAND && digitalRead(ONDDEMANDPIN) == LOW ) {
     delay(100);
-    if ( digitalRead(ONDDEMANDPIN) == LOW ){
+    if ( digitalRead(ONDDEMANDPIN) == LOW || BUTTONFUNC == 2){
       Serial.println("BUTTON PRESSED");
 
       // button reset/reboot
-      // wm.resetSettings();
-      // wm.reboot();
-      // delay(200);
-      // return;
+      if(BUTTONFUNC == 0){
+        wm.resetSettings();
+        wm.reboot();
+        delay(200);
+        return;
+      }
       
-      wm.setConfigPortalTimeout(140);
-      wm.setParamsPage(false); // move params to seperate page, not wifi, do not combine with setmenu!
+      // start configportal
+      if(BUTTONFUNC == 1){
+        if (!wm.startConfigPortal("OnDemandAP","12345678")) {
+          Serial.println("failed to connect and hit timeout");
+          delay(3000);
+        }
+        return;
+      }
 
-      // disable captive portal redirection
-      // wm.setCaptivePortalEnable(false);
-      
-      if (!wm.startConfigPortal("OnDemandAP","12345678")) {
-        Serial.println("failed to connect and hit timeout");
-        delay(3000);
+      //test autoconnect as reconnect etc.
+      if(BUTTONFUNC == 2){
+        wm.setConfigPortalTimeout(TESP_CP_TIMEOUT);
+        wm.autoConnect();
+        return;
       }
+    
     }
     else {
       //if you get here you have connected to the WiFi
diff --git a/neosensor/libraries/WiFiManager/extras/WiFiManager.template.html b/neosensor/libraries/WiFiManager/extras/WiFiManager.template.html
index 20787e1393c54429f4b4f12311394095b9312eab..934c03394e5d89f78d5e223207ddae2b8e2f9389 100644
--- a/neosensor/libraries/WiFiManager/extras/WiFiManager.template.html
+++ b/neosensor/libraries/WiFiManager/extras/WiFiManager.template.html
@@ -227,6 +227,7 @@ button:hover{
 			<form action='/erase' method='post'><button class='D'>Erase</button></form><br/>
 			<form action='/restart' method='post'><button>Restart</button></form><br/>
 			<form action='/exit' method='post'><button>Exit</button></form><br/>
+			<form action='/exit' method='post'><button>Update</button></form><br/>
 			<form action='/' method='post'><button>Back</button></form><br/>
 			<!-- /HTTP_PORTAL_MENU -->
 			<!-- SAMPLE -->
@@ -251,7 +252,7 @@ button:hover{
 			<div><a href='#p' onclick='c(this)'>{v}</a><div role='img' aria-label='{r}%' title='{r}%' class='q q-{q} {i}'></div></div>
 			<!-- /HTTP_ITEM -->
 			<!-- HTTP_FORM_START -->
-			<form method='get' action='wifisave'><label for='s'>SSID</label><br/><input id='s' name='s' length=32 placeholder='SSID'><br/><label for='p'>Password</label><input id='p' name='p' length=64 type='password' placeholder='password'><input type='checkbox' onclick='f()'> Show Password<br/>
+			<form method='get' action='wifisave'><label for='s'>SSID</label><br/><input id='s' name='s' length=32 placeholder='SSID'><br/><label for='p'>Password</label><input id='p' name='p' length=64 type='password' placeholder='password'><input type='checkbox' id='show-password' onclick='f()'> <label for='show-password'>Show Password</label><br/>
 			<!-- /HTTP_FORM_START -->
 			<!-- SAMPLE -->
 			<h3>custom parameter</h3><hr>
diff --git a/neosensor/libraries/WiFiManager/library.json b/neosensor/libraries/WiFiManager/library.json
index eaf4b5e3c03a3ad967b982fab0da351857c7a355..a04050af092cdff53d7da8faa5d12b09919227ea 100644
--- a/neosensor/libraries/WiFiManager/library.json
+++ b/neosensor/libraries/WiFiManager/library.json
@@ -1,6 +1,6 @@
 {
   "name": "WiFiManager",
-  "version": "2.0.14-beta",
+  "version": "2.0.17",
   "keywords": "wifi,wi-fi,esp,esp8266,esp32,espressif8266,espressif32,nodemcu,wemos,arduino",
   "description": "WiFi Configuration manager with web configuration portal for ESP boards",
   "authors":
diff --git a/neosensor/libraries/WiFiManager/library.properties b/neosensor/libraries/WiFiManager/library.properties
index 3dbc5f44e970ac7d6e39ad4972cf833bd26704f2..559ff72f70553b9a6e37cfae48462b559bd02ee4 100644
--- a/neosensor/libraries/WiFiManager/library.properties
+++ b/neosensor/libraries/WiFiManager/library.properties
@@ -1,5 +1,5 @@
 name=WiFiManager
-version=2.0.14-beta
+version=2.0.17
 author=tzapu
 maintainer=tablatronix
 sentence=WiFi Configuration manager with web configuration portal for Espressif ESPx boards, by tzapu
diff --git a/neosensor/libraries/WiFiManager/strings_en.h b/neosensor/libraries/WiFiManager/strings_en.h
index 5ea622ecfdd79bffd492cbd1536c8a0d7e28ffd7..cabeb58ba3408c045e1139e019a4ae845a7d1c3e 100644
--- a/neosensor/libraries/WiFiManager/strings_en.h
+++ b/neosensor/libraries/WiFiManager/strings_en.h
@@ -1,502 +1,15 @@
 /**
- * strings_en.h
- * engligh strings for
- * WiFiManager, a library for the ESP8266/Arduino platform
- * for configuration of WiFi credentials using a Captive Portal
- *
- * @author Creator tzapu
- * @author tablatronix
- * @version 0.0.0
- * @license MIT
+ * Contents of this file have moved to 2 new locations
+ * wm_strings_nn.h
+ *  wm_consts_nn.h
  */
 
-#ifndef _WM_STRINGS_H_
-#define _WM_STRINGS_H_
+#warning "This file is deprecated"
 
+#ifndef _STRINGS_EN_H_
+#define _STRINGS_EN_H_
 
-#ifndef WIFI_MANAGER_OVERRIDE_STRINGS
-// !!! ABOVE WILL NOT WORK if you define in your sketch, must be build flag, if anyone one knows how to order includes to be able to do this it would be neat.. I have seen it done..
+// strings files must include a consts file!
+#include "wm_strings_en.h" // include constants, tokens, routes
 
-const char WM_VERSION_STR[] PROGMEM = "v2.0.13-beta";
-
-const char HTTP_HEAD_START[]       PROGMEM = "<!DOCTYPE html>"
-"<html lang='en'><head>"
-"<meta name='format-detection' content='telephone=no'>"
-"<meta charset='UTF-8'>"
-"<meta  name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
-"<title>{v}</title>";
-
-const char HTTP_SCRIPT[]           PROGMEM = "<script>function c(l){"
-"document.getElementById('s').value=l.getAttribute('data-ssid')||l.innerText||l.textContent;"
-"p = l.nextElementSibling.classList.contains('l');"
-"document.getElementById('p').disabled = !p;"
-"if(p)document.getElementById('p').focus();};"
-"function f() {var x = document.getElementById('p');x.type==='password'?x.type='text':x.type='password';}"
-"</script>"; // @todo add button states, disable on click , show ack , spinner etc
-
-const char HTTP_HEAD_END[]         PROGMEM = "</head><body class='{c}'><div class='wrap'>"; // {c} = _bodyclass
-// example of embedded logo, base64 encoded inline, No styling here
-// const char HTTP_ROOT_MAIN[]        PROGMEM = "<img title=' alt=' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADQElEQVRoQ+2YjW0VQQyE7Q6gAkgFkAogFUAqgFQAVACpAKiAUAFQAaECQgWECggVGH1PPrRvn3dv9/YkFOksoUhhfzwz9ngvKrc89JbnLxuA/63gpsCmwCADWwkNEji8fVNgotDM7osI/x777x5l9F6JyB8R4eeVql4P0y8yNsjM7KGIPBORp558T04A+CwiH1UVUItiUQmZ2XMReSEiAFgjAPBeVS96D+sCYGaUx4cFbLfmhSpnqnrZuqEJgJnd8cQplVLciAgX//Cf0ToIeOB9wpmloLQAwpnVmAXgdf6pwjpJIz+XNoeZQQZlODV9vhc1Tuf6owrAk/8qIhFbJH7eI3eEzsvydQEICqBEkZwiALfF70HyHPpqScPV5HFjeFu476SkRA0AzOfy4hYwstj2ZkDgaphE7m6XqnoS7Q0BOPs/sw0kDROzjdXcCMFCNwzIy0EcRcOvBACfh4k0wgOmBX4xjfmk4DKTS31hgNWIKBCI8gdzogTgjYjQWFMw+o9LzJoZ63GUmjWm2wGDc7EvDDOj/1IVMIyD9SUAL0WEhpriRlXv5je5S+U1i2N88zdPuoVkeB+ls4SyxCoP3kVm9jsjpEsBLoOBNC5U9SwpGdakFkviuFP1keblATkTENTYcxkzgxTKOI3jyDxqLkQT87pMA++H3XvJBYtsNbBN6vuXq5S737WqHkW1VgMQNXJ0RshMqbbT33sJ5kpHWymzcJjNTeJIymJZtSQd9NHQHS1vodoFoTMkfbJzpRnLzB2vi6BZAJxWaCr+62BC+jzAxVJb3dmmiLzLwZhZNPE5e880Suo2AZgB8e8idxherqUPnT3brBDTlPxO3Z66rVwIwySXugdNd+5ejhqp/+NmgIwGX3Py3QBmlEi54KlwmjkOytQ+iJrLJj23S4GkOeecg8G091no737qvRRdzE+HLALQoMTBbJgBsCj5RSWUlUVJiZ4SOljb05eLFWgoJ5oY6yTyJp62D39jDANoKKcSocPJD5dQYzlFAFZJflUArgTPZKZwLXAnHmerfJquUkKZEgyzqOb5TuDt1P3nwxobqwPocZA11m4A1mBx5IxNgRH21ti7KbAGiyNn3HoF/gJ0w05A8xclpwAAAABJRU5ErkJggg==' /><h1>{v}</h1><h3>WiFiManager</h3>";
-const char HTTP_ROOT_MAIN[]        PROGMEM = "<h1>{t}</h1><h3>{v}</h3>";
-
-const char * const HTTP_PORTAL_MENU[] PROGMEM = {
-"<form action='/wifi'    method='get'><button>Configure WiFi</button></form><br/>\n", // MENU_WIFI
-"<form action='/0wifi'   method='get'><button>Configure WiFi (No Scan)</button></form><br/>\n", // MENU_WIFINOSCAN
-"<form action='/info'    method='get'><button>Info</button></form><br/>\n", // MENU_INFO
-"<form action='/param'   method='get'><button>Setup</button></form><br/>\n",//MENU_PARAM
-"<form action='/close'   method='get'><button>Close</button></form><br/>\n", // MENU_CLOSE
-"<form action='/restart' method='get'><button>Restart</button></form><br/>\n",// MENU_RESTART
-"<form action='/exit'    method='get'><button>Exit</button></form><br/>\n",  // MENU_EXIT
-"<form action='/erase'   method='get'><button class='D'>Erase</button></form><br/>\n", // MENU_ERASE
-"<form action='/update'  method='get'><button>Update</button></form><br/>\n",// MENU_UPDATE
-"<hr><br/>" // MENU_SEP
-};
-
-// const char HTTP_PORTAL_OPTIONS[]   PROGMEM = strcat(HTTP_PORTAL_MENU[0] , HTTP_PORTAL_MENU[3] , HTTP_PORTAL_MENU[7]);
-const char HTTP_PORTAL_OPTIONS[]   PROGMEM = "";
-const char HTTP_ITEM_QI[]          PROGMEM = "<div role='img' aria-label='{r}%' title='{r}%' class='q q-{q} {i} {h}'></div>"; // rssi icons
-const char HTTP_ITEM_QP[]          PROGMEM = "<div class='q {h}'>{r}%</div>"; // rssi percentage {h} = hidden showperc pref
-const char HTTP_ITEM[]             PROGMEM = "<div><a href='#p' onclick='c(this)' data-ssid='{V}'>{v}</a>{qi}{qp}</div>"; // {q} = HTTP_ITEM_QI, {r} = HTTP_ITEM_QP
-// const char HTTP_ITEM[]            PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a> {R} {r}% {q} {e}</div>"; // test all tokens
-
-const char HTTP_FORM_START[]       PROGMEM = "<form method='POST' action='{v}'>";
-const char HTTP_FORM_WIFI[]        PROGMEM = "<label for='s'>SSID</label><input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='{v}'><br/><label for='p'>Password</label><input id='p' name='p' maxlength='64' type='password' placeholder='{p}'><input type='checkbox' onclick='f()'> Show Password";
-const char HTTP_FORM_WIFI_END[]    PROGMEM = "";
-const char HTTP_FORM_STATIC_HEAD[] PROGMEM = "<hr><br/>";
-const char HTTP_FORM_END[]         PROGMEM = "<br/><br/><button type='submit'>Save</button></form>";
-const char HTTP_FORM_LABEL[]       PROGMEM = "<label for='{i}'>{t}</label>";
-const char HTTP_FORM_PARAM_HEAD[]  PROGMEM = "<hr><br/>";
-const char HTTP_FORM_PARAM[]       PROGMEM = "<br/><input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n"; // do not remove newline!
-
-const char HTTP_SCAN_LINK[]        PROGMEM = "<br/><form action='/wifi?refresh=1' method='POST'><button name='refresh' value='1'>Refresh</button></form>";
-const char HTTP_SAVED[]            PROGMEM = "<div class='msg'>Saving Credentials<br/>Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
-const char HTTP_PARAMSAVED[]       PROGMEM = "<div class='msg S'>Saved<br/></div>";
-const char HTTP_END[]              PROGMEM = "</div></body></html>";
-const char HTTP_ERASEBTN[]         PROGMEM = "<br/><form action='/erase' method='get'><button class='D'>Erase WiFi Config</button></form>";
-const char HTTP_UPDATEBTN[]        PROGMEM = "<br/><form action='/update' method='get'><button>Update</button></form>";
-const char HTTP_BACKBTN[]          PROGMEM = "<hr><br/><form action='/' method='get'><button>Back</button></form>";
-
-const char HTTP_STATUS_ON[]        PROGMEM = "<div class='msg S'><strong>Connected</strong> to {v}<br/><em><small>with IP {i}</small></em></div>";
-const char HTTP_STATUS_OFF[]       PROGMEM = "<div class='msg {c}'><strong>Not Connected</strong> to {v}{r}</div>"; // {c=class} {v=ssid} {r=status_off}
-const char HTTP_STATUS_OFFPW[]     PROGMEM = "<br/>Authentication Failure"; // STATION_WRONG_PASSWORD,  no eps32
-const char HTTP_STATUS_OFFNOAP[]   PROGMEM = "<br/>AP not found";   // WL_NO_SSID_AVAIL
-const char HTTP_STATUS_OFFFAIL[]   PROGMEM = "<br/>Could not Connect"; // WL_CONNECT_FAILED
-const char HTTP_STATUS_NONE[]      PROGMEM = "<div class='msg'>No AP set</div>";
-const char HTTP_BR[]               PROGMEM = "<br/>";
-
-const char HTTP_STYLE[]            PROGMEM = "<style>"
-".c,body{text-align:center;font-family:verdana}div,input,select{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box}"
-"input,button,select,.msg{border-radius:.3rem;width: 100%}input[type=radio],input[type=checkbox]{width:auto}"
-"button,input[type='button'],input[type='submit']{cursor:pointer;border:0;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}"
-"input[type='file']{border:1px solid #1fa3ec}"
-".wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}"
-// links
-"a{color:#000;font-weight:700;text-decoration:none}a:hover{color:#1fa3ec;text-decoration:underline}"
-// quality icons
-".q{height:16px;margin:0;padding:0 5px;text-align:right;min-width:38px;float:right}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px;padding-right:5px}.ql .q{float:left}.q:after,.q:before{content:'';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position: 16px 0;"
-"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}"
-// icons @2x media query (32px rescaled)
-"@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {"
-"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');"
-"background-size: 95px 16px;}}"
-// msg callouts
-".msg{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-left-color:#777}.msg h4{margin-top:0;margin-bottom:5px}.msg.P{border-left-color:#1fa3ec}.msg.P h4{color:#1fa3ec}.msg.D{border-left-color:#dc3630}.msg.D h4{color:#dc3630}.msg.S{border-left-color: #5cb85c}.msg.S h4{color: #5cb85c}"
-// lists
-"dt{font-weight:bold}dd{margin:0;padding:0 0 0.5em 0;min-height:12px}"
-"td{vertical-align: top;}"
-".h{display:none}"
-"button{transition: 0s opacity;transition-delay: 3s;transition-duration: 0s;cursor: pointer}"
-"button.D{background-color:#dc3630}"
-"button:active{opacity:50% !important;cursor:wait;transition-delay: 0s}"
-// invert
-"body.invert,body.invert a,body.invert h1 {background-color:#060606;color:#fff;}"
-"body.invert .msg{color:#fff;background-color:#282828;border-top:1px solid #555;border-right:1px solid #555;border-bottom:1px solid #555;}"
-"body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}"
-":disabled {opacity: 0.5;}"
-"</style>";
-
-#ifndef WM_NOHELP
-const char HTTP_HELP[]             PROGMEM =
- "<br/><h3>Available Pages</h3><hr>"
- "<table class='table'>"
- "<thead><tr><th>Page</th><th>Function</th></tr></thead><tbody>"
- "<tr><td><a href='/'>/</a></td>"
- "<td>Menu page.</td></tr>"
- "<tr><td><a href='/wifi'>/wifi</a></td>"
- "<td>Show WiFi scan results and enter WiFi configuration.(/0wifi noscan)</td></tr>"
- "<tr><td><a href='/wifisave'>/wifisave</a></td>"
- "<td>Save WiFi configuration information and configure device. Needs variables supplied.</td></tr>"
- "<tr><td><a href='/param'>/param</a></td>"
- "<td>Parameter page</td></tr>"
- "<tr><td><a href='/info'>/info</a></td>"
- "<td>Information page</td></tr>"
- "<tr><td><a href='/u'>/u</a></td>"
- "<td>OTA Update</td></tr>"
- "<tr><td><a href='/close'>/close</a></td>"
- "<td>Close the captiveportal popup,configportal will remain active</td></tr>"
- "<tr><td>/exit</td>"
- "<td>Exit Config Portal, configportal will close</td></tr>"
- "<tr><td>/restart</td>"
- "<td>Reboot the device</td></tr>"
- "<tr><td>/erase</td>"
- "<td>Erase WiFi configuration and reboot Device. Device will not reconnect to a network until new WiFi configuration data is entered.</td></tr>"
- "</table>"
- "<p/>Github <a href='https://github.com/tzapu/WiFiManager'>https://github.com/tzapu/WiFiManager</a>.";
-#else
-const char HTTP_HELP[]             PROGMEM = "";
-#endif
-
-const char HTTP_UPDATE[] PROGMEM = "Upload New Firmware<br/><form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>Update</button></form><small><a href='http://192.168.4.1/update' target='_blank'>* May not function inside captive portal, Open in browser http://192.168.4.1</a><small>";
-const char HTTP_UPDATE_FAIL[] PROGMEM = "<div class='msg D'><strong>Update Failed!</strong><Br/>Reboot device and try again</div>";
-const char HTTP_UPDATE_SUCCESS[] PROGMEM = "<div class='msg S'><strong>Update Successful.  </strong> <br/> Device Rebooting now...</div>";
-
-#ifdef WM_JSTEST
-const char HTTP_JS[] PROGMEM =
-"<script>function postAjax(url, data, success) {"
-"    var params = typeof data == 'string' ? data : Object.keys(data).map("
-"            function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }"
-"        ).join('&');"
-"    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject(\"Microsoft.XMLHTTP\");"
-"    xhr.open('POST', url);"
-"    xhr.onreadystatechange = function() {"
-"        if (xhr.readyState>3 && xhr.status==200) { success(xhr.responseText); }"
-"    };"
-"    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');"
-"    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');"
-"    xhr.send(params);"
-"    return xhr;}"
-"postAjax('/status', 'p1=1&p2=Hello+World', function(data){ console.log(data); });"
-"postAjax('/status', { p1: 1, p2: 'Hello World' }, function(data){ console.log(data); });"
-"</script>";
-#endif
-
-// Info html
-// @todo remove html elements from progmem, repetetive strings
-#ifdef ESP32
-	const char HTTP_INFO_esphead[]    PROGMEM = "<h3>{1}</h3><hr><dl>";
-	const char HTTP_INFO_chiprev[]    PROGMEM = "<dt>Chip Rev</dt><dd>{1}</dd>";
-  	const char HTTP_INFO_lastreset[]  PROGMEM = "<dt>Last reset reason</dt><dd>CPU0: {1}<br/>CPU1: {2}</dd>";
-  	const char HTTP_INFO_aphost[]     PROGMEM = "<dt>Access Point Hostname</dt><dd>{1}</dd>";
-    const char HTTP_INFO_psrsize[]    PROGMEM = "<dt>PSRAM Size</dt><dd>{1} bytes</dd>";
-	const char HTTP_INFO_temp[]       PROGMEM = "<dt>Temperature</dt><dd>{1} C&deg; / {2} F&deg;</dd>";
-    // const char HTTP_INFO_temp[]       PROGMEM =  "<dt><dt>Hall</dt><dd>{3}</dd>";
-#else
-	const char HTTP_INFO_esphead[]    PROGMEM = "<h3>esp8266</h3><hr><dl>";
-	const char HTTP_INFO_fchipid[]    PROGMEM = "<dt>Flash Chip ID</dt><dd>{1}</dd>";
-	const char HTTP_INFO_corever[]    PROGMEM = "<dt>Core Version</dt><dd>{1}</dd>";
-	const char HTTP_INFO_bootver[]    PROGMEM = "<dt>Boot Version</dt><dd>{1}</dd>";
-	const char HTTP_INFO_lastreset[]  PROGMEM = "<dt>Last reset reason</dt><dd>{1}</dd>";
-	const char HTTP_INFO_flashsize[]  PROGMEM = "<dt>Real Flash Size</dt><dd>{1} bytes</dd>";
-#endif
-
-const char HTTP_INFO_memsmeter[]  PROGMEM = "<br/><progress value='{1}' max='{2}'></progress></dd>";
-const char HTTP_INFO_memsketch[]  PROGMEM = "<dt>Memory - Sketch Size</dt><dd>Used / Total bytes<br/>{1} / {2}";
-const char HTTP_INFO_freeheap[]   PROGMEM = "<dt>Memory - Free Heap</dt><dd>{1} bytes available</dd>";
-const char HTTP_INFO_wifihead[]   PROGMEM = "<br/><h3>WiFi <small><em>({1})</em></small></h3><hr>";
-const char HTTP_INFO_uptime[]     PROGMEM = "<dt>Uptime</dt><dd>{1} Mins {2} Secs</dd>";
-const char HTTP_INFO_chipid[]     PROGMEM = "<dt>Chip ID</dt><dd>{1}</dd>";
-const char HTTP_INFO_idesize[]    PROGMEM = "<dt>Flash Size</dt><dd>{1} bytes</dd>";
-const char HTTP_INFO_sdkver[]     PROGMEM = "<dt>SDK Version</dt><dd>{1}</dd>";
-const char HTTP_INFO_cpufreq[]    PROGMEM = "<dt>CPU Frequency</dt><dd>{1}MHz</dd>";
-const char HTTP_INFO_apip[]       PROGMEM = "<dt>Access Point IP</dt><dd>{1}</dd>";
-const char HTTP_INFO_apmac[]      PROGMEM = "<dt>Access Point MAC</dt><dd>{1}</dd>";
-const char HTTP_INFO_apssid[]     PROGMEM = "<dt>Access Point SSID</dt><dd>{1}</dd>";
-const char HTTP_INFO_apbssid[]    PROGMEM = "<dt>BSSID</dt><dd>{1}</dd>";
-const char HTTP_INFO_stassid[]    PROGMEM = "<dt>Station SSID</dt><dd>{1}</dd>";
-const char HTTP_INFO_staip[]      PROGMEM = "<dt>Station IP</dt><dd>{1}</dd>";
-const char HTTP_INFO_stagw[]      PROGMEM = "<dt>Station Gateway</dt><dd>{1}</dd>";
-const char HTTP_INFO_stasub[]     PROGMEM = "<dt>Station Subnet</dt><dd>{1}</dd>";
-const char HTTP_INFO_dnss[]       PROGMEM = "<dt>DNS Server</dt><dd>{1}</dd>";
-const char HTTP_INFO_host[]       PROGMEM = "<dt>Hostname</dt><dd>{1}</dd>";
-const char HTTP_INFO_stamac[]     PROGMEM = "<dt>Station MAC</dt><dd>{1}</dd>";
-const char HTTP_INFO_conx[]       PROGMEM = "<dt>Connected</dt><dd>{1}</dd>";
-const char HTTP_INFO_autoconx[]   PROGMEM = "<dt>Autoconnect</dt><dd>{1}</dd>";
-
-const char HTTP_INFO_aboutver[]     PROGMEM = "<dt>WiFiManager</dt><dd>{1}</dd>";
-const char HTTP_INFO_aboutarduino[] PROGMEM = "<dt>Arduino</dt><dd>{1}</dd>";
-const char HTTP_INFO_aboutsdk[]     PROGMEM = "<dt>ESP-SDK/IDF</dt><dd>{1}</dd>";
-const char HTTP_INFO_aboutdate[]    PROGMEM = "<dt>Build Date</dt><dd>{1}</dd>";
-
-const char S_brand[]              PROGMEM = "WiFiManager";
-const char S_debugPrefix[]        PROGMEM = "*wm:";
-const char S_y[]                  PROGMEM = "Yes";
-const char S_n[]                  PROGMEM = "No";
-const char S_enable[]             PROGMEM = "Enabled";
-const char S_disable[]            PROGMEM = "Disabled";
-const char S_GET[]                PROGMEM = "GET";
-const char S_POST[]               PROGMEM = "POST";
-const char S_NA[]                 PROGMEM = "Unknown";
-const char S_passph[]             PROGMEM = "********";
-const char S_titlewifisaved[]     PROGMEM = "Credentials Saved";
-const char S_titlewifisettings[]  PROGMEM = "Settings Saved";
-const char S_titlewifi[]          PROGMEM = "Config ESP";
-const char S_titleinfo[]          PROGMEM = "Info";
-const char S_titleparam[]         PROGMEM = "Setup";
-const char S_titleparamsaved[]    PROGMEM = "Setup Saved";
-const char S_titleexit[]          PROGMEM = "Exit";
-const char S_titlereset[]         PROGMEM = "Reset";
-const char S_titleerase[]         PROGMEM = "Erase";
-const char S_titleclose[]         PROGMEM = "Close";
-const char S_options[]            PROGMEM = "options";
-const char S_nonetworks[]         PROGMEM = "No networks found. Refresh to scan again.";
-const char S_staticip[]           PROGMEM = "Static IP";
-const char S_staticgw[]           PROGMEM = "Static Gateway";
-const char S_staticdns[]          PROGMEM = "Static DNS";
-const char S_subnet[]             PROGMEM = "Subnet";
-const char S_exiting[]            PROGMEM = "Exiting";
-const char S_resetting[]          PROGMEM = "Module will reset in a few seconds.";
-const char S_closing[]            PROGMEM = "You can close the page, portal will continue to run";
-const char S_error[]              PROGMEM = "An Error Occured";
-const char S_notfound[]           PROGMEM = "File Not Found\n\n";
-const char S_uri[]                PROGMEM = "URI: ";
-const char S_method[]             PROGMEM = "\nMethod: ";
-const char S_args[]               PROGMEM = "\nArguments: ";
-const char S_parampre[]           PROGMEM = "param_";
-
-// debug strings
-const char D_HR[]                 PROGMEM = "--------------------";
-
-// END WIFI_MANAGER_OVERRIDE_STRINGS
-#endif
-
-// -----------------------------------------------------------------------------------------------
-// DO NOT EDIT BELOW THIS LINE
-
-const uint8_t _nummenutokens = 11;
-const char * const _menutokens[_nummenutokens] PROGMEM = {
-    "wifi",
-    "wifinoscan",
-    "info",
-    "param",
-    "close",
-    "restart",
-    "exit",
-    "erase",
-    "update",
-    "sep",
-    "custom"
-};
-
-const char R_root[]               PROGMEM = "/";
-const char R_wifi[]               PROGMEM = "/wifi";
-const char R_wifinoscan[]         PROGMEM = "/0wifi";
-const char R_wifisave[]           PROGMEM = "/wifisave";
-const char R_info[]               PROGMEM = "/info";
-const char R_param[]              PROGMEM = "/param";
-const char R_paramsave[]          PROGMEM = "/paramsave";
-const char R_restart[]            PROGMEM = "/restart";
-const char R_exit[]               PROGMEM = "/exit";
-const char R_close[]              PROGMEM = "/close";
-const char R_erase[]              PROGMEM = "/erase";
-const char R_status[]             PROGMEM = "/status";
-const char R_update[]             PROGMEM = "/update";
-const char R_updatedone[]         PROGMEM = "/u";
-
-
-//Strings
-const char S_ip[]                 PROGMEM = "ip";
-const char S_gw[]                 PROGMEM = "gw";
-const char S_sn[]                 PROGMEM = "sn";
-const char S_dns[]                PROGMEM = "dns";
-
-// softap ssid default prefix
-#ifdef ESP8266
-	const char S_ssidpre[]        PROGMEM = "ESP";
-#elif defined(ESP32)
-	const char S_ssidpre[]        PROGMEM = "ESP32";
-#else
-	const char S_ssidpre[]        PROGMEM = "WM";
-#endif
-
-//Tokens
-//@todo consolidate and reduce
-const char T_ss[]                 PROGMEM = "{"; // token start sentinel
-const char T_es[]                 PROGMEM = "}"; // token end sentinel
-const char T_1[]                  PROGMEM = "{1}"; // @token 1
-const char T_2[]                  PROGMEM = "{2}"; // @token 2
-const char T_3[]                  PROGMEM = "{3}"; // @token 2
-const char T_v[]                  PROGMEM = "{v}"; // @token v
-const char T_V[]                  PROGMEM = "{V}"; // @token v
-const char T_I[]                  PROGMEM = "{I}"; // @token I
-const char T_i[]                  PROGMEM = "{i}"; // @token i
-const char T_n[]                  PROGMEM = "{n}"; // @token n
-const char T_p[]                  PROGMEM = "{p}"; // @token p
-const char T_t[]                  PROGMEM = "{t}"; // @token t
-const char T_l[]                  PROGMEM = "{l}"; // @token l
-const char T_c[]                  PROGMEM = "{c}"; // @token c
-const char T_e[]                  PROGMEM = "{e}"; // @token e
-const char T_q[]                  PROGMEM = "{q}"; // @token q
-const char T_r[]                  PROGMEM = "{r}"; // @token r
-const char T_R[]                  PROGMEM = "{R}"; // @token R
-const char T_h[]                  PROGMEM = "{h}"; // @token h
-
-// http
-const char HTTP_HEAD_CL[]         PROGMEM = "Content-Length";
-const char HTTP_HEAD_CT[]         PROGMEM = "text/html";
-const char HTTP_HEAD_CT2[]        PROGMEM = "text/plain";
-const char HTTP_HEAD_CORS[]       PROGMEM = "Access-Control-Allow-Origin";
-const char HTTP_HEAD_CORS_ALLOW_ALL[]  PROGMEM = "*";
-
-const char * const WIFI_STA_STATUS[] PROGMEM
-{
-  "WL_IDLE_STATUS",     // 0 STATION_IDLE
-  "WL_NO_SSID_AVAIL",   // 1 STATION_NO_AP_FOUND
-  "WL_SCAN_COMPLETED",  // 2
-  "WL_CONNECTED",       // 3 STATION_GOT_IP
-  "WL_CONNECT_FAILED",  // 4 STATION_CONNECT_FAIL, STATION_WRONG_PASSWORD(NI)
-  "WL_CONNECTION_LOST", // 5
-  "WL_DISCONNECTED",    // 6
-  "WL_STATION_WRONG_PASSWORD" // 7 KLUDGE
-};
-
-#ifdef ESP32
-const char * const AUTH_MODE_NAMES[] PROGMEM
-{
-    "OPEN",
-    "WEP",
-    "WPA_PSK",
-    "WPA2_PSK",
-    "WPA_WPA2_PSK",
-    "WPA2_ENTERPRISE",
-    "MAX"
-};
-#elif defined(ESP8266)
-const char * const AUTH_MODE_NAMES[] PROGMEM
-{
-    "",
-    "",
-    "WPA_PSK",      // 2 ENC_TYPE_TKIP
-    "",
-    "WPA2_PSK",     // 4 ENC_TYPE_CCMP
-    "WEP",          // 5 ENC_TYPE_WEP
-    "",
-    "OPEN",         //7 ENC_TYPE_NONE
-    "WPA_WPA2_PSK", // 8 ENC_TYPE_AUTO
-};
-#endif
-
-const char* const WIFI_MODES[] PROGMEM = { "NULL", "STA", "AP", "STA+AP" };
-
-
-#ifdef ESP32
-// as 2.5.2
-// typedef struct {
-//     char                  cc[3];   /**< country code string */
-//     uint8_t               schan;   /**< start channel */
-//     uint8_t               nchan;   /**< total channel number */
-//     int8_t                max_tx_power;   /**< This field is used for getting WiFi maximum transmitting power, call esp_wifi_set_max_tx_power to set the maximum transmitting power. */
-//     wifi_country_policy_t policy;  /**< country policy */
-// } wifi_country_t;
-const wifi_country_t WM_COUNTRY_US{"US",1,11,CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER,WIFI_COUNTRY_POLICY_AUTO};
-const wifi_country_t WM_COUNTRY_CN{"CN",1,13,CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER,WIFI_COUNTRY_POLICY_AUTO};
-const wifi_country_t WM_COUNTRY_JP{"JP",1,14,CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER,WIFI_COUNTRY_POLICY_AUTO};
-#elif defined(ESP8266) && !defined(WM_NOCOUNTRY)
-// typedef struct {
-//     char cc[3];               /**< country code string */
-//     uint8_t schan;            /**< start channel */
-//     uint8_t nchan;            /**< total channel number */
-//     uint8_t policy;           /**< country policy */
-// } wifi_country_t;
-const wifi_country_t WM_COUNTRY_US{"US",1,11,WIFI_COUNTRY_POLICY_AUTO};
-const wifi_country_t WM_COUNTRY_CN{"CN",1,13,WIFI_COUNTRY_POLICY_AUTO};
-const wifi_country_t WM_COUNTRY_JP{"JP",1,14,WIFI_COUNTRY_POLICY_AUTO};
-#endif
-
-
-/*
-* ESP32 WiFi Events
-
-0  SYSTEM_EVENT_WIFI_READY               < ESP32 WiFi ready
-1  SYSTEM_EVENT_SCAN_DONE                < ESP32 finish scanning AP
-2  SYSTEM_EVENT_STA_START                < ESP32 station start
-3  SYSTEM_EVENT_STA_STOP                 < ESP32 station stop
-4  SYSTEM_EVENT_STA_CONNECTED            < ESP32 station connected to AP
-5  SYSTEM_EVENT_STA_DISCONNECTED         < ESP32 station disconnected from AP
-6  SYSTEM_EVENT_STA_AUTHMODE_CHANGE      < the auth mode of AP connected by ESP32 station changed
-7  SYSTEM_EVENT_STA_GOT_IP               < ESP32 station got IP from connected AP
-8  SYSTEM_EVENT_STA_LOST_IP              < ESP32 station lost IP and the IP is reset to 0
-9  SYSTEM_EVENT_STA_WPS_ER_SUCCESS       < ESP32 station wps succeeds in enrollee mode
-10 SYSTEM_EVENT_STA_WPS_ER_FAILED        < ESP32 station wps fails in enrollee mode
-11 SYSTEM_EVENT_STA_WPS_ER_TIMEOUT       < ESP32 station wps timeout in enrollee mode
-12 SYSTEM_EVENT_STA_WPS_ER_PIN           < ESP32 station wps pin code in enrollee mode
-13 SYSTEM_EVENT_AP_START                 < ESP32 soft-AP start
-14 SYSTEM_EVENT_AP_STOP                  < ESP32 soft-AP stop
-15 SYSTEM_EVENT_AP_STACONNECTED          < a station connected to ESP32 soft-AP
-16 SYSTEM_EVENT_AP_STADISCONNECTED       < a station disconnected from ESP32 soft-AP
-17 SYSTEM_EVENT_AP_STAIPASSIGNED         < ESP32 soft-AP assign an IP to a connected station
-18 SYSTEM_EVENT_AP_PROBEREQRECVED        < Receive probe request packet in soft-AP interface
-19 SYSTEM_EVENT_GOT_IP6                  < ESP32 station or ap or ethernet interface v6IP addr is preferred
-20 SYSTEM_EVENT_ETH_START                < ESP32 ethernet start
-21 SYSTEM_EVENT_ETH_STOP                 < ESP32 ethernet stop
-22 SYSTEM_EVENT_ETH_CONNECTED            < ESP32 ethernet phy link up
-23 SYSTEM_EVENT_ETH_DISCONNECTED         < ESP32 ethernet phy link down
-24 SYSTEM_EVENT_ETH_GOT_IP               < ESP32 ethernet got IP from connected AP
-25 SYSTEM_EVENT_MAX
-
-
-typedef enum {
-    ARDUINO_EVENT_WIFI_READY = 0,
-    ARDUINO_EVENT_WIFI_SCAN_DONE,
-    ARDUINO_EVENT_WIFI_STA_START,
-    ARDUINO_EVENT_WIFI_STA_STOP,
-    ARDUINO_EVENT_WIFI_STA_CONNECTED,
-    ARDUINO_EVENT_WIFI_STA_DISCONNECTED,
-    ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE,
-    ARDUINO_EVENT_WIFI_STA_GOT_IP,
-    ARDUINO_EVENT_WIFI_STA_GOT_IP6,
-    ARDUINO_EVENT_WIFI_STA_LOST_IP,
-    ARDUINO_EVENT_WIFI_AP_START,
-    ARDUINO_EVENT_WIFI_AP_STOP,
-    ARDUINO_EVENT_WIFI_AP_STACONNECTED,
-    ARDUINO_EVENT_WIFI_AP_STADISCONNECTED,
-    ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED,
-    ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED,
-    ARDUINO_EVENT_WIFI_AP_GOT_IP6,
-    ARDUINO_EVENT_WIFI_FTM_REPORT,
-    ARDUINO_EVENT_ETH_START,
-    ARDUINO_EVENT_ETH_STOP,
-    ARDUINO_EVENT_ETH_CONNECTED,
-    ARDUINO_EVENT_ETH_DISCONNECTED,
-    ARDUINO_EVENT_ETH_GOT_IP,
-    ARDUINO_EVENT_ETH_GOT_IP6,
-    ARDUINO_EVENT_WPS_ER_SUCCESS,
-    ARDUINO_EVENT_WPS_ER_FAILED,
-    ARDUINO_EVENT_WPS_ER_TIMEOUT,
-    ARDUINO_EVENT_WPS_ER_PIN,
-    ARDUINO_EVENT_WPS_ER_PBC_OVERLAP,
-    ARDUINO_EVENT_SC_SCAN_DONE,
-    ARDUINO_EVENT_SC_FOUND_CHANNEL,
-    ARDUINO_EVENT_SC_GOT_SSID_PSWD,
-    ARDUINO_EVENT_SC_SEND_ACK_DONE,
-    ARDUINO_EVENT_PROV_INIT,
-    ARDUINO_EVENT_PROV_DEINIT,
-    ARDUINO_EVENT_PROV_START,
-    ARDUINO_EVENT_PROV_END,
-    ARDUINO_EVENT_PROV_CRED_RECV,
-    ARDUINO_EVENT_PROV_CRED_FAIL,
-    ARDUINO_EVENT_PROV_CRED_SUCCESS,
-    ARDUINO_EVENT_MAX
-} arduino_event_id_t;
-
-typedef union {
-    wifi_event_sta_scan_done_t wifi_scan_done;
-    wifi_event_sta_authmode_change_t wifi_sta_authmode_change;
-    wifi_event_sta_connected_t wifi_sta_connected;
-    wifi_event_sta_disconnected_t wifi_sta_disconnected;
-    wifi_event_sta_wps_er_pin_t wps_er_pin;
-    wifi_event_sta_wps_fail_reason_t wps_fail_reason;
-    wifi_event_ap_probe_req_rx_t wifi_ap_probereqrecved;
-    wifi_event_ap_staconnected_t wifi_ap_staconnected;
-    wifi_event_ap_stadisconnected_t wifi_ap_stadisconnected;
-    wifi_event_ftm_report_t wifi_ftm_report;
-    ip_event_ap_staipassigned_t wifi_ap_staipassigned;
-    ip_event_got_ip_t got_ip;
-    ip_event_got_ip6_t got_ip6;
-    smartconfig_event_got_ssid_pswd_t sc_got_ssid_pswd;
-    esp_eth_handle_t eth_connected;
-    wifi_sta_config_t prov_cred_recv;
-    wifi_prov_sta_fail_reason_t prov_fail_reason;
-} arduino_event_info_t;
-
-*/
-#endif
+#endif
\ No newline at end of file
diff --git a/neosensor/libraries/WiFiManager/wm_consts_en.h b/neosensor/libraries/WiFiManager/wm_consts_en.h
new file mode 100644
index 0000000000000000000000000000000000000000..dac35e46a38492a88ff8e758dba18673adf1977d
--- /dev/null
+++ b/neosensor/libraries/WiFiManager/wm_consts_en.h
@@ -0,0 +1,265 @@
+/**
+ * wm_consts.h
+ * internal const strings/tokens
+ * WiFiManager, a library for the ESP8266/Arduino platform
+ * for configuration of WiFi credentials using a Captive Portal
+ *
+ * @author Creator tzapu
+ * @author tablatronix
+ * @version 0.0.0
+ * @license MIT
+ */
+
+#ifndef _WM_CONSTS_H
+#define _WM_CONSTS_H
+
+
+// -----------------------------------------------------------------------------------------------
+// TOKENS
+
+const char WM_VERSION_STR[] PROGMEM = "v2.0.17";
+
+static const char _wifi_token[]       PROGMEM = "wifi";
+static const char _wifinoscan_token[] PROGMEM = "wifinoscan";
+static const char _info_token[]       PROGMEM = "info";
+static const char _param_token[]      PROGMEM = "param";
+static const char _close_token[]      PROGMEM = "close";
+static const char _restart_token[]    PROGMEM = "restart";
+static const char _exit_token[]       PROGMEM = "exit";
+static const char _erase_token[]      PROGMEM = "erase";
+static const char _update_token[]     PROGMEM = "update";
+static const char _sep_token[]        PROGMEM = "sep";
+static const char _custom_token[]     PROGMEM = "custom";
+static PGM_P _menutokens[] PROGMEM = {
+    _wifi_token,
+    _wifinoscan_token,
+    _info_token,
+    _param_token,
+    _close_token,
+    _restart_token,
+    _exit_token,
+    _erase_token,
+    _update_token,
+    _sep_token,
+    _custom_token
+};
+const uint8_t _nummenutokens = (sizeof(_menutokens) / sizeof(PGM_P));
+
+
+const char R_root[]               PROGMEM = "/";
+const char R_wifi[]               PROGMEM = "/wifi";
+const char R_wifinoscan[]         PROGMEM = "/0wifi";
+const char R_wifisave[]           PROGMEM = "/wifisave";
+const char R_info[]               PROGMEM = "/info";
+const char R_param[]              PROGMEM = "/param";
+const char R_paramsave[]          PROGMEM = "/paramsave";
+const char R_restart[]            PROGMEM = "/restart";
+const char R_exit[]               PROGMEM = "/exit";
+const char R_close[]              PROGMEM = "/close";
+const char R_erase[]              PROGMEM = "/erase";
+const char R_status[]             PROGMEM = "/status";
+const char R_update[]             PROGMEM = "/update";
+const char R_updatedone[]         PROGMEM = "/u";
+
+
+//Strings
+const char S_ip[]                 PROGMEM = "ip";
+const char S_gw[]                 PROGMEM = "gw";
+const char S_sn[]                 PROGMEM = "sn";
+const char S_dns[]                PROGMEM = "dns";
+
+
+
+//Tokens
+//@todo consolidate and reduce
+const char T_ss[]                 PROGMEM = "{"; // token start sentinel
+const char T_es[]                 PROGMEM = "}"; // token end sentinel
+const char T_1[]                  PROGMEM = "{1}"; // @token 1
+const char T_2[]                  PROGMEM = "{2}"; // @token 2
+const char T_3[]                  PROGMEM = "{3}"; // @token 2
+const char T_v[]                  PROGMEM = "{v}"; // @token v
+const char T_V[]                  PROGMEM = "{V}"; // @token v
+const char T_I[]                  PROGMEM = "{I}"; // @token I
+const char T_i[]                  PROGMEM = "{i}"; // @token i
+const char T_n[]                  PROGMEM = "{n}"; // @token n
+const char T_p[]                  PROGMEM = "{p}"; // @token p
+const char T_t[]                  PROGMEM = "{t}"; // @token t
+const char T_l[]                  PROGMEM = "{l}"; // @token l
+const char T_c[]                  PROGMEM = "{c}"; // @token c
+const char T_e[]                  PROGMEM = "{e}"; // @token e
+const char T_q[]                  PROGMEM = "{q}"; // @token q
+const char T_r[]                  PROGMEM = "{r}"; // @token r
+const char T_R[]                  PROGMEM = "{R}"; // @token R
+const char T_h[]                  PROGMEM = "{h}"; // @token h
+
+// http
+const char HTTP_HEAD_CL[]         PROGMEM = "Content-Length";
+const char HTTP_HEAD_CT[]         PROGMEM = "text/html";
+const char HTTP_HEAD_CT2[]        PROGMEM = "text/plain";
+const char HTTP_HEAD_CORS[]       PROGMEM = "Access-Control-Allow-Origin";
+const char HTTP_HEAD_CORS_ALLOW_ALL[]  PROGMEM = "*";
+
+const char * const WIFI_STA_STATUS[] PROGMEM
+{
+  "WL_IDLE_STATUS",     // 0 STATION_IDLE
+  "WL_NO_SSID_AVAIL",   // 1 STATION_NO_AP_FOUND
+  "WL_SCAN_COMPLETED",  // 2
+  "WL_CONNECTED",       // 3 STATION_GOT_IP
+  "WL_CONNECT_FAILED",  // 4 STATION_CONNECT_FAIL, STATION_WRONG_PASSWORD(NI)
+  "WL_CONNECTION_LOST", // 5
+  "WL_DISCONNECTED",    // 6
+  "WL_STATION_WRONG_PASSWORD" // 7 KLUDGE
+};
+
+#ifdef ESP32
+const char * const AUTH_MODE_NAMES[] PROGMEM
+{
+    "OPEN",
+    "WEP",
+    "WPA_PSK",
+    "WPA2_PSK",
+    "WPA_WPA2_PSK",
+    "WPA2_ENTERPRISE",
+    "MAX"
+};
+#elif defined(ESP8266)
+const char * const AUTH_MODE_NAMES[] PROGMEM
+{
+    "",
+    "",
+    "WPA_PSK",      // 2 ENC_TYPE_TKIP
+    "",
+    "WPA2_PSK",     // 4 ENC_TYPE_CCMP
+    "WEP",          // 5 ENC_TYPE_WEP
+    "",
+    "OPEN",         //7 ENC_TYPE_NONE
+    "WPA_WPA2_PSK", // 8 ENC_TYPE_AUTO
+};
+#endif
+
+const char* const WIFI_MODES[] PROGMEM = { "NULL", "STA", "AP", "STA+AP" };
+
+
+#ifdef ESP32
+// as 2.5.2
+// typedef struct {
+//     char                  cc[3];   /**< country code string */
+//     uint8_t               schan;   /**< start channel */
+//     uint8_t               nchan;   /**< total channel number */
+//     int8_t                max_tx_power;   /**< This field is used for getting WiFi maximum transmitting power, call esp_wifi_set_max_tx_power to set the maximum transmitting power. */
+//     wifi_country_policy_t policy;  /**< country policy */
+// } wifi_country_t;
+const wifi_country_t WM_COUNTRY_US{"US",1,11,CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER,WIFI_COUNTRY_POLICY_AUTO};
+const wifi_country_t WM_COUNTRY_CN{"CN",1,13,CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER,WIFI_COUNTRY_POLICY_AUTO};
+const wifi_country_t WM_COUNTRY_JP{"JP",1,14,CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER,WIFI_COUNTRY_POLICY_AUTO};
+#elif defined(ESP8266) && !defined(WM_NOCOUNTRY)
+// typedef struct {
+//     char cc[3];               /**< country code string */
+//     uint8_t schan;            /**< start channel */
+//     uint8_t nchan;            /**< total channel number */
+//     uint8_t policy;           /**< country policy */
+// } wifi_country_t;
+const wifi_country_t WM_COUNTRY_US{"US",1,11,WIFI_COUNTRY_POLICY_AUTO};
+const wifi_country_t WM_COUNTRY_CN{"CN",1,13,WIFI_COUNTRY_POLICY_AUTO};
+const wifi_country_t WM_COUNTRY_JP{"JP",1,14,WIFI_COUNTRY_POLICY_AUTO};
+#endif
+
+
+/*
+* ESP32 WiFi Events
+
+0  SYSTEM_EVENT_WIFI_READY               < ESP32 WiFi ready
+1  SYSTEM_EVENT_SCAN_DONE                < ESP32 finish scanning AP
+2  SYSTEM_EVENT_STA_START                < ESP32 station start
+3  SYSTEM_EVENT_STA_STOP                 < ESP32 station stop
+4  SYSTEM_EVENT_STA_CONNECTED            < ESP32 station connected to AP
+5  SYSTEM_EVENT_STA_DISCONNECTED         < ESP32 station disconnected from AP
+6  SYSTEM_EVENT_STA_AUTHMODE_CHANGE      < the auth mode of AP connected by ESP32 station changed
+7  SYSTEM_EVENT_STA_GOT_IP               < ESP32 station got IP from connected AP
+8  SYSTEM_EVENT_STA_LOST_IP              < ESP32 station lost IP and the IP is reset to 0
+9  SYSTEM_EVENT_STA_WPS_ER_SUCCESS       < ESP32 station wps succeeds in enrollee mode
+10 SYSTEM_EVENT_STA_WPS_ER_FAILED        < ESP32 station wps fails in enrollee mode
+11 SYSTEM_EVENT_STA_WPS_ER_TIMEOUT       < ESP32 station wps timeout in enrollee mode
+12 SYSTEM_EVENT_STA_WPS_ER_PIN           < ESP32 station wps pin code in enrollee mode
+13 SYSTEM_EVENT_AP_START                 < ESP32 soft-AP start
+14 SYSTEM_EVENT_AP_STOP                  < ESP32 soft-AP stop
+15 SYSTEM_EVENT_AP_STACONNECTED          < a station connected to ESP32 soft-AP
+16 SYSTEM_EVENT_AP_STADISCONNECTED       < a station disconnected from ESP32 soft-AP
+17 SYSTEM_EVENT_AP_STAIPASSIGNED         < ESP32 soft-AP assign an IP to a connected station
+18 SYSTEM_EVENT_AP_PROBEREQRECVED        < Receive probe request packet in soft-AP interface
+19 SYSTEM_EVENT_GOT_IP6                  < ESP32 station or ap or ethernet interface v6IP addr is preferred
+20 SYSTEM_EVENT_ETH_START                < ESP32 ethernet start
+21 SYSTEM_EVENT_ETH_STOP                 < ESP32 ethernet stop
+22 SYSTEM_EVENT_ETH_CONNECTED            < ESP32 ethernet phy link up
+23 SYSTEM_EVENT_ETH_DISCONNECTED         < ESP32 ethernet phy link down
+24 SYSTEM_EVENT_ETH_GOT_IP               < ESP32 ethernet got IP from connected AP
+25 SYSTEM_EVENT_MAX
+
+
+typedef enum {
+    ARDUINO_EVENT_WIFI_READY = 0,
+    ARDUINO_EVENT_WIFI_SCAN_DONE,
+    ARDUINO_EVENT_WIFI_STA_START,
+    ARDUINO_EVENT_WIFI_STA_STOP,
+    ARDUINO_EVENT_WIFI_STA_CONNECTED,
+    ARDUINO_EVENT_WIFI_STA_DISCONNECTED,
+    ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE,
+    ARDUINO_EVENT_WIFI_STA_GOT_IP,
+    ARDUINO_EVENT_WIFI_STA_GOT_IP6,
+    ARDUINO_EVENT_WIFI_STA_LOST_IP,
+    ARDUINO_EVENT_WIFI_AP_START,
+    ARDUINO_EVENT_WIFI_AP_STOP,
+    ARDUINO_EVENT_WIFI_AP_STACONNECTED,
+    ARDUINO_EVENT_WIFI_AP_STADISCONNECTED,
+    ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED,
+    ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED,
+    ARDUINO_EVENT_WIFI_AP_GOT_IP6,
+    ARDUINO_EVENT_WIFI_FTM_REPORT,
+    ARDUINO_EVENT_ETH_START,
+    ARDUINO_EVENT_ETH_STOP,
+    ARDUINO_EVENT_ETH_CONNECTED,
+    ARDUINO_EVENT_ETH_DISCONNECTED,
+    ARDUINO_EVENT_ETH_GOT_IP,
+    ARDUINO_EVENT_ETH_GOT_IP6,
+    ARDUINO_EVENT_WPS_ER_SUCCESS,
+    ARDUINO_EVENT_WPS_ER_FAILED,
+    ARDUINO_EVENT_WPS_ER_TIMEOUT,
+    ARDUINO_EVENT_WPS_ER_PIN,
+    ARDUINO_EVENT_WPS_ER_PBC_OVERLAP,
+    ARDUINO_EVENT_SC_SCAN_DONE,
+    ARDUINO_EVENT_SC_FOUND_CHANNEL,
+    ARDUINO_EVENT_SC_GOT_SSID_PSWD,
+    ARDUINO_EVENT_SC_SEND_ACK_DONE,
+    ARDUINO_EVENT_PROV_INIT,
+    ARDUINO_EVENT_PROV_DEINIT,
+    ARDUINO_EVENT_PROV_START,
+    ARDUINO_EVENT_PROV_END,
+    ARDUINO_EVENT_PROV_CRED_RECV,
+    ARDUINO_EVENT_PROV_CRED_FAIL,
+    ARDUINO_EVENT_PROV_CRED_SUCCESS,
+    ARDUINO_EVENT_MAX
+} arduino_event_id_t;
+
+typedef union {
+    wifi_event_sta_scan_done_t wifi_scan_done;
+    wifi_event_sta_authmode_change_t wifi_sta_authmode_change;
+    wifi_event_sta_connected_t wifi_sta_connected;
+    wifi_event_sta_disconnected_t wifi_sta_disconnected;
+    wifi_event_sta_wps_er_pin_t wps_er_pin;
+    wifi_event_sta_wps_fail_reason_t wps_fail_reason;
+    wifi_event_ap_probe_req_rx_t wifi_ap_probereqrecved;
+    wifi_event_ap_staconnected_t wifi_ap_staconnected;
+    wifi_event_ap_stadisconnected_t wifi_ap_stadisconnected;
+    wifi_event_ftm_report_t wifi_ftm_report;
+    ip_event_ap_staipassigned_t wifi_ap_staipassigned;
+    ip_event_got_ip_t got_ip;
+    ip_event_got_ip6_t got_ip6;
+    smartconfig_event_got_ssid_pswd_t sc_got_ssid_pswd;
+    esp_eth_handle_t eth_connected;
+    wifi_sta_config_t prov_cred_recv;
+    wifi_prov_sta_fail_reason_t prov_fail_reason;
+} arduino_event_info_t;
+
+*/
+
+#endif
\ No newline at end of file
diff --git a/neosensor/libraries/WiFiManager/wm_strings_en.h b/neosensor/libraries/WiFiManager/wm_strings_en.h
new file mode 100644
index 0000000000000000000000000000000000000000..4b531603c344716788098f27c78c1e57c4e58f75
--- /dev/null
+++ b/neosensor/libraries/WiFiManager/wm_strings_en.h
@@ -0,0 +1,275 @@
+/**
+ * wm_strings_en.h
+ * engligh strings for
+ * WiFiManager, a library for the ESP8266/Arduino platform
+ * for configuration of WiFi credentials using a Captive Portal
+ *
+ * @author Creator tzapu
+ * @author tablatronix
+ * @version 0.0.0
+ * @license MIT
+ */
+
+#ifndef _WM_STRINGS_EN_H_
+#define _WM_STRINGS_EN_H_
+
+
+#ifndef WIFI_MANAGER_OVERRIDE_STRINGS
+// !!! ABOVE WILL NOT WORK if you define in your sketch, must be build flag, if anyone one knows how to order includes to be able to do this it would be neat.. I have seen it done..
+
+// strings files must include a consts file!
+#include "wm_consts_en.h" // include constants, tokens, routes
+
+const char WM_LANGUAGE[] PROGMEM = "en-US"; // i18n lang code
+
+const char HTTP_HEAD_START[]       PROGMEM = "<!DOCTYPE html>"
+"<html lang='en'><head>"
+"<meta name='format-detection' content='telephone=no'>"
+"<meta charset='UTF-8'>"
+"<meta  name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
+"<title>{v}</title>";
+
+const char HTTP_SCRIPT[]           PROGMEM = "<script>function c(l){"
+"document.getElementById('s').value=l.getAttribute('data-ssid')||l.innerText||l.textContent;"
+"p = l.nextElementSibling.classList.contains('l');"
+"document.getElementById('p').disabled = !p;"
+"if(p)document.getElementById('p').focus();};"
+"function f() {var x = document.getElementById('p');x.type==='password'?x.type='text':x.type='password';}"
+"</script>"; // @todo add button states, disable on click , show ack , spinner etc
+
+const char HTTP_HEAD_END[]         PROGMEM = "</head><body class='{c}'><div class='wrap'>"; // {c} = _bodyclass
+// example of embedded logo, base64 encoded inline, No styling here
+// const char HTTP_ROOT_MAIN[]        PROGMEM = "<img title=' alt=' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADQElEQVRoQ+2YjW0VQQyE7Q6gAkgFkAogFUAqgFQAVACpAKiAUAFQAaECQgWECggVGH1PPrRvn3dv9/YkFOksoUhhfzwz9ngvKrc89JbnLxuA/63gpsCmwCADWwkNEji8fVNgotDM7osI/x777x5l9F6JyB8R4eeVql4P0y8yNsjM7KGIPBORp558T04A+CwiH1UVUItiUQmZ2XMReSEiAFgjAPBeVS96D+sCYGaUx4cFbLfmhSpnqnrZuqEJgJnd8cQplVLciAgX//Cf0ToIeOB9wpmloLQAwpnVmAXgdf6pwjpJIz+XNoeZQQZlODV9vhc1Tuf6owrAk/8qIhFbJH7eI3eEzsvydQEICqBEkZwiALfF70HyHPpqScPV5HFjeFu476SkRA0AzOfy4hYwstj2ZkDgaphE7m6XqnoS7Q0BOPs/sw0kDROzjdXcCMFCNwzIy0EcRcOvBACfh4k0wgOmBX4xjfmk4DKTS31hgNWIKBCI8gdzogTgjYjQWFMw+o9LzJoZ63GUmjWm2wGDc7EvDDOj/1IVMIyD9SUAL0WEhpriRlXv5je5S+U1i2N88zdPuoVkeB+ls4SyxCoP3kVm9jsjpEsBLoOBNC5U9SwpGdakFkviuFP1keblATkTENTYcxkzgxTKOI3jyDxqLkQT87pMA++H3XvJBYtsNbBN6vuXq5S737WqHkW1VgMQNXJ0RshMqbbT33sJ5kpHWymzcJjNTeJIymJZtSQd9NHQHS1vodoFoTMkfbJzpRnLzB2vi6BZAJxWaCr+62BC+jzAxVJb3dmmiLzLwZhZNPE5e880Suo2AZgB8e8idxherqUPnT3brBDTlPxO3Z66rVwIwySXugdNd+5ejhqp/+NmgIwGX3Py3QBmlEi54KlwmjkOytQ+iJrLJj23S4GkOeecg8G091no737qvRRdzE+HLALQoMTBbJgBsCj5RSWUlUVJiZ4SOljb05eLFWgoJ5oY6yTyJp62D39jDANoKKcSocPJD5dQYzlFAFZJflUArgTPZKZwLXAnHmerfJquUkKZEgyzqOb5TuDt1P3nwxobqwPocZA11m4A1mBx5IxNgRH21ti7KbAGiyNn3HoF/gJ0w05A8xclpwAAAABJRU5ErkJggg==' /><h1>{v}</h1><h3>WiFiManager</h3>";
+const char HTTP_ROOT_MAIN[]        PROGMEM = "<h1>{t}</h1><h3>{v}</h3>";
+
+const char * const HTTP_PORTAL_MENU[] PROGMEM = {
+"<form action='/wifi'    method='get'><button>Configure WiFi</button></form><br/>\n", // MENU_WIFI
+"<form action='/0wifi'   method='get'><button>Configure WiFi (No scan)</button></form><br/>\n", // MENU_WIFINOSCAN
+"<form action='/info'    method='get'><button>Info</button></form><br/>\n", // MENU_INFO
+"<form action='/param'   method='get'><button>Setup</button></form><br/>\n",//MENU_PARAM
+"<form action='/close'   method='get'><button>Close</button></form><br/>\n", // MENU_CLOSE
+"<form action='/restart' method='get'><button>Restart</button></form><br/>\n",// MENU_RESTART
+"<form action='/exit'    method='get'><button>Exit</button></form><br/>\n",  // MENU_EXIT
+"<form action='/erase'   method='get'><button class='D'>Erase</button></form><br/>\n", // MENU_ERASE
+"<form action='/update'  method='get'><button>Update</button></form><br/>\n",// MENU_UPDATE
+"<hr><br/>" // MENU_SEP
+};
+
+// const char HTTP_PORTAL_OPTIONS[]   PROGMEM = strcat(HTTP_PORTAL_MENU[0] , HTTP_PORTAL_MENU[3] , HTTP_PORTAL_MENU[7]);
+const char HTTP_PORTAL_OPTIONS[]   PROGMEM = "";
+const char HTTP_ITEM_QI[]          PROGMEM = "<div role='img' aria-label='{r}%' title='{r}%' class='q q-{q} {i} {h}'></div>"; // rssi icons
+const char HTTP_ITEM_QP[]          PROGMEM = "<div class='q {h}'>{r}%</div>"; // rssi percentage {h} = hidden showperc pref
+const char HTTP_ITEM[]             PROGMEM = "<div><a href='#p' onclick='c(this)' data-ssid='{V}'>{v}</a>{qi}{qp}</div>"; // {q} = HTTP_ITEM_QI, {r} = HTTP_ITEM_QP
+// const char HTTP_ITEM[]            PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a> {R} {r}% {q} {e}</div>"; // test all tokens
+
+const char HTTP_FORM_START[]       PROGMEM = "<form method='POST' action='{v}'>";
+const char HTTP_FORM_WIFI[]        PROGMEM = "<label for='s'>SSID</label><input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='{v}'><br/><label for='p'>Password</label><input id='p' name='p' maxlength='64' type='password' placeholder='{p}'><input type='checkbox' id='showpass' onclick='f()'> <label for='showpass'>Show Password</label><br/>";
+const char HTTP_FORM_WIFI_END[]    PROGMEM = "";
+const char HTTP_FORM_STATIC_HEAD[] PROGMEM = "<hr><br/>";
+const char HTTP_FORM_END[]         PROGMEM = "<br/><br/><button type='submit'>Save</button></form>";
+const char HTTP_FORM_LABEL[]       PROGMEM = "<label for='{i}'>{t}</label>";
+const char HTTP_FORM_PARAM_HEAD[]  PROGMEM = "<hr><br/>";
+const char HTTP_FORM_PARAM[]       PROGMEM = "<br/><input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n"; // do not remove newline!
+
+const char HTTP_SCAN_LINK[]        PROGMEM = "<br/><form action='/wifi?refresh=1' method='POST'><button name='refresh' value='1'>Refresh</button></form>";
+const char HTTP_SAVED[]            PROGMEM = "<div class='msg'>Saving Credentials<br/>Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
+const char HTTP_PARAMSAVED[]       PROGMEM = "<div class='msg S'>Saved<br/></div>";
+const char HTTP_END[]              PROGMEM = "</div></body></html>";
+const char HTTP_ERASEBTN[]         PROGMEM = "<br/><form action='/erase' method='get'><button class='D'>Erase WiFi config</button></form>";
+const char HTTP_UPDATEBTN[]        PROGMEM = "<br/><form action='/update' method='get'><button>Update</button></form>";
+const char HTTP_BACKBTN[]          PROGMEM = "<hr><br/><form action='/' method='get'><button>Back</button></form>";
+
+const char HTTP_STATUS_ON[]        PROGMEM = "<div class='msg S'><strong>Connected</strong> to {v}<br/><em><small>with IP {i}</small></em></div>";
+const char HTTP_STATUS_OFF[]       PROGMEM = "<div class='msg {c}'><strong>Not connected</strong> to {v}{r}</div>"; // {c=class} {v=ssid} {r=status_off}
+const char HTTP_STATUS_OFFPW[]     PROGMEM = "<br/>Authentication failure"; // STATION_WRONG_PASSWORD,  no eps32
+const char HTTP_STATUS_OFFNOAP[]   PROGMEM = "<br/>AP not found";   // WL_NO_SSID_AVAIL
+const char HTTP_STATUS_OFFFAIL[]   PROGMEM = "<br/>Could not connect"; // WL_CONNECT_FAILED
+const char HTTP_STATUS_NONE[]      PROGMEM = "<div class='msg'>No AP set</div>";
+const char HTTP_BR[]               PROGMEM = "<br/>";
+
+const char HTTP_STYLE[]            PROGMEM = "<style>"
+".c,body{text-align:center;font-family:verdana}div,input,select{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box}"
+"input,button,select,.msg{border-radius:.3rem;width: 100%}input[type=radio],input[type=checkbox]{width:auto}"
+"button,input[type='button'],input[type='submit']{cursor:pointer;border:0;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}"
+"input[type='file']{border:1px solid #1fa3ec}"
+".wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}"
+// links
+"a{color:#000;font-weight:700;text-decoration:none}a:hover{color:#1fa3ec;text-decoration:underline}"
+// quality icons
+".q{height:16px;margin:0;padding:0 5px;text-align:right;min-width:38px;float:right}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px;padding-right:5px}.ql .q{float:left}.q:after,.q:before{content:'';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position: 16px 0;"
+"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}"
+// icons @2x media query (32px rescaled)
+"@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {"
+"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');"
+"background-size: 95px 16px;}}"
+// msg callouts
+".msg{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-left-color:#777}.msg h4{margin-top:0;margin-bottom:5px}.msg.P{border-left-color:#1fa3ec}.msg.P h4{color:#1fa3ec}.msg.D{border-left-color:#dc3630}.msg.D h4{color:#dc3630}.msg.S{border-left-color: #5cb85c}.msg.S h4{color: #5cb85c}"
+// lists
+"dt{font-weight:bold}dd{margin:0;padding:0 0 0.5em 0;min-height:12px}"
+"td{vertical-align: top;}"
+".h{display:none}"
+"button{transition: 0s opacity;transition-delay: 3s;transition-duration: 0s;cursor: pointer}"
+"button.D{background-color:#dc3630}"
+"button:active{opacity:50% !important;cursor:wait;transition-delay: 0s}"
+// invert
+"body.invert,body.invert a,body.invert h1 {background-color:#060606;color:#fff;}"
+"body.invert .msg{color:#fff;background-color:#282828;border-top:1px solid #555;border-right:1px solid #555;border-bottom:1px solid #555;}"
+"body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}"
+":disabled {opacity: 0.5;}"
+"</style>";
+
+#ifndef WM_NOHELP
+const char HTTP_HELP[]             PROGMEM =
+ "<br/><h3>Available pages</h3><hr>"
+ "<table class='table'>"
+ "<thead><tr><th>Page</th><th>Function</th></tr></thead><tbody>"
+ "<tr><td><a href='/'>/</a></td>"
+ "<td>Menu page.</td></tr>"
+ "<tr><td><a href='/wifi'>/wifi</a></td>"
+ "<td>Show WiFi scan results and enter WiFi configuration.(/0wifi noscan)</td></tr>"
+ "<tr><td><a href='/wifisave'>/wifisave</a></td>"
+ "<td>Save WiFi configuration information and configure device. Needs variables supplied.</td></tr>"
+ "<tr><td><a href='/param'>/param</a></td>"
+ "<td>Parameter page</td></tr>"
+ "<tr><td><a href='/info'>/info</a></td>"
+ "<td>Information page</td></tr>"
+ "<tr><td><a href='/u'>/u</a></td>"
+ "<td>OTA Update</td></tr>"
+ "<tr><td><a href='/close'>/close</a></td>"
+ "<td>Close the captiveportal popup, config portal will remain active</td></tr>"
+ "<tr><td>/exit</td>"
+ "<td>Exit Config portal, config portal will close</td></tr>"
+ "<tr><td>/restart</td>"
+ "<td>Reboot the device</td></tr>"
+ "<tr><td>/erase</td>"
+ "<td>Erase WiFi configuration and reboot device. Device will not reconnect to a network until new WiFi configuration data is entered.</td></tr>"
+ "</table>"
+ "<p/>Github <a href='https://github.com/tzapu/WiFiManager'>https://github.com/tzapu/WiFiManager</a>.";
+#else
+const char HTTP_HELP[]             PROGMEM = "";
+#endif
+
+const char HTTP_UPDATE[] PROGMEM = "Upload new firmware<br/><form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>Update</button></form><small><a href='http://192.168.4.1/update' target='_blank'>* May not function inside captive portal, open in browser http://192.168.4.1</a><small>";
+const char HTTP_UPDATE_FAIL[] PROGMEM = "<div class='msg D'><strong>Update failed!</strong><Br/>Reboot device and try again</div>";
+const char HTTP_UPDATE_SUCCESS[] PROGMEM = "<div class='msg S'><strong>Update successful.  </strong> <br/> Device rebooting now...</div>";
+
+#ifdef WM_JSTEST
+const char HTTP_JS[] PROGMEM =
+"<script>function postAjax(url, data, success) {"
+"    var params = typeof data == 'string' ? data : Object.keys(data).map("
+"            function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }"
+"        ).join('&');"
+"    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject(\"Microsoft.XMLHTTP\");"
+"    xhr.open('POST', url);"
+"    xhr.onreadystatechange = function() {"
+"        if (xhr.readyState>3 && xhr.status==200) { success(xhr.responseText); }"
+"    };"
+"    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');"
+"    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');"
+"    xhr.send(params);"
+"    return xhr;}"
+"postAjax('/status', 'p1=1&p2=Hello+World', function(data){ console.log(data); });"
+"postAjax('/status', { p1: 1, p2: 'Hello World' }, function(data){ console.log(data); });"
+"</script>";
+#endif
+
+// Info html
+// @todo remove html elements from progmem, repetetive strings
+#ifdef ESP32
+	const char HTTP_INFO_esphead[]    PROGMEM = "<h3>esp32</h3><hr><dl>";
+	const char HTTP_INFO_chiprev[]    PROGMEM = "<dt>Chip rev</dt><dd>{1}</dd>";
+  	const char HTTP_INFO_lastreset[]  PROGMEM = "<dt>Last reset reason</dt><dd>CPU0: {1}<br/>CPU1: {2}</dd>";
+  	const char HTTP_INFO_aphost[]     PROGMEM = "<dt>Access point hostname</dt><dd>{1}</dd>";
+    const char HTTP_INFO_psrsize[]    PROGMEM = "<dt>PSRAM Size</dt><dd>{1} bytes</dd>";
+	const char HTTP_INFO_temp[]       PROGMEM = "<dt>Temperature</dt><dd>{1} C&deg; / {2} F&deg;</dd>";
+    const char HTTP_INFO_hall[]       PROGMEM = "<dt>Hall</dt><dd>{1}</dd>";
+#else
+	const char HTTP_INFO_esphead[]    PROGMEM = "<h3>esp8266</h3><hr><dl>";
+	const char HTTP_INFO_fchipid[]    PROGMEM = "<dt>Flash chip ID</dt><dd>{1}</dd>";
+	const char HTTP_INFO_corever[]    PROGMEM = "<dt>Core version</dt><dd>{1}</dd>";
+	const char HTTP_INFO_bootver[]    PROGMEM = "<dt>Boot version</dt><dd>{1}</dd>";
+	const char HTTP_INFO_lastreset[]  PROGMEM = "<dt>Last reset reason</dt><dd>{1}</dd>";
+	const char HTTP_INFO_flashsize[]  PROGMEM = "<dt>Real flash size</dt><dd>{1} bytes</dd>";
+#endif
+
+const char HTTP_INFO_memsmeter[]  PROGMEM = "<br/><progress value='{1}' max='{2}'></progress></dd>";
+const char HTTP_INFO_memsketch[]  PROGMEM = "<dt>Memory - Sketch size</dt><dd>Used / Total bytes<br/>{1} / {2}";
+const char HTTP_INFO_freeheap[]   PROGMEM = "<dt>Memory - Free heap</dt><dd>{1} bytes available</dd>";
+const char HTTP_INFO_wifihead[]   PROGMEM = "<br/><h3>WiFi</h3><hr>";
+const char HTTP_INFO_uptime[]     PROGMEM = "<dt>Uptime</dt><dd>{1} mins {2} secs</dd>";
+const char HTTP_INFO_chipid[]     PROGMEM = "<dt>Chip ID</dt><dd>{1}</dd>";
+const char HTTP_INFO_idesize[]    PROGMEM = "<dt>Flash size</dt><dd>{1} bytes</dd>";
+const char HTTP_INFO_sdkver[]     PROGMEM = "<dt>SDK version</dt><dd>{1}</dd>";
+const char HTTP_INFO_cpufreq[]    PROGMEM = "<dt>CPU frequency</dt><dd>{1}MHz</dd>";
+const char HTTP_INFO_apip[]       PROGMEM = "<dt>Access point IP</dt><dd>{1}</dd>";
+const char HTTP_INFO_apmac[]      PROGMEM = "<dt>Access point MAC</dt><dd>{1}</dd>";
+const char HTTP_INFO_apssid[]     PROGMEM = "<dt>Access point SSID</dt><dd>{1}</dd>";
+const char HTTP_INFO_apbssid[]    PROGMEM = "<dt>BSSID</dt><dd>{1}</dd>";
+const char HTTP_INFO_stassid[]    PROGMEM = "<dt>Station SSID</dt><dd>{1}</dd>";
+const char HTTP_INFO_staip[]      PROGMEM = "<dt>Station IP</dt><dd>{1}</dd>";
+const char HTTP_INFO_stagw[]      PROGMEM = "<dt>Station gateway</dt><dd>{1}</dd>";
+const char HTTP_INFO_stasub[]     PROGMEM = "<dt>Station subnet</dt><dd>{1}</dd>";
+const char HTTP_INFO_dnss[]       PROGMEM = "<dt>DNS Server</dt><dd>{1}</dd>";
+const char HTTP_INFO_host[]       PROGMEM = "<dt>Hostname</dt><dd>{1}</dd>";
+const char HTTP_INFO_stamac[]     PROGMEM = "<dt>Station MAC</dt><dd>{1}</dd>";
+const char HTTP_INFO_conx[]       PROGMEM = "<dt>Connected</dt><dd>{1}</dd>";
+const char HTTP_INFO_autoconx[]   PROGMEM = "<dt>Autoconnect</dt><dd>{1}</dd>";
+
+const char HTTP_INFO_aboutver[]     PROGMEM = "<dt>WiFiManager</dt><dd>{1}</dd>";
+const char HTTP_INFO_aboutarduino[] PROGMEM = "<dt>Arduino</dt><dd>{1}</dd>";
+const char HTTP_INFO_aboutsdk[]     PROGMEM = "<dt>ESP-SDK/IDF</dt><dd>{1}</dd>";
+const char HTTP_INFO_aboutdate[]    PROGMEM = "<dt>Build date</dt><dd>{1}</dd>";
+
+const char S_brand[]              PROGMEM = "WiFiManager";
+const char S_debugPrefix[]        PROGMEM = "*wm:";
+const char S_y[]                  PROGMEM = "Yes";
+const char S_n[]                  PROGMEM = "No";
+const char S_enable[]             PROGMEM = "Enabled";
+const char S_disable[]            PROGMEM = "Disabled";
+const char S_GET[]                PROGMEM = "GET";
+const char S_POST[]               PROGMEM = "POST";
+const char S_NA[]                 PROGMEM = "Unknown";
+const char S_passph[]             PROGMEM = "********";
+const char S_titlewifisaved[]     PROGMEM = "Credentials saved";
+const char S_titlewifisettings[]  PROGMEM = "Settings saved";
+const char S_titlewifi[]          PROGMEM = "Config ESP";
+const char S_titleinfo[]          PROGMEM = "Info";
+const char S_titleparam[]         PROGMEM = "Setup";
+const char S_titleparamsaved[]    PROGMEM = "Setup saved";
+const char S_titleexit[]          PROGMEM = "Exit";
+const char S_titlereset[]         PROGMEM = "Reset";
+const char S_titleerase[]         PROGMEM = "Erase";
+const char S_titleclose[]         PROGMEM = "Close";
+const char S_options[]            PROGMEM = "options";
+const char S_nonetworks[]         PROGMEM = "No networks found. Refresh to scan again.";
+const char S_staticip[]           PROGMEM = "Static IP";
+const char S_staticgw[]           PROGMEM = "Static gateway";
+const char S_staticdns[]          PROGMEM = "Static DNS";
+const char S_subnet[]             PROGMEM = "Subnet";
+const char S_exiting[]            PROGMEM = "Exiting";
+const char S_resetting[]          PROGMEM = "Module will reset in a few seconds.";
+const char S_closing[]            PROGMEM = "You can close the page, portal will continue to run";
+const char S_error[]              PROGMEM = "An error occured";
+const char S_notfound[]           PROGMEM = "File not found\n\n";
+const char S_uri[]                PROGMEM = "URI: ";
+const char S_method[]             PROGMEM = "\nMethod: ";
+const char S_args[]               PROGMEM = "\nArguments: ";
+const char S_parampre[]           PROGMEM = "param_";
+
+// debug strings
+const char D_HR[]                 PROGMEM = "--------------------";
+
+
+// softap ssid default prefix
+#ifdef ESP8266
+    const char S_ssidpre[]        PROGMEM = "ESP";
+#elif defined(ESP32)
+    const char S_ssidpre[]        PROGMEM = "ESP32";
+#else
+    const char S_ssidpre[]        PROGMEM = "WM";
+#endif
+
+// END WIFI_MANAGER_OVERRIDE_STRINGS
+#endif
+
+#endif
diff --git a/neosensor/libraries/WiFiManager/wm_strings_es.h b/neosensor/libraries/WiFiManager/wm_strings_es.h
new file mode 100644
index 0000000000000000000000000000000000000000..781d0553c04dc5887fc854a0cbce7a42ddee3e71
--- /dev/null
+++ b/neosensor/libraries/WiFiManager/wm_strings_es.h
@@ -0,0 +1,282 @@
+/**
+ * SAMPLE SAMPLE SAMPLE
+ * 
+ * wm_strings_es.h
+ * spanish strings for
+ * WiFiManager, a library for the ESPX/Arduino platform
+ * for configuration of WiFi credentials using a Captive Portal
+ *
+ * @author Creator tzapu
+ * @author tablatronix
+ * @version 0.0.0
+ * @license MIT
+ */
+
+#ifndef _WM_STRINGS_EN_H_
+#define _WM_STRINGS_EN_H_
+
+
+/**
+ * ADD TO BUILD FLAGS
+ * -DWM_STRINGS_FILE="\"wm_strings_es.h\""
+ */
+
+#ifndef WIFI_MANAGER_OVERRIDE_STRINGS
+// !!! ABOVE WILL NOT WORK if you define in your sketch, must be build flag, if anyone one knows how to order includes to be able to do this it would be neat.. I have seen it done..
+
+// strings files must include a consts file!
+// Copy and change to custom locale tokens if necessary, but strings should be good enough
+#include "wm_consts_en.h" // include constants, tokens, routes
+
+const char WM_LANGUAGE[] PROGMEM = "es-ES"; // i18n lang code
+
+const char HTTP_HEAD_START[]       PROGMEM = "<!DOCTYPE html>"
+"<html lang='en'><head>"
+"<meta name='format-detection' content='telephone=no'>"
+"<meta charset='UTF-8'>"
+"<meta  name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
+"<title>{v}</title>";
+
+const char HTTP_SCRIPT[]           PROGMEM = "<script>function c(l){"
+"document.getElementById('s').value=l.getAttribute('data-ssid')||l.innerText||l.textContent;"
+"p = l.nextElementSibling.classList.contains('l');"
+"document.getElementById('p').disabled = !p;"
+"if(p)document.getElementById('p').focus();};"
+"function f() {var x = document.getElementById('p');x.type==='password'?x.type='text':x.type='password';}"
+"</script>"; // @todo add button states, disable on click , show ack , spinner etc
+
+const char HTTP_HEAD_END[]         PROGMEM = "</head><body class='{c}'><div class='wrap'>"; // {c} = _bodyclass
+// example of embedded logo, base64 encoded inline, No styling here
+// const char HTTP_ROOT_MAIN[]        PROGMEM = "<img title=' alt=' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADQElEQVRoQ+2YjW0VQQyE7Q6gAkgFkAogFUAqgFQAVACpAKiAUAFQAaECQgWECggVGH1PPrRvn3dv9/YkFOksoUhhfzwz9ngvKrc89JbnLxuA/63gpsCmwCADWwkNEji8fVNgotDM7osI/x777x5l9F6JyB8R4eeVql4P0y8yNsjM7KGIPBORp558T04A+CwiH1UVUItiUQmZ2XMReSEiAFgjAPBeVS96D+sCYGaUx4cFbLfmhSpnqnrZuqEJgJnd8cQplVLciAgX//Cf0ToIeOB9wpmloLQAwpnVmAXgdf6pwjpJIz+XNoeZQQZlODV9vhc1Tuf6owrAk/8qIhFbJH7eI3eEzsvydQEICqBEkZwiALfF70HyHPpqScPV5HFjeFu476SkRA0AzOfy4hYwstj2ZkDgaphE7m6XqnoS7Q0BOPs/sw0kDROzjdXcCMFCNwzIy0EcRcOvBACfh4k0wgOmBX4xjfmk4DKTS31hgNWIKBCI8gdzogTgjYjQWFMw+o9LzJoZ63GUmjWm2wGDc7EvDDOj/1IVMIyD9SUAL0WEhpriRlXv5je5S+U1i2N88zdPuoVkeB+ls4SyxCoP3kVm9jsjpEsBLoOBNC5U9SwpGdakFkviuFP1keblATkTENTYcxkzgxTKOI3jyDxqLkQT87pMA++H3XvJBYtsNbBN6vuXq5S737WqHkW1VgMQNXJ0RshMqbbT33sJ5kpHWymzcJjNTeJIymJZtSQd9NHQHS1vodoFoTMkfbJzpRnLzB2vi6BZAJxWaCr+62BC+jzAxVJb3dmmiLzLwZhZNPE5e880Suo2AZgB8e8idxherqUPnT3brBDTlPxO3Z66rVwIwySXugdNd+5ejhqp/+NmgIwGX3Py3QBmlEi54KlwmjkOytQ+iJrLJj23S4GkOeecg8G091no737qvRRdzE+HLALQoMTBbJgBsCj5RSWUlUVJiZ4SOljb05eLFWgoJ5oY6yTyJp62D39jDANoKKcSocPJD5dQYzlFAFZJflUArgTPZKZwLXAnHmerfJquUkKZEgyzqOb5TuDt1P3nwxobqwPocZA11m4A1mBx5IxNgRH21ti7KbAGiyNn3HoF/gJ0w05A8xclpwAAAABJRU5ErkJggg==' /><h1>{v}</h1><h3>WiFiManager</h3>";
+const char HTTP_ROOT_MAIN[]        PROGMEM = "<h1>{t}</h1><h3>{v}</h3>";
+
+const char * const HTTP_PORTAL_MENU[] PROGMEM = {
+"<form action='/wifi'    method='get'><button>Configurar WiFi</button></form><br/>\n", // MENU_WIFI
+"<form action='/0wifi'   method='get'><button>Configurar WiFi (sin escanear)</button></form><br/>\n", // MENU_WIFINOSCAN
+"<form action='/info'    method='get'><button>Información</button></form><br/>\n", // MENU_INFO
+"<form action='/param'   method='get'><button>Configuración</button></form><br/>\n",//MENU_PARAM
+"<form action='/close'   method='get'><button>Cerca</button></form><br/>\n", // MENU_CLOSE
+"<form action='/restart' method='get'><button>Reanudar</button></form><br/>\n",// MENU_RESTART
+"<form action='/exit'    method='get'><button>Salida</button></form><br/>\n",  // MENU_EXIT
+"<form action='/erase'   method='get'><button class='D'>Borrar</button></form><br/>\n", // MENU_ERASE
+"<form action='/update'  method='get'><button>Actualizer</button></form><br/>\n",// MENU_UPDATE
+"<hr><br/>" // MENU_SEP
+};
+
+// const char HTTP_PORTAL_OPTIONS[]   PROGMEM = strcat(HTTP_PORTAL_MENU[0] , HTTP_PORTAL_MENU[3] , HTTP_PORTAL_MENU[7]);
+const char HTTP_PORTAL_OPTIONS[]   PROGMEM = "";
+const char HTTP_ITEM_QI[]          PROGMEM = "<div role='img' aria-label='{r}%' title='{r}%' class='q q-{q} {i} {h}'></div>"; // rssi icons
+const char HTTP_ITEM_QP[]          PROGMEM = "<div class='q {h}'>{r}%</div>"; // rssi percentage {h} = hidden showperc pref
+const char HTTP_ITEM[]             PROGMEM = "<div><a href='#p' onclick='c(this)' data-ssid='{V}'>{v}</a>{qi}{qp}</div>"; // {q} = HTTP_ITEM_QI, {r} = HTTP_ITEM_QP
+// const char HTTP_ITEM[]            PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a> {R} {r}% {q} {e}</div>"; // test all tokens
+
+const char HTTP_FORM_START[]       PROGMEM = "<form method='POST' action='{v}'>";
+const char HTTP_FORM_WIFI[]        PROGMEM = "<label for='s'>SSID</label><input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='{v}'><br/><label for='p'>Contraseña</label><input id='p' name='p' maxlength='64' type='password' placeholder='{p}'><input type='checkbox' onclick='f()'> Mostrar contraseña";
+const char HTTP_FORM_WIFI_END[]    PROGMEM = "";
+const char HTTP_FORM_STATIC_HEAD[] PROGMEM = "<hr><br/>";
+const char HTTP_FORM_END[]         PROGMEM = "<br/><br/><button type='submit'>Save</button></form>";
+const char HTTP_FORM_LABEL[]       PROGMEM = "<label for='{i}'>{t}</label>";
+const char HTTP_FORM_PARAM_HEAD[]  PROGMEM = "<hr><br/>";
+const char HTTP_FORM_PARAM[]       PROGMEM = "<br/><input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n"; // do not remove newline!
+
+const char HTTP_SCAN_LINK[]        PROGMEM = "<br/><form action='/wifi?refresh=1' method='POST'><button name='refresh' value='1'>Refresh</button></form>";
+const char HTTP_SAVED[]            PROGMEM = "<div class='msg'>Saving Credentials<br/>Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
+const char HTTP_PARAMSAVED[]       PROGMEM = "<div class='msg S'>Saved<br/></div>";
+const char HTTP_END[]              PROGMEM = "</div></body></html>";
+const char HTTP_ERASEBTN[]         PROGMEM = "<br/><form action='/erase' method='get'><button class='D'>Erase WiFi Config</button></form>";
+const char HTTP_UPDATEBTN[]        PROGMEM = "<br/><form action='/update' method='get'><button>Actualizer</button></form>";
+const char HTTP_BACKBTN[]          PROGMEM = "<hr><br/><form action='/' method='get'><button>Atrás</button></form>";
+
+const char HTTP_STATUS_ON[]        PROGMEM = "<div class='msg S'><strong>Conectado</strong> a {v}<br/><em><small>con IP {i}</small></em></div>";
+const char HTTP_STATUS_OFF[]       PROGMEM = "<div class='msg {c}'><strong>No conectado</strong> a {v}{r}</div>"; // {c=class} {v=ssid} {r=status_off}
+const char HTTP_STATUS_OFFPW[]     PROGMEM = "<br/>Authentication Failure"; // STATION_WRONG_PASSWORD,  no eps32
+const char HTTP_STATUS_OFFNOAP[]   PROGMEM = "<br/>No Encontrado";   // WL_NO_SSID_AVAIL
+const char HTTP_STATUS_OFFFAIL[]   PROGMEM = "<br/>No se pudo conectar"; // WL_CONNECT_FAILED
+const char HTTP_STATUS_NONE[]      PROGMEM = "<div class='msg'>Sin AP establecido</div>";
+const char HTTP_BR[]               PROGMEM = "<br/>";
+
+const char HTTP_STYLE[]            PROGMEM = "<style>"
+".c,body{text-align:center;font-family:verdana}div,input,select{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box}"
+"input,button,select,.msg{border-radius:.3rem;width: 100%}input[type=radio],input[type=checkbox]{width:auto}"
+"button,input[type='button'],input[type='submit']{cursor:pointer;border:0;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}"
+"input[type='file']{border:1px solid #1fa3ec}"
+".wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}"
+// links
+"a{color:#000;font-weight:700;text-decoration:none}a:hover{color:#1fa3ec;text-decoration:underline}"
+// quality icons
+".q{height:16px;margin:0;padding:0 5px;text-align:right;min-width:38px;float:right}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px;padding-right:5px}.ql .q{float:left}.q:after,.q:before{content:'';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position: 16px 0;"
+"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}"
+// icons @2x media query (32px rescaled)
+"@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {"
+"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');"
+"background-size: 95px 16px;}}"
+// msg callouts
+".msg{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-left-color:#777}.msg h4{margin-top:0;margin-bottom:5px}.msg.P{border-left-color:#1fa3ec}.msg.P h4{color:#1fa3ec}.msg.D{border-left-color:#dc3630}.msg.D h4{color:#dc3630}.msg.S{border-left-color: #5cb85c}.msg.S h4{color: #5cb85c}"
+// lists
+"dt{font-weight:bold}dd{margin:0;padding:0 0 0.5em 0;min-height:12px}"
+"td{vertical-align: top;}"
+".h{display:none}"
+"button{transition: 0s opacity;transition-delay: 3s;transition-duration: 0s;cursor: pointer}"
+"button.D{background-color:#dc3630}"
+"button:active{opacity:50% !important;cursor:wait;transition-delay: 0s}"
+// invert
+"body.invert,body.invert a,body.invert h1 {background-color:#060606;color:#fff;}"
+"body.invert .msg{color:#fff;background-color:#282828;border-top:1px solid #555;border-right:1px solid #555;border-bottom:1px solid #555;}"
+"body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}"
+":disabled {opacity: 0.5;}"
+"</style>";
+
+#ifndef WM_NOHELP
+const char HTTP_HELP[]             PROGMEM =
+ "<br/><h3>Available Pages</h3><hr>"
+ "<table class='table'>"
+ "<thead><tr><th>Page</th><th>Function</th></tr></thead><tbody>"
+ "<tr><td><a href='/'>/</a></td>"
+ "<td>Menu page.</td></tr>"
+ "<tr><td><a href='/wifi'>/wifi</a></td>"
+ "<td>Show WiFi scan results and enter WiFi configuration.(/0wifi noscan)</td></tr>"
+ "<tr><td><a href='/wifisave'>/wifisave</a></td>"
+ "<td>Save WiFi configuration information and configure device. Needs variables supplied.</td></tr>"
+ "<tr><td><a href='/param'>/param</a></td>"
+ "<td>Parameter page</td></tr>"
+ "<tr><td><a href='/info'>/info</a></td>"
+ "<td>Information page</td></tr>"
+ "<tr><td><a href='/u'>/u</a></td>"
+ "<td>OTA Update</td></tr>"
+ "<tr><td><a href='/close'>/close</a></td>"
+ "<td>Close the captiveportal popup,configportal will remain active</td></tr>"
+ "<tr><td>/exit</td>"
+ "<td>Exit Config Portal, configportal will close</td></tr>"
+ "<tr><td>/restart</td>"
+ "<td>Reboot the device</td></tr>"
+ "<tr><td>/erase</td>"
+ "<td>Erase WiFi configuration and reboot Device. Device will not reconnect to a network until new WiFi configuration data is entered.</td></tr>"
+ "</table>"
+ "<p/>Github <a href='https://github.com/tzapu/WiFiManager'>https://github.com/tzapu/WiFiManager</a>.";
+#else
+const char HTTP_HELP[]             PROGMEM = "";
+#endif
+
+const char HTTP_UPDATE[] PROGMEM = "Upload New Firmware<br/><form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>Update</button></form><small><a href='http://192.168.4.1/update' target='_blank'>* May not function inside captive portal, Open in browser http://192.168.4.1</a><small>";
+const char HTTP_UPDATE_FAIL[] PROGMEM = "<div class='msg D'><strong>Update Failed!</strong><Br/>Reboot device and try again</div>";
+const char HTTP_UPDATE_SUCCESS[] PROGMEM = "<div class='msg S'><strong>Update Successful.  </strong> <br/> Device Rebooting now...</div>";
+
+#ifdef WM_JSTEST
+const char HTTP_JS[] PROGMEM =
+"<script>function postAjax(url, data, success) {"
+"    var params = typeof data == 'string' ? data : Object.keys(data).map("
+"            function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }"
+"        ).join('&');"
+"    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject(\"Microsoft.XMLHTTP\");"
+"    xhr.open('POST', url);"
+"    xhr.onreadystatechange = function() {"
+"        if (xhr.readyState>3 && xhr.status==200) { success(xhr.responseText); }"
+"    };"
+"    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');"
+"    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');"
+"    xhr.send(params);"
+"    return xhr;}"
+"postAjax('/status', 'p1=1&p2=Hello+World', function(data){ console.log(data); });"
+"postAjax('/status', { p1: 1, p2: 'Hello World' }, function(data){ console.log(data); });"
+"</script>";
+#endif
+
+// Info html
+// @todo remove html elements from progmem, repetetive strings
+#ifdef ESP32
+	const char HTTP_INFO_esphead[]    PROGMEM = "<h3>esp32</h3><hr><dl>";
+	const char HTTP_INFO_chiprev[]    PROGMEM = "<dt>Chip Rev</dt><dd>{1}</dd>";
+  	const char HTTP_INFO_lastreset[]  PROGMEM = "<dt>Last reset reason</dt><dd>CPU0: {1}<br/>CPU1: {2}</dd>";
+  	const char HTTP_INFO_aphost[]     PROGMEM = "<dt>Access Point Hostname</dt><dd>{1}</dd>";
+    const char HTTP_INFO_psrsize[]    PROGMEM = "<dt>PSRAM Size</dt><dd>{1} bytes</dd>";
+	const char HTTP_INFO_temp[]       PROGMEM = "<dt>Temperature</dt><dd>{1} C&deg; / {2} F&deg;</dd><dt>Hall</dt><dd>{3}</dd>";
+#else
+	const char HTTP_INFO_esphead[]    PROGMEM = "<h3>esp8266</h3><hr><dl>";
+	const char HTTP_INFO_fchipid[]    PROGMEM = "<dt>Flash Chip ID</dt><dd>{1}</dd>";
+	const char HTTP_INFO_corever[]    PROGMEM = "<dt>Core Version</dt><dd>{1}</dd>";
+	const char HTTP_INFO_bootver[]    PROGMEM = "<dt>Boot Version</dt><dd>{1}</dd>";
+	const char HTTP_INFO_lastreset[]  PROGMEM = "<dt>Last reset reason</dt><dd>{1}</dd>";
+	const char HTTP_INFO_flashsize[]  PROGMEM = "<dt>Real Flash Size</dt><dd>{1} bytes</dd>";
+#endif
+
+const char HTTP_INFO_memsmeter[]  PROGMEM = "<br/><progress value='{1}' max='{2}'></progress></dd>";
+const char HTTP_INFO_memsketch[]  PROGMEM = "<dt>Memory - Sketch Size</dt><dd>Used / Total bytes<br/>{1} / {2}";
+const char HTTP_INFO_freeheap[]   PROGMEM = "<dt>Memory - Free Heap</dt><dd>{1} bytes available</dd>";
+const char HTTP_INFO_wifihead[]   PROGMEM = "<br/><h3>WiFi</h3><hr>";
+const char HTTP_INFO_uptime[]     PROGMEM = "<dt>Uptime</dt><dd>{1} Mins {2} Secs</dd>";
+const char HTTP_INFO_chipid[]     PROGMEM = "<dt>Chip ID</dt><dd>{1}</dd>";
+const char HTTP_INFO_idesize[]    PROGMEM = "<dt>Flash Size</dt><dd>{1} bytes</dd>";
+const char HTTP_INFO_sdkver[]     PROGMEM = "<dt>SDK Version</dt><dd>{1}</dd>";
+const char HTTP_INFO_cpufreq[]    PROGMEM = "<dt>CPU Frequency</dt><dd>{1}MHz</dd>";
+const char HTTP_INFO_apip[]       PROGMEM = "<dt>Access Point IP</dt><dd>{1}</dd>";
+const char HTTP_INFO_apmac[]      PROGMEM = "<dt>Access Point MAC</dt><dd>{1}</dd>";
+const char HTTP_INFO_apssid[]     PROGMEM = "<dt>Access Point SSID</dt><dd>{1}</dd>";
+const char HTTP_INFO_apbssid[]    PROGMEM = "<dt>BSSID</dt><dd>{1}</dd>";
+const char HTTP_INFO_stassid[]    PROGMEM = "<dt>Station SSID</dt><dd>{1}</dd>";
+const char HTTP_INFO_staip[]      PROGMEM = "<dt>Station IP</dt><dd>{1}</dd>";
+const char HTTP_INFO_stagw[]      PROGMEM = "<dt>Station Gateway</dt><dd>{1}</dd>";
+const char HTTP_INFO_stasub[]     PROGMEM = "<dt>Station Subnet</dt><dd>{1}</dd>";
+const char HTTP_INFO_dnss[]       PROGMEM = "<dt>DNS Server</dt><dd>{1}</dd>";
+const char HTTP_INFO_host[]       PROGMEM = "<dt>Hostname</dt><dd>{1}</dd>";
+const char HTTP_INFO_stamac[]     PROGMEM = "<dt>Station MAC</dt><dd>{1}</dd>";
+const char HTTP_INFO_conx[]       PROGMEM = "<dt>Connected</dt><dd>{1}</dd>";
+const char HTTP_INFO_autoconx[]   PROGMEM = "<dt>Autoconnect</dt><dd>{1}</dd>";
+
+const char HTTP_INFO_aboutver[]     PROGMEM = "<dt>WiFiManager</dt><dd>{1}</dd>";
+const char HTTP_INFO_aboutarduino[] PROGMEM = "<dt>Arduino</dt><dd>{1}</dd>";
+const char HTTP_INFO_aboutsdk[]     PROGMEM = "<dt>ESP-SDK/IDF</dt><dd>{1}</dd>";
+const char HTTP_INFO_aboutdate[]    PROGMEM = "<dt>Build Date</dt><dd>{1}</dd>";
+
+const char S_brand[]              PROGMEM = "WiFiManager";
+const char S_debugPrefix[]        PROGMEM = "*wm:";
+const char S_y[]                  PROGMEM = "Yes";
+const char S_n[]                  PROGMEM = "No";
+const char S_enable[]             PROGMEM = "Enabled";
+const char S_disable[]            PROGMEM = "Disabled";
+const char S_GET[]                PROGMEM = "GET";
+const char S_POST[]               PROGMEM = "POST";
+const char S_NA[]                 PROGMEM = "Unknown";
+const char S_passph[]             PROGMEM = "********";
+const char S_titlewifisaved[]     PROGMEM = "Credentials Saved";
+const char S_titlewifisettings[]  PROGMEM = "Settings Saved";
+const char S_titlewifi[]          PROGMEM = "Config ESP";
+const char S_titleinfo[]          PROGMEM = "Info";
+const char S_titleparam[]         PROGMEM = "Setup";
+const char S_titleparamsaved[]    PROGMEM = "Setup Saved";
+const char S_titleexit[]          PROGMEM = "Exit";
+const char S_titlereset[]         PROGMEM = "Reset";
+const char S_titleerase[]         PROGMEM = "Erase";
+const char S_titleclose[]         PROGMEM = "Close";
+const char S_options[]            PROGMEM = "options";
+const char S_nonetworks[]         PROGMEM = "No networks found. Refresh to scan again.";
+const char S_staticip[]           PROGMEM = "Static IP";
+const char S_staticgw[]           PROGMEM = "Static Gateway";
+const char S_staticdns[]          PROGMEM = "Static DNS";
+const char S_subnet[]             PROGMEM = "Subnet";
+const char S_exiting[]            PROGMEM = "Exiting";
+const char S_resetting[]          PROGMEM = "Module will reset in a few seconds.";
+const char S_closing[]            PROGMEM = "You can close the page, portal will continue to run";
+const char S_error[]              PROGMEM = "An Error Occured";
+const char S_notfound[]           PROGMEM = "File Not Found\n\n";
+const char S_uri[]                PROGMEM = "URI: ";
+const char S_method[]             PROGMEM = "\nMethod: ";
+const char S_args[]               PROGMEM = "\nArguments: ";
+const char S_parampre[]           PROGMEM = "param_";
+
+// debug strings
+const char D_HR[]                 PROGMEM = "--------------------";
+
+
+// softap ssid default prefix
+#ifdef ESP8266
+    const char S_ssidpre[]        PROGMEM = "ESP";
+#elif defined(ESP32)
+    const char S_ssidpre[]        PROGMEM = "ESP32";
+#else
+    const char S_ssidpre[]        PROGMEM = "WM";
+#endif
+
+// END WIFI_MANAGER_OVERRIDE_STRINGS
+#endif
+
+#endif
diff --git a/neosensor/libraries/boards/neosensor.h b/neosensor/libraries/boards/neosensor.h
index f3b5175f41987139f1b212a8d623566683c67f41..ff772c628eb9d1b88bba0987b37120f962848755 100644
--- a/neosensor/libraries/boards/neosensor.h
+++ b/neosensor/libraries/boards/neosensor.h
@@ -12,6 +12,7 @@
  *  - noise detector (8bits DAC MCP4706)
  * ---
  *
+ * F.Thieolt    jul.24  removed ADC calibration and resolution (now using defaults)
  * F.Thieolt    Jan.23  added option to force 802.11b WiFi protocol
  *                      in order to overcome the DHCP issue
  * F.Thiebolt   Nov.21  esp32 added serial link definition
@@ -44,7 +45,7 @@
  */
 #define BOARD_NAME          "neOSensor"
 #define BOARD_REVISION      1.2
-#define BOARD_FWREV         240306          // Firmware revision <year><month><day> in 2 digits each
+#define BOARD_FWREV         240726          // Firmware revision <year><month><day> in 2 digits each
 
 
 
@@ -198,20 +199,6 @@
   #endif
 #endif
 
-/* ADC resolution:
- * noisy ADC on ESP32 thus downgrading to 11 bits
- */
-#ifndef ADC_RESOLUTION
-#if ESP32
- #ifndef DISABLE_ADC_CAL
-  #define ADC_RESOLUTION      ADC_WIDTH_BIT_11
- #else
-  #define ADC_RESOLUTION      11
- #endif /* DISABLE_ADC_CAL */
-#elif ESP8266
-  #define ADC_RESOLUTION      10  // default on esp8266
-#endif
-#endif /* ADC_RESOLUTION */
 
 
 /* #############################################################################
diff --git a/neosensor/libraries/neocampus/neocampus_OTA.cpp b/neosensor/libraries/neocampus/neocampus_OTA.cpp
index 669315f8d2f2897d8eb6442084cd814b37f0cb28..bb9d6137e192fe08bb2493da4e305a206f46b7eb 100644
--- a/neosensor/libraries/neocampus/neocampus_OTA.cpp
+++ b/neosensor/libraries/neocampus/neocampus_OTA.cpp
@@ -7,9 +7,10 @@
  * TODO:
  * - provide a secure way for OTA to occur
  * ---
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   aug.20  removed EEPROM support (useless with ESP chips ;) )
- * Thiebolt F. Nov.19   upgrade to Arduino JSON6
- * Thiebolt F. August 17
+ * Thiebolt F.  Nov.19   upgrade to Arduino JSON6
+ * Thiebolt F.  August 17
  * 
  */
 
@@ -21,6 +22,11 @@
  */
 #include <Arduino.h>
 #include <ArduinoJson.h>          // https://github.com/bblanchon/ArduinoJson
+#if defined(ESP8266)
+  #include <ESP8266WiFi.h>
+#elif defined(ESP32)
+  #include <WiFi.h>
+#endif
 #ifdef ESP8266
   #include <ESP8266httpUpdate.h>
 #elif defined(ESP32)
@@ -47,7 +53,6 @@ extern bool _need2reboot;
  * --- Definitions ------------------------------------------------------------
  * ----------------------------------------------------------------------------
  */
-#define OTA_JSON_SIZE       (JSON_OBJECT_SIZE(10))    // no more than 10 fields in JSON structure
 
 
 
@@ -82,7 +87,7 @@ bool neOCampusOTA( void ) {
   }
 
   // get firmware revision from json and compare to current
-  StaticJsonDocument<OTA_JSON_SIZE> root;
+  JsonDocument root;
   auto err = deserializeJson( root, ota_json );
   if( err ) {
     log_error(F("\n[OTA] ERROR parsing JSON file!"));
diff --git a/neosensor/libraries/neocampus/neocampus_OTA.h b/neosensor/libraries/neocampus/neocampus_OTA.h
index 6754eb2dd8580f7490763ba9f16cf8c03b549ccd..540e15b6447b3ee660d894d35b5a5fc0178f2b6a 100644
--- a/neosensor/libraries/neocampus/neocampus_OTA.h
+++ b/neosensor/libraries/neocampus/neocampus_OTA.h
@@ -4,6 +4,7 @@
  * Over The Air (OTA) firmware upgrade utilities
  *
  * ---
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   aug.20  switched to HTTPs (i.e with ssl support) :s
  * Thiebolt F.  aug.17  initial release
  * 
diff --git a/neosensor/libraries/neocampus/neocampus_utils.cpp b/neosensor/libraries/neocampus/neocampus_utils.cpp
index 70de3c8d428f170be3448b72923cf9ed161cfb4f..ea9cf8f5d0ac65939c3b53df5e8ecbb6658487a2 100644
--- a/neosensor/libraries/neocampus/neocampus_utils.cpp
+++ b/neosensor/libraries/neocampus/neocampus_utils.cpp
@@ -430,7 +430,6 @@ const char *getMacAddress( void ) {
   static mac_str_t _mac_sta;
 
   if( _initialized == false ) {
-    //WiFi.softAPmacAddress( _mac );  // beware that AP mac addr is different from STA mac addr!
     WiFi.macAddress( _mac );
     snprintf(_mac_sta, sizeof(_mac_sta), "%02x:%02x:%02x:%02x:%02x:%02x", _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5]);
     _initialized = true;
diff --git a/neosensor/libraries/neocampus/sensocampus.cpp b/neosensor/libraries/neocampus/sensocampus.cpp
index d148f08893c0efc9bf5d8559f838d9cea701b336..0726193470148c1a76e3f8f58f0e6bf4e4b1f212 100644
--- a/neosensor/libraries/neocampus/sensocampus.cpp
+++ b/neosensor/libraries/neocampus/sensocampus.cpp
@@ -11,6 +11,7 @@
  * Notes:
  * - BASE_TOPIC is not taken from http credentials but from 1st topic of config !
  * ---
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   may.23  add NVS support to save sensOCampus given credentials
  * F.Thiebolt   aug.20  removed EEPROM support
  *                      added SPIFFS support to load/save config file
@@ -50,7 +51,6 @@
  */
 #define CONFIG_FILE             "/sensocampus.json"     // senOCampus config file contains credentials and access to MQTT(s)
                                                         // note that config of various modules is NOT saved
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(20))  // used to parse sensOCampus config FILE
 
 /* --- NVS namespace for WiFi credentials
  * Note: MQTT_PASSWD sent once by sensOCampus, others
@@ -127,13 +127,7 @@ bool senso::loadConfigFile( void ) {
   log_debug(F("\n\treading ")); log_debug(size,DEC); log_debug(F(" bytes from config file ")); log_flush();
   configFile.readBytes(buf.get(), size);
 
-  // JSON buffer allocation
-  if( size > (CONFIG_JSON_SIZE) ) {
-    log_error(F("\n[senso] config file to read is too large for JsonBuffer to handle: ")); log_error(size,DEC); log_error(F(" bytes!")); log_flush();
-    return false;
-  }
-  
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -183,7 +177,7 @@ bool senso::saveConfigFile( void ) {
   log_info(F("\n[senso] start to SAVE sensOCampus parameters in config file ...")); log_flush();
   
   // create JSON structure
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
   
   
@@ -515,7 +509,7 @@ for (JsonObject::iterator it=root.begin(); it!=root.end(); ++it) {
 bool senso::_parseCredentials( char *json ) {
   log_debug(F("\n[senso] start parsing JSON credentials ..."));
 
-  StaticJsonDocument<SENSO_JSON_SIZE> root;
+  JsonDocument root;
   
   auto err = deserializeJson( root, json );
   if( err ) {
@@ -600,21 +594,12 @@ bool senso::_parseCredentials( char *json ) {
 bool senso::_parseConfig( const char *json ) {
   log_debug(F("\n[senso] start parsing JSON config ..."));
 
-  if( !_modulesJSON.capacity() ) {
-    log_error(F("\n[senso] unable to allocated modules JSON whose size is: ")); log_error(SENSO_JSON_SIZE,DEC); log_flush();
-    return false;
-  }
-  log_debug(F("\n[senso] cleared JSON modules capacity (bytes): ")); log_debug(_modulesJSON.capacity()); log_flush();
-
   // deserialize
   auto err = deserializeJson( _modulesJSON, json );
   if( err ) {
     log_error(F("\n[senso] ERROR parsing JSON config: "));log_error(err.c_str()); log_flush();
     return false;
   }
-  log_info(F("\n[senso] JSON modules configuration use ")); log_info(_modulesJSON.memoryUsage());
-  log_info(F(" / ")); log_info(_modulesJSON.capacity()); log_info(F(" bytes available.\n"));
-  log_flush();
 #if (LOG_LEVEL >= LOG_LVL_DEBUG)
   serializeJsonPretty( _modulesJSON, Serial );
 #endif
diff --git a/neosensor/libraries/neocampus/sensocampus.h b/neosensor/libraries/neocampus/sensocampus.h
index 9bef67fba414b1fbbd38d273f1cd4bd172ad9e1b..566542eb5665182e029ef952bd40cd8313e22694 100644
--- a/neosensor/libraries/neocampus/sensocampus.h
+++ b/neosensor/libraries/neocampus/sensocampus.h
@@ -4,6 +4,7 @@
  * sensOCampus client class for interactions with sensOCampus server
  *
  * ---
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   aug.20  switched to httpS and make use of filesystem to store things
  * Thiebolt F. July 17
  * 
@@ -41,7 +42,6 @@
 #define SENSO_SUBID_MAXSIZE             16    // subID field maxsize (mac addr is 17 bytes)
 #define SENSO_HTTP_URL_MAXSIZE          128   // maximum size of a URL sent to sensOCampus
 #define SENSO_HTTP_MAX_RESPONSE_SIZE    1024  // maximum size of a response sent from sensOCampus
-#define SENSO_JSON_SIZE                 (JSON_OBJECT_SIZE(256))   // max number of objects in any sensOCampus JSON response
 
 
 
@@ -90,7 +90,7 @@ private:
     
     wifiParametersMgt *_wp;               // global wifiParametersMgt
 
-    DynamicJsonDocument _modulesJSON = DynamicJsonDocument(SENSO_JSON_SIZE);    // modules configuration
+    JsonDocument _modulesJSON;    // modules configuration
 
     // grabbed from sensocampus sever
     char _mqtt_server[MQTT_SERVER_NAME_LENGTH];
diff --git a/neosensor/libraries/neocampus/wifiParametersMgt.cpp b/neosensor/libraries/neocampus/wifiParametersMgt.cpp
index 5f0d888799f111e63e803f48b0368ef3250e7bab..384197b7f582a99f35643dea59fac72d8f505cb0 100644
--- a/neosensor/libraries/neocampus/wifiParametersMgt.cpp
+++ b/neosensor/libraries/neocampus/wifiParametersMgt.cpp
@@ -4,7 +4,8 @@
  * User's high-level parameters management class.
  * Most of those parameters come from WiFiManager parameters
  *
- * Thiebolt F. Jun.18 initial release
+ * F.Thiebolt   jul.24  switched to JsonDocument
+ * Thiebolt F.  Jun.18  initial release
  * 
  */
 
@@ -59,8 +60,6 @@ extern bool _need2reboot;
  */
 #define WIFI_CONFIG_FILE        "/wifi.json"        // WiFi configuration file to store credentials and locales confs
 
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(20))
-
 // NVS namespace for WiFi credentials
 #define WIFI_NVS_NAMESPACE      "wifiCredentials"  // 15 chars max.
 #define WIFI_NVS_SSID_KEY       "ssid"
@@ -127,14 +126,8 @@ bool wifiParametersMgt::loadConfigFile( void ) {
   std::unique_ptr<char[]> buf(new char[size]);
   log_debug(F("\n\treading ")); log_debug(size,DEC); log_debug(F(" bytes from config file ")); log_flush();
   configFile.readBytes(buf.get(), size);
-
-  // JSON buffer allocation
-  if( size > (CONFIG_JSON_SIZE) ) {
-    log_error(F("\n[wifiParams] config file to read is too large for JsonBuffer to handle: ")); log_error(size,DEC); log_error(F(" bytes!")); log_flush();
-    return false;
-  }
   
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -170,7 +163,7 @@ bool wifiParametersMgt::saveConfigFile( void ) {
   log_info(F("\n[wifiParams] start to SAVE wifi parameters in config file ...")); log_flush();
   
   // create JSON structure
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
   
   
diff --git a/neosensor/libraries/neocampus_drivers/lcc_sensor.cpp b/neosensor/libraries/neocampus_drivers/lcc_sensor.cpp
index c0027871621950985bd28a66ac28aa898c6934c5..0a925d609c749a9d525175de0358121a22a4b66a 100644
--- a/neosensor/libraries/neocampus_drivers/lcc_sensor.cpp
+++ b/neosensor/libraries/neocampus_drivers/lcc_sensor.cpp
@@ -8,6 +8,7 @@
 
 	@section  HISTORY
 
+    jul.24  F.Thiebolt  removed ADC calibration for esp32
     aug.20  F.Thiebolt  neOCampus integration
                         adapted for neOCampus
                         added new CalculatePPM computation proposal
@@ -20,10 +21,6 @@
  * Includes
  */
 #include <Arduino.h>
-#if defined(ESP32) && !defined(DISABLE_ADC_CAL)
-  #include <esp_adc_cal.h>
-  extern esp_adc_cal_characteristics_t *adc_chars;
-#endif /* ESP32 adcanced ADC */
 
 #include "neocampus.h"
 #include "neocampus_debug.h"
@@ -507,24 +504,10 @@ boolean lcc_sensor::readSensor_mv( uint32_t *pval ) {
   if( pval==nullptr ) return false;
 
 #if defined(ESP32)
-  #if !defined(DISABLE_ADC_CAL)
-  // advanced ADC reading
-  esp_err_t res;
-  uint8_t _retry = 3;
-  do {
-    res = esp_adc_cal_get_voltage( (adc_channel_t)digitalPinToAnalogChannel(_inputs[LCC_SENSOR_ANALOG]),
-                                  adc_chars, pval );
-    if( res!=ESP_OK ) delay(20);
-  } while( res!=ESP_OK and _retry-- );
-  return (res==ESP_OK ? true : false);
-
-  #else /* ADC_CAL is disabled */
   // regular ADC reading
   *pval = ((uint32_t)(analogRead(_inputs[LCC_SENSOR_ANALOG]))*_adc_voltageRef) / ((uint32_t)(pow(2,_adc_resolution))-1);
   return true;
 
-  #endif /* DISABLE_ADC_CAL */
-
 #elif defined(ESP8266)
   // 10bits resolution with 1.1 ref. voltage
   *pval = ((uint32_t)(analogRead(_inputs[LCC_SENSOR_ANALOG]))*_adc_voltageRef) / ((uint32_t)(pow(2,_adc_resolution))-1);
@@ -718,41 +701,6 @@ void lcc_sensor::_reset_gpio( void ) {
   }
   _cur_gain = LCC_SENSOR_GAIN_NONE;
 
-  // configure analog_input
-#if defined(ESP32)
-  #if !defined(DISABLE_ADC_CAL)
-  if( _inputs[LCC_SENSOR_ANALOG]!=INVALID_GPIO ) {
-    /* the default 11db attenuation enables analog input full range
-     * Note: unsure if it's not already done somewhere ...
-     */
-    adc1_config_channel_atten( (adc1_channel_t)digitalPinToAnalogChannel(_inputs[LCC_SENSOR_ANALOG]), ADC_ATTEN_DB_11 );
-  }
-  #else /* ADC_CAL is disabled */
-  /*
-   * regular ADC configuration, DEFAULTS are:
-   * - 8 times sampling
-   * - 11dB attenuation ==> voltage ref is 3300mv
-   * - 12 bits resolution
-   */
-  /* adc voltage ref
-   * TODO: CHANGE ME if default attenuation is not 11dB
-   */
-  _adc_voltageRef  = 3300;    // full range ADC resolution reach this voltage for a DEFAULT 11db attenuation
-
-  // adc resolution
-  _adc_resolution = ADC_RESOLUTION;
-  analogSetWidth( _adc_resolution );
-
-  #endif /* DISABLE_ADC_CAL */
-#elif defined(ESP8266)
-  /* ESP8266 defaults:
-   * - 10 bits resolution
-   * - 1100mv voltage ref
-   */
-  _adc_resolution = ADC_RESOLUTION;
-  _adc_voltageRef = 1100;
-#endif
-
   // configure gpio output
   if( _heater_gpio != INVALID_GPIO ) {
     pinMode( _heater_gpio, OUTPUT );
diff --git a/neosensor/libraries/neocampus_modules/airquality.cpp b/neosensor/libraries/neocampus_modules/airquality.cpp
index 1c24c6ae8b1e493d5c534a13b5b495603a048891..08e9fc458a915e2ad3f12ab9e9da01e99dd7476e 100644
--- a/neosensor/libraries/neocampus_modules/airquality.cpp
+++ b/neosensor/libraries/neocampus_modules/airquality.cpp
@@ -4,6 +4,7 @@
  * AirQuality module to manage all kind of air quality sensors that does not
  * fit within the existing sensOCampus classes.
  *
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   mar.22  added support for SCD4x along with its auto-detection
  * F.Thiebolt   oct.21  added support for particle meters (e.g PMS5003)
  *                      switched to data available delivery (instead of timer based)
@@ -35,10 +36,7 @@
  * Definitons
  */
 #define MQTT_MODULE_NAME        "airquality"  // used to build module's base topic
-#define DATA_JSON_SIZE          (JSON_OBJECT_SIZE(20))  // for MQTT data sending
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(3))   // for config FILE that contains: frequency
-                                                        // note: others parameters are sent from sensOCampus
-                                                        // hence not saved ;)
+
 // [oct.21] set FLOAT resolution data to send over MQTT
 // We'll consider ppm as integer (i.e not float)
 // We'll consider µg/m3 as integer (i.e not float)
@@ -278,7 +276,7 @@ bool airquality::loadConfig( void ) {
   configFile.close();
 
   // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -302,7 +300,7 @@ bool airquality::loadConfig( void ) {
 bool airquality::saveConfig( void ) {
   
   // static JSON buffer
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
@@ -324,9 +322,7 @@ boolean airquality::loadSensoConfig( senso *sp ) {
 
   boolean _sensor_added = false;
 
-  // [aug.20] ought to be >= of senso config Json ?!?!
-  //StaticJsonDocument<SENSO_JSON_SIZE> _doc;   // [aug.21] crash on esp8266 due to stack overflow!
-  DynamicJsonDocument _doc(SENSO_JSON_SIZE);  
+  JsonDocument _doc;  
   JsonArray root = _doc.to<JsonArray>();
 
   if( !sp->getModuleConf(MQTT_MODULE_NAME, root) ) {
@@ -488,7 +484,7 @@ boolean airquality::_sendValues( void ) {
 
     if( _sensor[cur_sensor]==nullptr || _sensor[cur_sensor]->getTrigger()!=true ) continue;
     
-    StaticJsonDocument<DATA_JSON_SIZE> _doc;
+    JsonDocument _doc;
     JsonObject root = _doc.to<JsonObject>();
 
     /* Retrieve data from current sensor
@@ -564,7 +560,7 @@ bool airquality::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg( root );
@@ -584,7 +580,7 @@ bool airquality::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setFrequency( (uint16_t)(*value), AIRQUALITY_MIN_FREQUENCY, AIRQUALITY_MAX_FREQUENCY );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg(root);
diff --git a/neosensor/libraries/neocampus_modules/base.cpp b/neosensor/libraries/neocampus_modules/base.cpp
index 1f5bbe725534a18df8199c58f3bd162fa81920a6..14647b3a40ba29a3ed35f961f66968861c5dda2d 100644
--- a/neosensor/libraries/neocampus_modules/base.cpp
+++ b/neosensor/libraries/neocampus_modules/base.cpp
@@ -8,6 +8,7 @@
  * - implement single TCP connexion for all MQTT messages (WARNING: requires topic parsing!)
  * 
  * ---
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   apr.21  added MQTT client settings through API (buffer_size,
  *                      socker_timeout ...)
  *                      moved to own modules their intrinsic status() description
@@ -37,7 +38,6 @@
 /*
  * Definitions
  */
-#define COMMAND_JSON_SIZE       (JSON_OBJECT_SIZE(5))
 
 
 
@@ -159,7 +159,7 @@ void base::callback(char* topic, byte* payload, unsigned int length) {
   }
 
   // does it matches our ID (i.e dest == "all" or dest == <unitID>)
-  StaticJsonDocument<COMMAND_JSON_SIZE> root;
+  JsonDocument root;
   
   auto err = deserializeJson( root, payload );
   if( err ) {
diff --git a/neosensor/libraries/neocampus_modules/device.cpp b/neosensor/libraries/neocampus_modules/device.cpp
index 373cb8cc66d01ed1cd21bb59415f0ef67358cfc3..11cfa32fe0b536747f44b550697a20bf89e148ca 100644
--- a/neosensor/libraries/neocampus_modules/device.cpp
+++ b/neosensor/libraries/neocampus_modules/device.cpp
@@ -3,6 +3,7 @@
  * 
  * Device module for high-level end-device management
  *
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   aug.21  implement correct device own status
  *                      added JsonDocument to enable global shared JSON
  * Thiebolt F.  Nov.19  migrate to Arduino Json 6
@@ -39,8 +40,6 @@ extern "C" modulesMgt modulesList;
  * Definitions
  */
 #define MQTT_MODULE_NAME        "device"  // used to build module's base topic
-#define DATA_JSON_SIZE          (JSON_OBJECT_SIZE(20))
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(3))   // config file contains: frequency
 
 
 
@@ -162,7 +161,7 @@ bool device::process( void ) {
   // update module's own status if needed
   if( _status!=deviceStatus_t::run ) _status = deviceStatus_t::run;
 
-  StaticJsonDocument<DATA_JSON_SIZE> _doc;
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   /* add items to JSON object:
@@ -276,7 +275,7 @@ bool device::_processOrder( const char *order, int *value, const char *svalue )
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg( root );
@@ -288,7 +287,7 @@ bool device::_processOrder( const char *order, int *value, const char *svalue )
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setFrequency( (uint16_t)(*value), DEVICE_MIN_FREQUENCY, DEVICE_MAX_FREQUENCY );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg( root );
@@ -303,7 +302,7 @@ bool device::_processOrder( const char *order, int *value, const char *svalue )
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       log_debug(F("\n[device] ORDER to restart application (will reboot in fact)... please wait ..."));
       _status = deviceStatus_t::reboot;
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       sendmsg( root );
@@ -317,7 +316,7 @@ bool device::_processOrder( const char *order, int *value, const char *svalue )
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       log_debug(F("\n[device] ORDER to reboot whole device ... please wait ..."));
       _status = deviceStatus_t::reboot;
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       sendmsg( root );
@@ -343,7 +342,7 @@ bool device::_processOrder( const char *order, int *value, const char *svalue )
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       log_debug(F("\n[device] ORDER for a firmware upgrade ... please wait ..."));
       _status = deviceStatus_t::upgrade;
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       sendmsg( root );
@@ -381,7 +380,7 @@ bool device::loadConfig( void ) {
   configFile.close();
 
   // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -421,7 +420,7 @@ bool device::_loadConfig( JsonObject root ) {
 bool device::saveConfig( void ) {
   
   // static JSON document
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
diff --git a/neosensor/libraries/neocampus_modules/digital.cpp b/neosensor/libraries/neocampus_modules/digital.cpp
index 50a9928d68e9b77316c592c10715421619bc261d..b304fb128d22c1f1fcd9cefe5df00f345b6e89de 100644
--- a/neosensor/libraries/neocampus_modules/digital.cpp
+++ b/neosensor/libraries/neocampus_modules/digital.cpp
@@ -420,8 +420,7 @@ bool digital::loadConfig( void ) {
   configFile.readBytes(buf.get(), size);
   configFile.close();
 
-  // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -447,7 +446,7 @@ bool digital::saveConfig( void ) {
   return false;
 #if 0
   // static JSON buffer
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  JsonDocument<CONFIG_JSON_SIZE> _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
@@ -493,9 +492,7 @@ boolean digital::loadSensoConfig( senso *sp ) {
 #endif /* 0 */
   boolean _sensor_added = false;
 
-  // [aug.20] ought to be >= of senso config Json ?!?!
-  //StaticJsonDocument<SENSO_JSON_SIZE> _doc;   // [aug.21] crash on esp8266 !
-  DynamicJsonDocument _doc(SENSO_JSON_SIZE);  
+  JsonDocument _doc;  
   JsonArray root = _doc.to<JsonArray>();
 
   if( !sp->getModuleConf(MQTT_MODULE_NAME, root) ) {
@@ -649,7 +646,7 @@ boolean digital::_sendValues( void ) {
 
     if( _gpio[i]==nullptr || not _gpio[i]->_trigger ) continue;
 
-    StaticJsonDocument<DATA_JSON_SIZE> _doc;
+    JsonDocument _doc;
     JsonObject root = _doc.to<JsonObject>();
 
     // retrieve data from current sensor
@@ -719,7 +716,7 @@ bool digital::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg( root );
diff --git a/neosensor/libraries/neocampus_modules/display.cpp b/neosensor/libraries/neocampus_modules/display.cpp
index 4397111b30c4d6c5c4bde06e0d2eab78c1a87291..4025bab1280b74225f3f7f2bb8c97bf20aedeeef 100644
--- a/neosensor/libraries/neocampus_modules/display.cpp
+++ b/neosensor/libraries/neocampus_modules/display.cpp
@@ -8,6 +8,7 @@
  * ---
  * TODO:
  * ---
+ * F.Thiebolt   jul.24  switched to JsonDocument
  * F.Thiebolt   aug.21  initial release
  * 
  */
@@ -31,8 +32,6 @@
  * Definitions
  */
 #define MQTT_MODULE_NAME        "display"               // used to build module's base topic
-#define DATA_JSON_SIZE          (JSON_OBJECT_SIZE(20))
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(3))   // config file contains: frequency
 // [nov.20] set FLOAT resolution data to send over MQTT
 // #define FLOAT_RESOLUTION        3
 
@@ -379,7 +378,7 @@ boolean display::_sendValues( void ) {
 
     if( _display[cur_display]==nullptr || _display[cur_display]->getTrigger()!=true ) continue;
 
-    StaticJsonDocument<DATA_JSON_SIZE> _doc;
+    JsonDocument _doc;
     JsonObject root = _doc.to<JsonObject>();
 
     // retrieve official value
@@ -433,7 +432,7 @@ bool display::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg(root);
@@ -453,7 +452,7 @@ bool display::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setFrequency( (uint16_t)(*value), DISPLAY_MIN_COOLDOWN, DISPLAY_MAX_COOLDOWN );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg( root );
@@ -485,7 +484,7 @@ bool display::loadConfig( void ) {
   configFile.close();
 
   // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -524,8 +523,8 @@ bool display::_loadConfig( JsonObject root ) {
  */
 bool display::saveConfig( void ) {
   
-  // static JSON buffer
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  // JSON buffer
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
diff --git a/neosensor/libraries/neocampus_modules/humidity.cpp b/neosensor/libraries/neocampus_modules/humidity.cpp
index badd125cbbb9a10123c528e1ea1de5524b82ca6e..1954675000c6de1e08f385a0fe1f3ccabfd8989f 100644
--- a/neosensor/libraries/neocampus_modules/humidity.cpp
+++ b/neosensor/libraries/neocampus_modules/humidity.cpp
@@ -10,8 +10,9 @@
  * TODO:
  * - convert all 'frequency' parameters & define into 'cooldown' ones
  * ---
- * F.Thiebolt aug.20  switched to intelligent data sending vs timer based data sending
- * Thiebolt.F may.20  initial release
+ * F.Thiebolt   jul.24  switched to JsonDocument
+ * F.Thiebolt   aug.20  switched to intelligent data sending vs timer based data sending
+ * Thiebolt.F   may.20  initial release
  * 
  */
 
@@ -34,8 +35,6 @@
  * Definitions
  */
 #define MQTT_MODULE_NAME        "humidity"  // used to build module's base topic
-#define DATA_JSON_SIZE          (JSON_OBJECT_SIZE(20))
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(3))   // config file contains: frequency
 
 
 
@@ -340,7 +339,7 @@ boolean humidity::_sendValues( void ) {
 
     if( _sensor[cur_sensor]==nullptr || _sensor[cur_sensor]->getTrigger()!=true ) continue;
 
-    StaticJsonDocument<DATA_JSON_SIZE> _doc;
+    JsonDocument _doc;
     JsonObject root = _doc.to<JsonObject>();
 
     // retrieve data from current sensor
@@ -392,7 +391,7 @@ bool humidity::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg(root);
@@ -412,7 +411,7 @@ bool humidity::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setFrequency( (uint16_t)(*value), HUMIDITY_MIN_FREQUENCY, HUMIDITY_MAX_FREQUENCY );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg( root );
@@ -443,8 +442,8 @@ bool humidity::loadConfig( void ) {
   configFile.readBytes(buf.get(), size);
   configFile.close();
 
-  // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  // allocate JSON buffer for module's config file
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -483,8 +482,8 @@ bool humidity::_loadConfig( JsonObject root ) {
  */
 bool humidity::saveConfig( void ) {
   
-  // static JSON buffer
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  // JSON buffer
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
diff --git a/neosensor/libraries/neocampus_modules/luminosity.cpp b/neosensor/libraries/neocampus_modules/luminosity.cpp
index 7f515525c94adaa96fa4ef38cddfee397f879cdb..a0a52a74839504483c5e6bee266cbee6b4ee30e5 100644
--- a/neosensor/libraries/neocampus_modules/luminosity.cpp
+++ b/neosensor/libraries/neocampus_modules/luminosity.cpp
@@ -9,10 +9,11 @@
  * TODO:
  * - convert all 'frequency' parameters & define into 'cooldown' ones
  * ---
- * F.Thiebolt aug.20  switched to intelligent data sending vs timer based data sending
- * Thiebolt.F may.20  force data sent through MQTT as an int
- * Thiebolt.F may.18  send back status upon any change settings received order 
- * Thiebolt.F jul.17  initial release
+ * F.Thiebolt   jul.24  switched to JsonDocument
+ * F.Thiebolt   aug.20  switched to intelligent data sending vs timer based data sending
+ * Thiebolt.F   may.20  force data sent through MQTT as an int
+ * Thiebolt.F   may.18  send back status upon any change settings received order 
+ * Thiebolt.F   jul.17  initial release
  * 
  */
 
@@ -35,8 +36,6 @@
  * Definitons
  */
 #define MQTT_MODULE_NAME        "luminosity"  // used to build module's base topic
-#define DATA_JSON_SIZE          (JSON_OBJECT_SIZE(20))
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(3))   // config file contains: frequency
 
 
 // constructors
@@ -324,7 +323,7 @@ boolean luminosity::_sendValues( void ) {
 
     if( _sensor[cur_sensor]==nullptr || _sensor[cur_sensor]->getTrigger()!=true ) continue;
 
-    StaticJsonDocument<DATA_JSON_SIZE> _doc;
+    JsonDocument _doc;
     JsonObject root = _doc.to<JsonObject>();
 
     // retrieve data from current sensor
@@ -377,7 +376,7 @@ bool luminosity::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg( root );
@@ -397,7 +396,7 @@ bool luminosity::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setFrequency( (uint16_t)(*value), LUMINOSITY_MIN_FREQUENCY, LUMINOSITY_MAX_FREQUENCY );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg(root);
@@ -428,8 +427,8 @@ bool luminosity::loadConfig( void ) {
   configFile.readBytes(buf.get(), size);
   configFile.close();
 
-  // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  // allocate JSON buffer for module's config file
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -469,7 +468,7 @@ bool luminosity::_loadConfig( JsonObject root ) {
 bool luminosity::saveConfig( void ) {
   
   // static JSON buffer
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
diff --git a/neosensor/libraries/neocampus_modules/modulesMgt.cpp b/neosensor/libraries/neocampus_modules/modulesMgt.cpp
index 0cfcdd311369f79c681208133761a157724d380d..69ea6740debe0634f337e68687f54049239114b0 100644
--- a/neosensor/libraries/neocampus_modules/modulesMgt.cpp
+++ b/neosensor/libraries/neocampus_modules/modulesMgt.cpp
@@ -4,9 +4,9 @@
  * Modules management class for high-level modules management
  *
  * 
- * F.Thiebolt aug.21  added support for shared JSON
- * Thiebolt F. Nov.19   cancel modules startALL if need2reboot flag is active
- * Thiebolt F. June 18
+ * F.Thiebolt   aug.21  added support for shared JSON
+ * Thiebolt F.  Nov.19   cancel modules startALL if need2reboot flag is active
+ * Thiebolt F.  June 18
  * 
  */
 
diff --git a/neosensor/libraries/neocampus_modules/noise.cpp b/neosensor/libraries/neocampus_modules/noise.cpp
index 9c8f281462ad1d0d7c8f76a13cb95e07aadee1bc..48991e20e77212feec1e5f335bd3495b0cd9beb3 100644
--- a/neosensor/libraries/neocampus_modules/noise.cpp
+++ b/neosensor/libraries/neocampus_modules/noise.cpp
@@ -3,10 +3,11 @@
  * 
  * Noise module to detect noise according to parameters
  * 
- * Thiebolt.F may.20  force value sent throught MQTT as INT (useless but just
- *                    to get coherent with others classes) 
- * Thiebolt.F may.18  send back status upon any change settings received order 
- * Thiebolt.F jul.17  initial release
+ * F.Thiebolt   jul.24  switched to JsonDocument
+ * Thiebolt.F   may.20  force value sent throught MQTT as INT (useless but just
+ *                      to get coherent with others classes) 
+ * Thiebolt.F   may.18  send back status upon any change settings received order 
+ * Thiebolt.F   jul.17  initial release
  * 
  */
 
@@ -29,8 +30,6 @@
  * Definitions
  */
 #define MQTT_MODULE_NAME        "noise"  // used to build module's base topic
-#define DATA_JSON_SIZE          (JSON_OBJECT_SIZE(20))
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(3))   // config file contains: frequency, threshold, sensitivity
 
 
 
@@ -289,7 +288,7 @@ bool noise::process( void ) {
   // check that at least one DAC is available
   if( !_dac ) return false;
   
-  StaticJsonDocument<DATA_JSON_SIZE> _doc;
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   if( noiseDetected ) {
@@ -479,7 +478,7 @@ bool noise::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg( root );
@@ -490,7 +489,7 @@ bool noise::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("acquire");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send values ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       noiseDetectedMsg( root );
       return sendmsg( root );
@@ -502,7 +501,7 @@ bool noise::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setFrequency( (uint16_t)(*value), NOISE_MIN_FREQUENCY, NOISE_MAX_FREQUENCY );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg( root );
@@ -517,7 +516,7 @@ bool noise::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setSensitivity( (uint8_t)(*value) );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg( root );
@@ -532,7 +531,7 @@ bool noise::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setThreshold( (uint16_t)(*value) );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg( root );
@@ -563,8 +562,8 @@ bool noise::loadConfig( void ) {
   configFile.readBytes(buf.get(), size);
   configFile.close();
 
-  // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  // allocate JSON buffer for module's config file
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -615,8 +614,8 @@ bool noise::_loadConfig( JsonObject root ) {
  */
 bool noise::saveConfig( void ) {
   
-  // static JSON buffer
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  // JSON buffer
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
diff --git a/neosensor/libraries/neocampus_modules/temperature.cpp b/neosensor/libraries/neocampus_modules/temperature.cpp
index b9fad5feb5377e808bd525165540525b8fe96860..9c408a935f138871c3b3b723af5b8810a7444a04 100644
--- a/neosensor/libraries/neocampus_modules/temperature.cpp
+++ b/neosensor/libraries/neocampus_modules/temperature.cpp
@@ -9,14 +9,15 @@
  * TODO:
  * - convert all 'frequency' parameters & define into 'cooldown' ones
  * ---
- * F.Thiebolt mar.22  add support for SCD4x sensor
- * F.Thiebolt aug.20  switched to intelligent data sending vs timer based data sending
- * Thiebolt.F nov.20  previous 'force data as float' didn't work! we need to
- *                    use serialized(String(1.0,6)); // 1.000000
+ * F.Thiebolt   jul.24  switched to JsonDocument
+ * F.Thiebolt   mar.22  add support for SCD4x sensor
+ * F.Thiebolt   aug.20  switched to intelligent data sending vs timer based data sending
+ * Thiebolt.F   nov.20  previous 'force data as float' didn't work! we need to
+ *                      use serialized(String(1.0,6)); // 1.000000
  * https://arduinojson.org/v6/how-to/configure-the-serialization-of-floats/
- * Thiebolt.F may.20  force data as float
- * Thiebolt.F may.18  send back status upon any change settings received order 
- * Thiebolt.F jul.17  initial release
+ * Thiebolt.F   may.20  force data as float
+ * Thiebolt.F   may.18  send back status upon any change settings received order 
+ * Thiebolt.F   jul.17  initial release
  * 
  */
 
@@ -39,8 +40,6 @@
  * Definitions
  */
 #define MQTT_MODULE_NAME        "temperature"  // used to build module's base topic
-#define DATA_JSON_SIZE          (JSON_OBJECT_SIZE(20))
-#define CONFIG_JSON_SIZE        (JSON_OBJECT_SIZE(3))   // config file contains: frequency
 // [nov.20] set FLOAT resolution of data to get sent over MQTT
 #define FLOAT_RESOLUTION        3
 
@@ -361,7 +360,7 @@ boolean temperature::_sendValues( void ) {
 
     if( _sensor[cur_sensor]==nullptr || _sensor[cur_sensor]->getTrigger()!=true ) continue;
 
-    StaticJsonDocument<DATA_JSON_SIZE> _doc;
+    JsonDocument _doc;
     JsonObject root = _doc.to<JsonObject>();
 
     // retrieve official value
@@ -415,7 +414,7 @@ bool temperature::_processOrder( const char *order, int *value ) {
     const char *_order = PSTR("status");
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       // required to send status ... so publishing while in callback :)
-      StaticJsonDocument<DATA_JSON_SIZE> _doc;
+      JsonDocument _doc;
       JsonObject root = _doc.to<JsonObject>();
       status( root );
       return sendmsg(root);
@@ -435,7 +434,7 @@ bool temperature::_processOrder( const char *order, int *value ) {
     if( strncmp_P(order, _order, strlen_P(_order))==0 ) {
       if( value ) {
         setFrequency( (uint16_t)(*value), TEMPERATURE_MIN_FREQUENCY, TEMPERATURE_MAX_FREQUENCY );
-        StaticJsonDocument<DATA_JSON_SIZE> _doc;
+        JsonDocument _doc;
         JsonObject root = _doc.to<JsonObject>();
         status( root );
         sendmsg( root );
@@ -466,8 +465,8 @@ bool temperature::loadConfig( void ) {
   configFile.readBytes(buf.get(), size);
   configFile.close();
 
-  // allocate JSON static buffer for module's config file
-  StaticJsonDocument<CONFIG_JSON_SIZE> root;
+  // allocate JSON buffer for module's config file
+  JsonDocument root;
 
   auto err = deserializeJson( root, buf.get() );
   if( err ) {
@@ -506,8 +505,8 @@ bool temperature::_loadConfig( JsonObject root ) {
  */
 bool temperature::saveConfig( void ) {
   
-  // static JSON buffer
-  StaticJsonDocument<CONFIG_JSON_SIZE> _doc;
+  // JSON buffer
+  JsonDocument _doc;
   JsonObject root = _doc.to<JsonObject>();
 
   // frequency
diff --git a/neosensor/libraries/readme-WiFiManager.txt b/neosensor/libraries/readme-WiFiManager.txt
deleted file mode 100644
index 50ebeceb956cc53b87b53908966b55e5ea9cbf6f..0000000000000000000000000000000000000000
--- a/neosensor/libraries/readme-WiFiManager.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-* [may.20] git development branch version
-
-git clone -b development https://github.com/tzapu/WiFiManager.git
-
-Then modify 'WiFimanager.h' and set _customhtml in public part of the class
-
diff --git a/neosensor/neosensor.ino b/neosensor/neosensor.ino
index 93ec61f47daf1873c07cefe9cedb4ac724cd1e2d..ffd4e3fcb4d2bc4d44d2a25528fc4e6571e1cb85 100644
--- a/neosensor/neosensor.ino
+++ b/neosensor/neosensor.ino
@@ -63,7 +63,11 @@
 
 /* As of esp8266 arduino lib >=2.4.0, time is managed via local or sntp along with TZ support :) */ 
 #include <time.h>                         // time() ctime()
-#include <sntp.h>
+#ifdef ESP8266
+  #include <sntp.h>
+#elif defined(ESP32)
+  #include <esp_sntp.h>
+#endif
 #ifdef ESP8266
   #include <coredecls.h>                  // settimeofday_cb(), tune_timeshift64()
 #endif
@@ -77,17 +81,6 @@
   }
 #endif /* ESP8266 */
 
-/*
- * ESP32 advanced ops:
- * - enhanced ADC (calibration)
- */
-#if defined(ESP32) && !defined(DISABLE_ADC_CAL)
-  #include <esp_adc_cal.h>
-  #define DEFL_ESP32_ADC_VREF       1100  // default 1100mv thay will get used ONLY if efuse vref is not set (i.e uncalibrated esp32 ---before Q1-18)
-  esp_adc_cal_value_t esp_adc_cal_src       = (esp_adc_cal_value_t)(-1);
-  esp_adc_cal_characteristics_t *adc_chars  = new esp_adc_cal_characteristics_t;
-#endif /* ESP32 adcanced ADC */
-
 
 
 /* neOCampus related includes */
@@ -227,12 +220,11 @@ time_t cbtime_cur, cbtime_prev;     // time set in callback
 
 
 /*
+ * [jul.24] switching to default JsonDocument (i.e heap allocation)
  * [aug.21] shared JSON document to allow modules to exchange data.
  * ... mainly used by display module ;)
  */
-#define MODULES_SHARED_JSON_SIZE  512
-//StaticJsonDocument<MODULES_SHARED_JSON_SIZE> sharedRoot;    // stack allocation
-DynamicJsonDocument sharedRoot(MODULES_SHARED_JSON_SIZE);   // heap allocation
+JsonDocument sharedRoot;   // heap allocation
 
 
 // --- Functions ---------------------------------------------------------------
@@ -457,6 +449,7 @@ void setupLed( uint8_t led, enum_ledmode_t led_mode ) {
 #elif defined(ESP32)
       ledcSetup(LED_CHANNEL, LED_BASE_FREQ, LED_RESOLUTION);
       ledcAttachPin( led, LED_CHANNEL);
+      // ledcAttachChannel( led, LED_BASE_FREQ, LED_RESOLUTION, LED_CHANNEL); // IDF rev 3.X
       timer_led.attach_ms(50, _ledWiFiMode, LED_CHANNEL);
 #endif
       break;
@@ -468,6 +461,7 @@ void setupLed( uint8_t led, enum_ledmode_t led_mode ) {
       analogWrite( led, 0 );  // [Dec.17] workaround for resilient analog value written to led
 #elif defined(ESP32)
       ledcDetachPin( led );
+      //ledcDetach( led ); // IDF rev 3.X
 #endif
       pinMode(led,INPUT);
       break;
@@ -704,14 +698,6 @@ void earlySetup( void ) {
 #elif defined(ESP32)
   esp_sleep_pd_config(ESP_PD_DOMAIN_MAX,ESP_PD_OPTION_ON);
 #endif
-
-#if defined(ESP32) && !defined(DISABLE_ADC_CAL)
-  // retrieve esp32 adc calibration
-  adc1_config_width( ADC_RESOLUTION );
-  // adc1_config_channel_atten(ADC1_CHANNEL_5,ADC_ATTEN_DB_11); sensor dependent !
-  esp_adc_cal_src = 
-           esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_RESOLUTION, DEFL_ESP32_ADC_VREF, adc_chars);
-#endif /* ESP32 advanced ADC */
 }
 
 
@@ -751,24 +737,6 @@ void lateSetup( void ) {
   }
 #endif /* ESP32 */
 
-#if defined(ESP32) && !defined(DISABLE_ADC_CAL)
-  // display ADC calibration method
-  if( esp_adc_cal_src != (esp_adc_cal_value_t)(-1) ) {
-    log_info(F("\n# ESP32 ADC calibration source: "));
-    switch(esp_adc_cal_src) {
-      case ESP_ADC_CAL_VAL_EFUSE_VREF:
-        log_info(F("eFuse vRef")); break;
-      case ESP_ADC_CAL_VAL_EFUSE_TP:
-        log_info(F("eFuse two points")); break;
-      case ESP_ADC_CAL_VAL_DEFAULT_VREF:
-        log_info(F("default Vref :(")); break;
-      default:
-        log_error(F("unknown ?!?!"));
-    }
-    log_flush();
-  }
-#endif /* ESP32 adcanced ADC */
-
   log_info(F("\n# max TCP listening sockets = ")); log_info(MAX_TCP_CONNECTIONS, DEC); log_flush();
 
   // display loop() delay
diff --git a/tests/WiFi_get_mac/WiFi_get_mac.ino b/tests/WiFi_get_mac/WiFi_get_mac.ino
new file mode 100644
index 0000000000000000000000000000000000000000..5dd6d3aaff6185d302d27dd2c75541af5bb64ece
--- /dev/null
+++ b/tests/WiFi_get_mac/WiFi_get_mac.ino
@@ -0,0 +1,34 @@
+#include "Arduino.h"
+#include <WiFi.h>
+
+#include <esp_wifi.h>
+
+void setup(){
+  Serial.begin(115200);
+  delay(5000);
+
+  WiFi.mode(WIFI_MODE_STA);
+  
+  // Variable to store the MAC address
+  uint8_t baseMac[6];
+  
+  // Get MAC address of the WiFi station interface
+  esp_wifi_get_mac(WIFI_IF_STA,baseMac);
+  Serial.print("Station MAC: ");
+  for (int i = 0; i < 5; i++) {
+    Serial.printf("%02X:", baseMac[i]);
+  }
+  Serial.printf("%02X\n", baseMac[5]);
+  
+  // Get the MAC address of the Wi-Fi AP interface
+  esp_wifi_get_mac(WIFI_IF_AP, baseMac);
+  Serial.print("SoftAP MAC: ");
+  for (int i = 0; i < 5; i++) {
+    Serial.printf("%02X:", baseMac[i]);
+  }
+  Serial.printf("%02X\n", baseMac[5]);
+}
+ 
+void loop() {
+  delay(1000);
+}