From 65c18eb26b46ccc7ea87539f5e393dbf4cd9b5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 13 Apr 2026 11:17:06 +0200 Subject: [PATCH] Remove the fragile and heavy json libraries from the firmware, remove JSON topics on MQTT, remove JSON Logging/Tracing, replace JSON output on webserver with simple string concatenation, since they use a fixed set of templates anyway. The Heltec solar node still uses ArduinoJSON for god knows what in its included custom library. --- bin/config-dist.yaml | 4 - src/RedirectablePrint.cpp | 18 +- src/mesh/Router.cpp | 45 - src/mesh/http/ContentHandler.cpp | 432 +++++---- src/mqtt/MQTT.cpp | 166 ---- src/mqtt/MQTT.h | 8 +- src/platform/portduino/PortduinoGlue.cpp | 52 - src/platform/portduino/PortduinoGlue.h | 36 - src/serialization/JSON.cpp | 245 ----- src/serialization/JSON.h | 73 -- src/serialization/JSONValue.cpp | 897 ------------------ src/serialization/JSONValue.h | 95 -- src/serialization/MeshPacketSerializer.cpp | 476 ---------- src/serialization/MeshPacketSerializer.h | 23 - .../MeshPacketSerializer_nRF52.cpp | 421 -------- .../ports/test_encrypted.cpp | 59 -- .../ports/test_nodeinfo.cpp | 51 - .../ports/test_position.cpp | 57 -- .../ports/test_telemetry.cpp | 528 ----------- .../ports/test_text_message.cpp | 105 -- .../ports/test_waypoint.cpp | 53 -- .../test_meshpacket_serializer/test_helpers.h | 49 - .../test_serializer.cpp | 61 -- .../nrf52840/rak4631_eth_gw/platformio.ini | 3 - 24 files changed, 260 insertions(+), 3697 deletions(-) delete mode 100644 src/serialization/JSON.cpp delete mode 100644 src/serialization/JSON.h delete mode 100644 src/serialization/JSONValue.cpp delete mode 100644 src/serialization/JSONValue.h delete mode 100644 src/serialization/MeshPacketSerializer.cpp delete mode 100644 src/serialization/MeshPacketSerializer.h delete mode 100644 src/serialization/MeshPacketSerializer_nRF52.cpp delete mode 100644 test/test_meshpacket_serializer/ports/test_encrypted.cpp delete mode 100644 test/test_meshpacket_serializer/ports/test_nodeinfo.cpp delete mode 100644 test/test_meshpacket_serializer/ports/test_position.cpp delete mode 100644 test/test_meshpacket_serializer/ports/test_telemetry.cpp delete mode 100644 test/test_meshpacket_serializer/ports/test_text_message.cpp delete mode 100644 test/test_meshpacket_serializer/ports/test_waypoint.cpp delete mode 100644 test/test_meshpacket_serializer/test_helpers.h delete mode 100644 test/test_meshpacket_serializer/test_serializer.cpp diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 38fc057a0..9bf4e5943 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -185,10 +185,6 @@ Input: Logging: LogLevel: info # debug, info, warn, error -# TraceFile: /var/log/meshtasticd.json -# JSONFile: /packets.json # File location for JSON output of decoded packets -# JSONFileRotate: 60 # Rotate JSON file every N minutes, or 0 for no rotation -# JSONFilter: position # filter for packets to save to JSON file # AsciiLogs: true # default if not specified is !isatty() on stdout Webserver: diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 9450f8990..0dcd90080 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -51,7 +51,7 @@ size_t RedirectablePrint::write(uint8_t c) size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg) { va_list copy; -#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO +#if ARCH_PORTDUINO static char printBuf[512]; #else static char printBuf[160]; @@ -285,20 +285,8 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) newFormat[len + 1] = '\0'; #if ARCH_PORTDUINO - // level trace is special, two possible ways to handle it. - if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { - if (portduino_config.traceFilename != "") { - va_list arg; - va_start(arg, format); - try { - traceFile << va_arg(arg, char *) << std::endl; - } catch (const std::ios_base::failure &e) { - } - va_end(arg); - } - if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { - return; - } + if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { + return; } if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { return; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 836cd1a22..7666e4ca4 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -19,12 +19,8 @@ #endif #include "Default.h" #if ARCH_PORTDUINO -#include "Throttle.h" #include "platform/portduino/PortduinoGlue.h" #endif -#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO -#include "serialization/MeshPacketSerializer.h" -#endif #define MAX_RX_FROMRADIO \ 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big @@ -544,36 +540,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p) } */ printPacket("decoded message", p); -#if ENABLE_JSON_LOGGING - LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str()); -#elif ARCH_PORTDUINO - if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) { - LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str()); - } else if (portduino_config.JSONFilename != "") { - if (portduino_config.JSONFileRotate != 0) { - static uint32_t fileage = 0; - - if (portduino_config.JSONFileRotate != 0 && - (fileage == 0 || !Throttle::isWithinTimespanMs(fileage, portduino_config.JSONFileRotate * 60 * 1000))) { - time_t timestamp = time(NULL); - struct tm *timeinfo; - char buffer[80]; - timeinfo = localtime(×tamp); - strftime(buffer, 80, "%Y%m%d-%H%M%S", timeinfo); - - std::string datetime(buffer); - if (JSONFile.is_open()) { - JSONFile.close(); - } - JSONFile.open(portduino_config.JSONFilename + "_" + datetime, std::ios::out | std::ios::app); - fileage = millis(); - } - } - if (portduino_config.JSONFilter == (_meshtastic_PortNum)0 || portduino_config.JSONFilter == p->decoded.portnum) { - JSONFile << MeshPacketSerializer::JsonSerialize(p, false) << std::endl; - } - } -#endif return DecodeState::DECODE_SUCCESS; } else { LOG_WARN("No suitable channel found for decoding, hash was 0x%x!", p->channel); @@ -838,17 +804,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) { -#if ENABLE_JSON_LOGGING - // Even ignored packets get logged in the trace - p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone - LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); -#elif ARCH_PORTDUINO - // Even ignored packets get logged in the trace - if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) { - p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone - LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); - } -#endif // assert(radioConfig.has_preferences); if (is_in_repeated(config.lora.ignore_incoming, p->from)) { LOG_DEBUG("Ignore msg, 0x%x is in our ignore list", p->from); diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 6b3aa4859..0290ea52d 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -11,11 +11,12 @@ #endif #include "SPILock.h" #include "power.h" -#include "serialization/JSON.h" #include #include #include #include +#include +#include #ifdef ARCH_ESP32 #include "esp_task_wdt.h" @@ -259,40 +260,95 @@ void htmlDeleteDir(const char *dirname) root.close(); } -JSONArray htmlListDir(const char *dirname, uint8_t levels) +// Escape a string into a JSON double-quoted literal. Matches the previous +// SimpleJSON StringifyString behavior (0x00-0x1F and 0x7F -> \u00xx lowercase, +// escapes " \ / \b \f \n \r \t, UTF-8 passes through unchanged). +static std::string jsonEscape(const std::string &str) +{ + std::string out = "\""; + for (size_t i = 0; i < str.size(); ++i) { + char chr = str[i]; + if (chr == '"' || chr == '\\' || chr == '/') { + out += '\\'; + out += chr; + } else if (chr == '\b') { + out += "\\b"; + } else if (chr == '\f') { + out += "\\f"; + } else if (chr == '\n') { + out += "\\n"; + } else if (chr == '\r') { + out += "\\r"; + } else if (chr == '\t') { + out += "\\t"; + } else if ((unsigned char)chr < 0x20 || chr == 0x7F) { + char buf[8]; + snprintf(buf, sizeof(buf), "\\u%04x", (unsigned char)chr); + out += buf; + } else { + out += chr; + } + } + out += "\""; + return out; +} + +// Format a numeric value the way the previous SimpleJSON serializer did +// (std::stringstream with precision 15, NaN/Inf -> "null"). +static std::string jsonNum(double v) +{ + if (std::isinf(v) || std::isnan(v)) + return "null"; + std::ostringstream ss; + ss.precision(15); + ss << v; + return ss.str(); +} + +// Build a serialized JSON array string listing files in `dirname`. +// Subdirectories recurse as nested arrays (up to `levels` deep). +std::string htmlListDir(const char *dirname, uint8_t levels) { File root = FSCom.open(dirname, FILE_O_READ); - JSONArray fileList; + std::string out = "["; + bool first = true; if (!root) { - return fileList; + out += "]"; + return out; } if (!root.isDirectory()) { - return fileList; + out += "]"; + return out; } // iterate over the file list File file = root.openNextFile(); while (file) { + std::string element; + bool haveElement = false; if (file.isDirectory() && !String(file.name()).endsWith(".")) { if (levels) { #ifdef ARCH_ESP32 - fileList.push_back(new JSONValue(htmlListDir(file.path(), levels - 1))); + element = htmlListDir(file.path(), levels - 1); #else - fileList.push_back(new JSONValue(htmlListDir(file.name(), levels - 1))); + element = htmlListDir(file.name(), levels - 1); #endif + haveElement = true; file.close(); } } else { - JSONObject thisFileMap; - thisFileMap["size"] = new JSONValue((int)file.size()); #ifdef ARCH_ESP32 String fileName = String(file.path()).substring(1); - thisFileMap["name"] = new JSONValue(fileName.c_str()); #else String fileName = String(file.name()).substring(1); - thisFileMap["name"] = new JSONValue(fileName.c_str()); #endif String tempName = String(file.name()).substring(1); + // Keys in the previous std::map were emitted in + // alphabetical order: name, nameModified, size. + element = "{"; + element += jsonEscape("name"); + element += ":"; + element += jsonEscape(fileName.c_str()); if (tempName.endsWith(".gz")) { #ifdef ARCH_ESP32 String modifiedFile = String(file.path()).substring(1); @@ -300,15 +356,30 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels) String modifiedFile = String(file.name()).substring(1); #endif modifiedFile.remove((modifiedFile.length() - 3), 3); - thisFileMap["nameModified"] = new JSONValue(modifiedFile.c_str()); + element += ","; + element += jsonEscape("nameModified"); + element += ":"; + element += jsonEscape(modifiedFile.c_str()); } - fileList.push_back(new JSONValue(thisFileMap)); + element += ","; + element += jsonEscape("size"); + element += ":"; + element += jsonNum((int)file.size()); + element += "}"; + haveElement = true; + } + if (haveElement) { + if (!first) + out += ","; + out += element; + first = false; } file.close(); file = root.openNextFile(); } root.close(); - return fileList; + out += "]"; + return out; } void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) @@ -318,28 +389,25 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) res->setHeader("Access-Control-Allow-Methods", "GET"); concurrency::LockGuard g(spiLock); - auto fileList = htmlListDir("/static", 10); + std::string fileList = htmlListDir("/static", 10); - // create json output structure - JSONObject filesystemObj; - filesystemObj["total"] = new JSONValue((int)FSCom.totalBytes()); - filesystemObj["used"] = new JSONValue((int)FSCom.usedBytes()); - filesystemObj["free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes())); + uint64_t total = FSCom.totalBytes(); + uint64_t used = FSCom.usedBytes(); - JSONObject jsonObjInner; - jsonObjInner["files"] = new JSONValue(fileList); - jsonObjInner["filesystem"] = new JSONValue(filesystemObj); + // Key order matches the previous std::map-based emission (alphabetical). + std::string out; + out.reserve(fileList.size() + 128); + out += "{\"data\":{\"files\":"; + out += fileList; + out += ",\"filesystem\":{\"free\":"; + out += jsonNum((int)(total - used)); + out += ",\"total\":"; + out += jsonNum((int)total); + out += ",\"used\":"; + out += jsonNum((int)used); + out += "}},\"status\":\"ok\"}"; - JSONObject jsonObjOuter; - jsonObjOuter["data"] = new JSONValue(jsonObjInner); - jsonObjOuter["status"] = new JSONValue("ok"); - - JSONValue *value = new JSONValue(jsonObjOuter); - - std::string jsonString = value->Stringify(); - res->print(jsonString.c_str()); - - delete value; + res->print(out.c_str()); } void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) @@ -354,27 +422,13 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) if (params->getQueryParameter("delete", paramValDelete)) { std::string pathDelete = "/" + paramValDelete; concurrency::LockGuard g(spiLock); - if (FSCom.remove(pathDelete.c_str())) { - - LOG_INFO("%s", pathDelete.c_str()); - JSONObject jsonObjOuter; - jsonObjOuter["status"] = new JSONValue("ok"); - JSONValue *value = new JSONValue(jsonObjOuter); - std::string jsonString = value->Stringify(); - res->print(jsonString.c_str()); - delete value; - return; - } else { - - LOG_INFO("%s", pathDelete.c_str()); - JSONObject jsonObjOuter; - jsonObjOuter["status"] = new JSONValue("Error"); - JSONValue *value = new JSONValue(jsonObjOuter); - std::string jsonString = value->Stringify(); - res->print(jsonString.c_str()); - delete value; - return; - } + const char *status = FSCom.remove(pathDelete.c_str()) ? "ok" : "Error"; + LOG_INFO("%s", pathDelete.c_str()); + std::string out = "{\"status\":"; + out += jsonEscape(status); + out += "}"; + res->print(out.c_str()); + return; } } @@ -615,95 +669,113 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) res->println("
");
     }
 
-    // Helper lambda to create JSON array and clean up memory properly
-    auto createJSONArrayFromLog = [](const uint32_t *logArray, int count) -> JSONValue * {
-        JSONArray tempArray;
+    auto arrayFromLog = [](const uint32_t *logArray, int count) -> std::string {
+        std::string s = "[";
         for (int i = 0; i < count; i++) {
-            tempArray.push_back(new JSONValue((int)logArray[i]));
+            if (i)
+                s += ",";
+            s += jsonNum((int)logArray[i]);
         }
-        JSONValue *result = new JSONValue(tempArray);
-        // Note: Don't delete tempArray elements here - JSONValue now owns them
-        return result;
+        s += "]";
+        return s;
     };
 
-    // data->airtime->tx_log
     uint32_t *logArray;
     logArray = airTime->airtimeReport(TX_LOG);
-    JSONValue *txLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
-
-    // data->airtime->rx_log
+    std::string txLog = arrayFromLog(logArray, airTime->getPeriodsToLog());
     logArray = airTime->airtimeReport(RX_LOG);
-    JSONValue *rxLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
-
-    // data->airtime->rx_all_log
+    std::string rxLog = arrayFromLog(logArray, airTime->getPeriodsToLog());
     logArray = airTime->airtimeReport(RX_ALL_LOG);
-    JSONValue *rxAllLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
+    std::string rxAllLog = arrayFromLog(logArray, airTime->getPeriodsToLog());
 
-    // data->airtime
-    JSONObject jsonObjAirtime;
-    jsonObjAirtime["tx_log"] = txLogJsonValue;
-    jsonObjAirtime["rx_log"] = rxLogJsonValue;
-    jsonObjAirtime["rx_all_log"] = rxAllLogJsonValue;
-    jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
-    jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
-    jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
-    jsonObjAirtime["seconds_per_period"] = new JSONValue(int(airTime->getSecondsPerPeriod()));
-    jsonObjAirtime["periods_to_log"] = new JSONValue(airTime->getPeriodsToLog());
-
-    // data->wifi
-    JSONObject jsonObjWifi;
-    jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
     String wifiIPString = WiFi.localIP().toString();
     std::string wifiIP = wifiIPString.c_str();
-    jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
 
-    // data->memory
-    JSONObject jsonObjMemory;
-    jsonObjMemory["heap_total"] = new JSONValue((int)memGet.getHeapSize());
-    jsonObjMemory["heap_free"] = new JSONValue((int)memGet.getFreeHeap());
-    jsonObjMemory["psram_total"] = new JSONValue((int)memGet.getPsramSize());
-    jsonObjMemory["psram_free"] = new JSONValue((int)memGet.getFreePsram());
     spiLock->lock();
-    jsonObjMemory["fs_total"] = new JSONValue((int)FSCom.totalBytes());
-    jsonObjMemory["fs_used"] = new JSONValue((int)FSCom.usedBytes());
-    jsonObjMemory["fs_free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes()));
+    uint64_t fsTotal = FSCom.totalBytes();
+    uint64_t fsUsed = FSCom.usedBytes();
     spiLock->unlock();
 
-    // data->power
-    JSONObject jsonObjPower;
-    jsonObjPower["battery_percent"] = new JSONValue(powerStatus->getBatteryChargePercent());
-    jsonObjPower["battery_voltage_mv"] = new JSONValue(powerStatus->getBatteryVoltageMv());
-    jsonObjPower["has_battery"] = new JSONValue(BoolToString(powerStatus->getHasBattery()));
-    jsonObjPower["has_usb"] = new JSONValue(BoolToString(powerStatus->getHasUSB()));
-    jsonObjPower["is_charging"] = new JSONValue(BoolToString(powerStatus->getIsCharging()));
+    // Emit keys in the same alphabetical order as the previous
+    // std::map-based JSON output to keep responses byte-compatible.
+    std::string out;
+    out.reserve(1024);
+    out += "{\"data\":{";
 
-    // data->device
-    JSONObject jsonObjDevice;
-    jsonObjDevice["reboot_counter"] = new JSONValue((int)myNodeInfo.reboot_count);
+    // airtime
+    out += "\"airtime\":{";
+    out += "\"channel_utilization\":";
+    out += jsonNum(airTime->channelUtilizationPercent());
+    out += ",\"periods_to_log\":";
+    out += jsonNum(airTime->getPeriodsToLog());
+    out += ",\"rx_all_log\":";
+    out += rxAllLog;
+    out += ",\"rx_log\":";
+    out += rxLog;
+    out += ",\"seconds_per_period\":";
+    out += jsonNum((int)airTime->getSecondsPerPeriod());
+    out += ",\"seconds_since_boot\":";
+    out += jsonNum((int)airTime->getSecondsSinceBoot());
+    out += ",\"tx_log\":";
+    out += txLog;
+    out += ",\"utilization_tx\":";
+    out += jsonNum(airTime->utilizationTXPercent());
+    out += "}";
 
-    // data->radio
-    JSONObject jsonObjRadio;
-    jsonObjRadio["frequency"] = new JSONValue(RadioLibInterface::instance->getFreq());
-    jsonObjRadio["lora_channel"] = new JSONValue((int)RadioLibInterface::instance->getChannelNum() + 1);
+    // device
+    out += ",\"device\":{\"reboot_counter\":";
+    out += jsonNum((int)myNodeInfo.reboot_count);
+    out += "}";
 
-    // collect data to inner data object
-    JSONObject jsonObjInner;
-    jsonObjInner["airtime"] = new JSONValue(jsonObjAirtime);
-    jsonObjInner["wifi"] = new JSONValue(jsonObjWifi);
-    jsonObjInner["memory"] = new JSONValue(jsonObjMemory);
-    jsonObjInner["power"] = new JSONValue(jsonObjPower);
-    jsonObjInner["device"] = new JSONValue(jsonObjDevice);
-    jsonObjInner["radio"] = new JSONValue(jsonObjRadio);
+    // memory
+    out += ",\"memory\":{";
+    out += "\"fs_free\":";
+    out += jsonNum((int)(fsTotal - fsUsed));
+    out += ",\"fs_total\":";
+    out += jsonNum((int)fsTotal);
+    out += ",\"fs_used\":";
+    out += jsonNum((int)fsUsed);
+    out += ",\"heap_free\":";
+    out += jsonNum((int)memGet.getFreeHeap());
+    out += ",\"heap_total\":";
+    out += jsonNum((int)memGet.getHeapSize());
+    out += ",\"psram_free\":";
+    out += jsonNum((int)memGet.getFreePsram());
+    out += ",\"psram_total\":";
+    out += jsonNum((int)memGet.getPsramSize());
+    out += "}";
 
-    // create json output structure
-    JSONObject jsonObjOuter;
-    jsonObjOuter["data"] = new JSONValue(jsonObjInner);
-    jsonObjOuter["status"] = new JSONValue("ok");
-    // serialize and write it to the stream
-    JSONValue *value = new JSONValue(jsonObjOuter);
-    std::string jsonString = value->Stringify();
-    res->print(jsonString.c_str());
-    delete value;
+    // power (has_* / is_charging were serialized as the strings "true"/"false")
+    out += ",\"power\":{";
+    out += "\"battery_percent\":";
+    out += jsonNum(powerStatus->getBatteryChargePercent());
+    out += ",\"battery_voltage_mv\":";
+    out += jsonNum(powerStatus->getBatteryVoltageMv());
+    out += ",\"has_battery\":";
+    out += jsonEscape(BoolToString(powerStatus->getHasBattery()));
+    out += ",\"has_usb\":";
+    out += jsonEscape(BoolToString(powerStatus->getHasUSB()));
+    out += ",\"is_charging\":";
+    out += jsonEscape(BoolToString(powerStatus->getIsCharging()));
+    out += "}";
+
+    // radio
+    out += ",\"radio\":{\"frequency\":";
+    out += jsonNum(RadioLibInterface::instance->getFreq());
+    out += ",\"lora_channel\":";
+    out += jsonNum((int)RadioLibInterface::instance->getChannelNum() + 1);
+    out += "}";
+
+    // wifi
+    out += ",\"wifi\":{\"ip\":";
+    out += jsonEscape(wifiIP);
+    out += ",\"rssi\":";
+    out += jsonNum(WiFi.RSSI());
+    out += "}";
+
+    out += "},\"status\":\"ok\"}";
+
+    res->print(out.c_str());
 }
 
 void handleNodes(HTTPRequest *req, HTTPResponse *res)
@@ -724,58 +796,65 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
         res->println("
");
     }
 
-    JSONArray nodesArray;
+    std::string out;
+    out.reserve(2048);
+    out += "{\"data\":{\"nodes\":[";
 
+    bool firstNode = true;
     uint32_t readIndex = 0;
     const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
     while (tempNodeInfo != NULL) {
         if (tempNodeInfo->has_user) {
-            JSONObject node;
-
             char id[16];
             snprintf(id, sizeof(id), "!%08x", tempNodeInfo->num);
-
-            node["id"] = new JSONValue(id);
-            node["snr"] = new JSONValue(tempNodeInfo->snr);
-            node["via_mqtt"] = new JSONValue(BoolToString(tempNodeInfo->via_mqtt));
-            node["last_heard"] = new JSONValue((int)tempNodeInfo->last_heard);
-            node["position"] = new JSONValue();
-
-            if (nodeDB->hasValidPosition(tempNodeInfo)) {
-                JSONObject position;
-                position["latitude"] = new JSONValue((float)tempNodeInfo->position.latitude_i * 1e-7);
-                position["longitude"] = new JSONValue((float)tempNodeInfo->position.longitude_i * 1e-7);
-                position["altitude"] = new JSONValue((int)tempNodeInfo->position.altitude);
-                node["position"] = new JSONValue(position);
-            }
-
-            node["long_name"] = new JSONValue(tempNodeInfo->user.long_name);
-            node["short_name"] = new JSONValue(tempNodeInfo->user.short_name);
             char macStr[18];
             snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", tempNodeInfo->user.macaddr[0],
                      tempNodeInfo->user.macaddr[1], tempNodeInfo->user.macaddr[2], tempNodeInfo->user.macaddr[3],
                      tempNodeInfo->user.macaddr[4], tempNodeInfo->user.macaddr[5]);
-            node["mac_address"] = new JSONValue(macStr);
-            node["hw_model"] = new JSONValue(tempNodeInfo->user.hw_model);
 
-            nodesArray.push_back(new JSONValue(node));
+            std::string position;
+            if (nodeDB->hasValidPosition(tempNodeInfo)) {
+                position = "{\"altitude\":";
+                position += jsonNum((int)tempNodeInfo->position.altitude);
+                position += ",\"latitude\":";
+                position += jsonNum((float)tempNodeInfo->position.latitude_i * 1e-7);
+                position += ",\"longitude\":";
+                position += jsonNum((float)tempNodeInfo->position.longitude_i * 1e-7);
+                position += "}";
+            } else {
+                position = "null";
+            }
+
+            if (!firstNode)
+                out += ",";
+            firstNode = false;
+
+            // Alphabetical key order matches previous std::map-based output.
+            out += "{\"hw_model\":";
+            out += jsonNum(tempNodeInfo->user.hw_model);
+            out += ",\"id\":";
+            out += jsonEscape(id);
+            out += ",\"last_heard\":";
+            out += jsonNum((int)tempNodeInfo->last_heard);
+            out += ",\"long_name\":";
+            out += jsonEscape(tempNodeInfo->user.long_name);
+            out += ",\"mac_address\":";
+            out += jsonEscape(macStr);
+            out += ",\"position\":";
+            out += position;
+            out += ",\"short_name\":";
+            out += jsonEscape(tempNodeInfo->user.short_name);
+            out += ",\"snr\":";
+            out += jsonNum(tempNodeInfo->snr);
+            out += ",\"via_mqtt\":";
+            out += jsonEscape(BoolToString(tempNodeInfo->via_mqtt));
+            out += "}";
         }
         tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
     }
 
-    // collect data to inner data object
-    JSONObject jsonObjInner;
-    jsonObjInner["nodes"] = new JSONValue(nodesArray);
-
-    // create json output structure
-    JSONObject jsonObjOuter;
-    jsonObjOuter["data"] = new JSONValue(jsonObjInner);
-    jsonObjOuter["status"] = new JSONValue("ok");
-    // serialize and write it to the stream
-    JSONValue *value = new JSONValue(jsonObjOuter);
-    std::string jsonString = value->Stringify();
-    res->print(jsonString.c_str());
-    delete value;
+    out += "]},\"status\":\"ok\"}";
+    res->print(out.c_str());
 }
 
 /*
@@ -897,20 +976,28 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
 
     int n = WiFi.scanNetworks();
 
-    // build list of network objects
-    JSONArray networkObjs;
+    std::string out = "{\"data\":[";
+    bool firstNet = true;
     if (n > 0) {
         for (int i = 0; i < n; ++i) {
             char ssidArray[50];
+            // The previous implementation pre-escaped quotes before handing
+            // the value to the JSON serializer; preserve that (byte-compatible
+            // even if it double-encodes a quote) so existing clients are not
+            // affected by this refactor.
             String ssidString = String(WiFi.SSID(i));
             ssidString.replace("\"", "\\\"");
             ssidString.toCharArray(ssidArray, 50);
 
             if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) {
-                JSONObject thisNetwork;
-                thisNetwork["ssid"] = new JSONValue(ssidArray);
-                thisNetwork["rssi"] = new JSONValue(int(WiFi.RSSI(i)));
-                networkObjs.push_back(new JSONValue(thisNetwork));
+                if (!firstNet)
+                    out += ",";
+                firstNet = false;
+                out += "{\"rssi\":";
+                out += jsonNum((int)WiFi.RSSI(i));
+                out += ",\"ssid\":";
+                out += jsonEscape(ssidArray);
+                out += "}";
             }
             // Yield some cpu cycles to IP stack.
             //   This is important in case the list is large and it takes us time to return
@@ -918,16 +1005,7 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
             yield();
         }
     }
-
-    // build output structure
-    JSONObject jsonObjOuter;
-    jsonObjOuter["data"] = new JSONValue(networkObjs);
-    jsonObjOuter["status"] = new JSONValue("ok");
-
-    // serialize and write it to the stream
-    JSONValue *value = new JSONValue(jsonObjOuter);
-    std::string jsonString = value->Stringify();
-    res->print(jsonString.c_str());
-    delete value;
+    out += "],\"status\":\"ok\"}";
+    res->print(out.c_str());
 }
 #endif
\ No newline at end of file
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index aba06c210..97ebd08ec 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -24,10 +24,6 @@
 #define ETH ETH2
 #endif // HAS_ETHERNET
 #include "Default.h"
-#if !defined(ARCH_NRF52) || NRF52_USE_JSON
-#include "serialization/JSON.h"
-#include "serialization/MeshPacketSerializer.h"
-#endif
 #include 
 #include 
 #include 
@@ -148,96 +144,6 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
         router->enqueueReceivedMessage(p.release());
 }
 
-#if !defined(ARCH_NRF52) || NRF52_USE_JSON
-// returns true if this is a valid JSON envelope which we accept on downlink
-inline bool isValidJsonEnvelope(JSONObject &json)
-{
-    // Generate node ID from nodenum for comparison
-    std::string nodeId = nodeDB->getNodeId();
-    // if "sender" is provided, avoid processing packets we uplinked
-    return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(nodeId) != 0) : true) &&
-           (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
-           (json.find("from") != json.end()) && json["from"]->IsNumber() &&
-           (json["from"]->AsNumber() == nodeDB->getNodeNum()) &&            // only accept message if the "from" is us
-           (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type
-           (json.find("payload") != json.end());                            // should have a payload
-}
-
-inline void onReceiveJson(byte *payload, size_t length)
-{
-    char payloadStr[length + 1];
-    memcpy(payloadStr, payload, length);
-    payloadStr[length] = 0; // null terminated string
-    std::unique_ptr json_value(JSON::Parse(payloadStr));
-    if (json_value == nullptr) {
-        LOG_ERROR("JSON received payload on MQTT but not a valid JSON");
-        return;
-    }
-
-    JSONObject json;
-    json = json_value->AsObject();
-
-    if (!isValidJsonEnvelope(json)) {
-        LOG_ERROR("JSON received payload on MQTT but not a valid envelope");
-        return;
-    }
-
-    // this is a valid envelope
-    if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) {
-        std::string jsonPayloadStr = json["payload"]->AsString();
-        LOG_INFO("JSON payload %s, length %u", jsonPayloadStr.c_str(), jsonPayloadStr.length());
-
-        // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh
-        meshtastic_MeshPacket *p = router->allocForSending();
-        p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
-        if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
-            (json["channel"]->AsNumber() < channels.getNumChannels()))
-            p->channel = json["channel"]->AsNumber();
-        if (json.find("to") != json.end() && json["to"]->IsNumber())
-            p->to = json["to"]->AsNumber();
-        if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
-            p->hop_limit = json["hopLimit"]->AsNumber();
-        if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
-            memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
-            p->decoded.payload.size = jsonPayloadStr.length();
-            service->sendToMesh(p, RX_SRC_LOCAL);
-        } else {
-            LOG_WARN("Received MQTT json payload too long, drop");
-        }
-    } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) {
-        // invent the "sendposition" type for a valid envelope
-        JSONObject posit;
-        posit = json["payload"]->AsObject(); // get nested JSON Position
-        meshtastic_Position pos = meshtastic_Position_init_default;
-        if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber())
-            pos.latitude_i = posit["latitude_i"]->AsNumber();
-        if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber())
-            pos.longitude_i = posit["longitude_i"]->AsNumber();
-        if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber())
-            pos.altitude = posit["altitude"]->AsNumber();
-        if (posit.find("time") != posit.end() && posit["time"]->IsNumber())
-            pos.time = posit["time"]->AsNumber();
-
-        // construct protobuf data packet using POSITION, send it to the mesh
-        meshtastic_MeshPacket *p = router->allocForSending();
-        p->decoded.portnum = meshtastic_PortNum_POSITION_APP;
-        if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
-            (json["channel"]->AsNumber() < channels.getNumChannels()))
-            p->channel = json["channel"]->AsNumber();
-        if (json.find("to") != json.end() && json["to"]->IsNumber())
-            p->to = json["to"]->AsNumber();
-        if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
-            p->hop_limit = json["hopLimit"]->AsNumber();
-        p->decoded.payload.size =
-            pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Position_msg,
-                               &pos); // make the Data protobuf from position
-        service->sendToMesh(p, RX_SRC_LOCAL);
-    } else {
-        LOG_DEBUG("JSON ignore downlink message with unsupported type");
-    }
-}
-#endif
-
 /// Determines if the given IPAddress is a private IPv4 address, i.e. not routable on the public internet.
 bool isPrivateIpAddress(const IPAddress &ip)
 {
@@ -382,26 +288,6 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
         return;
     }
 
-    // check if this is a json payload message by comparing the topic start
-    if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) {
-#if !defined(ARCH_NRF52) || NRF52_USE_JSON
-        // parse the channel name from the topic string
-        // the topic has been checked above for having jsonTopic prefix, so just move past it
-        char *channelName = topic + jsonTopic.length();
-        // if another "/" was added, parse string up to that character
-        channelName = strtok(channelName, "/") ? strtok(channelName, "/") : channelName;
-        // We allow downlink JSON packets only on a channel named "mqtt"
-        const meshtastic_Channel &sendChannel = channels.getByName(channelName);
-        if (!(strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 &&
-              sendChannel.settings.downlink_enabled)) {
-            LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled");
-            return;
-        }
-        onReceiveJson(payload, length);
-#endif
-        return;
-    }
-
     onReceiveProto(topic, payload, length);
 }
 
@@ -426,12 +312,10 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
 
         if (*moduleConfig.mqtt.root) {
             cryptTopic = moduleConfig.mqtt.root + cryptTopic;
-            jsonTopic = moduleConfig.mqtt.root + jsonTopic;
             mapTopic = moduleConfig.mqtt.root + mapTopic;
             isConfiguredForDefaultRootTopic = isDefaultRootTopic(moduleConfig.mqtt.root);
         } else {
             cryptTopic = "msh" + cryptTopic;
-            jsonTopic = "msh" + jsonTopic;
             mapTopic = "msh" + mapTopic;
             isConfiguredForDefaultRootTopic = true;
         }
@@ -582,14 +466,6 @@ void MQTT::sendSubscriptions()
             std::string topic = cryptTopic + channels.getGlobalId(i) + "/+";
             LOG_INFO("Subscribe to %s", topic.c_str());
             pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right?
-#if !defined(ARCH_NRF52) ||                                                                                                      \
-    defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ###
-            if (moduleConfig.mqtt.json_enabled == true) {
-                std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+";
-                LOG_INFO("Subscribe to %s", topicDecoded.c_str());
-                pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right?
-            }
-#endif // ARCH_NRF52 NRF52_USE_JSON
         }
     }
 #if !MESHTASTIC_EXCLUDE_PKI
@@ -728,33 +604,6 @@ void MQTT::publishQueuedMessages()
     const std::unique_ptr entry(mqttQueue.dequeuePtr(0));
     LOG_INFO("publish %s, %u bytes from queue", entry->topic.c_str(), entry->envBytes.size());
     publish(entry->topic.c_str(), entry->envBytes.data(), entry->envBytes.size(), false);
-
-#if !defined(ARCH_NRF52) ||                                                                                                      \
-    defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
-    if (!moduleConfig.mqtt.json_enabled)
-        return;
-
-    // handle json topic
-    const DecodedServiceEnvelope env(entry->envBytes.data(), entry->envBytes.size());
-    if (!env.validDecode || env.packet == NULL || env.channel_id == NULL)
-        return;
-
-    auto jsonString = MeshPacketSerializer::JsonSerialize(env.packet);
-    if (jsonString.length() == 0)
-        return;
-
-    // Generate node ID from nodenum for topic
-    std::string nodeId = nodeDB->getNodeId();
-
-    std::string topicJson;
-    if (env.packet->pki_encrypted) {
-        topicJson = jsonTopic + "PKI/" + nodeId;
-    } else {
-        topicJson = jsonTopic + env.channel_id + "/" + nodeId;
-    }
-    LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
-    publish(topicJson.c_str(), jsonString.c_str(), false);
-#endif // ARCH_NRF52 NRF52_USE_JSON
 }
 
 void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex)
@@ -818,21 +667,6 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
     if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
         LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
         publish(topic.c_str(), bytes, numBytes, false);
-
-#if !defined(ARCH_NRF52) ||                                                                                                      \
-    defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
-        if (!moduleConfig.mqtt.json_enabled)
-            return;
-        // handle json topic
-        auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded);
-        if (jsonString.length() == 0)
-            return;
-        // Generate node ID from nodenum for JSON topic
-        std::string nodeIdForJson = nodeDB->getNodeId();
-        std::string topicJson = jsonTopic + channelId + "/" + nodeIdForJson;
-        LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
-        publish(topicJson.c_str(), jsonString.c_str(), false);
-#endif // ARCH_NRF52 NRF52_USE_JSON
     } else {
         LOG_INFO("MQTT not connected, queue packet");
         QueueEntry *entry;
diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h
index 7d5715602..ebca19030 100644
--- a/src/mqtt/MQTT.h
+++ b/src/mqtt/MQTT.h
@@ -6,9 +6,6 @@
 #include "concurrency/OSThread.h"
 #include "mesh/Channels.h"
 #include "mesh/generated/meshtastic/mqtt.pb.h"
-#if !defined(ARCH_NRF52) || NRF52_USE_JSON
-#include "serialization/JSON.h"
-#endif
 #if HAS_WIFI
 #include 
 #if __has_include()
@@ -101,9 +98,8 @@ class MQTT : private concurrency::OSThread
     explicit MQTT(std::unique_ptr mqttClient);
 #endif
 
-    std::string cryptTopic = "/2/e/";   // msh/2/e/CHANNELID/NODEID
-    std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID
-    std::string mapTopic = "/2/map/";   // For protobuf-encoded MapReport messages
+    std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID
+    std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages
 
     // For map reporting (only applies when enabled)
     const uint32_t default_map_position_precision = 14; // defaults to max. offset of ~1459m
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index 9e0a1b2a5..9a9d4dde2 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -33,8 +33,6 @@
 
 portduino_config_struct portduino_config;
 portduino_status_struct portduino_status;
-std::ofstream traceFile;
-std::ofstream JSONFile;
 Ch341Hal *ch341Hal = nullptr;
 char *configPath = nullptr;
 char *optionMac = nullptr;
@@ -622,31 +620,6 @@ void portduinoSetup()
         SPI.begin(portduino_config.lora_spi_dev.c_str());
     }
 
-    if (portduino_config.traceFilename != "") {
-        try {
-            traceFile.open(portduino_config.traceFilename, std::ios::out | std::ios::app);
-        } catch (std::ofstream::failure &e) {
-            std::cout << "*** traceFile Exception " << e.what() << std::endl;
-            exit(EXIT_FAILURE);
-        }
-        if (!traceFile.is_open()) {
-            std::cout << "*** traceFile open failure" << std::endl;
-            exit(EXIT_FAILURE);
-        }
-    } else if (portduino_config.JSONFilename != "") {
-        try {
-            if (portduino_config.JSONFileRotate == 0) {
-                JSONFile.open(portduino_config.JSONFilename, std::ios::out | std::ios::app);
-            }
-        } catch (std::ofstream::failure &e) {
-            std::cout << "*** JSONFile Exception " << e.what() << std::endl;
-            exit(EXIT_FAILURE);
-        }
-        if (!JSONFile.is_open()) {
-            std::cout << "*** JSONFile open failure" << std::endl;
-            exit(EXIT_FAILURE);
-        }
-    }
     if (verboseEnabled && portduino_config.logoutputlevel != level_trace) {
         portduino_config.logoutputlevel = level_debug;
     }
@@ -692,31 +665,6 @@ bool loadConfig(const char *configPath)
             } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "error") {
                 portduino_config.logoutputlevel = level_error;
             }
-            portduino_config.traceFilename = yamlConfig["Logging"]["TraceFile"].as("");
-            portduino_config.JSONFilename = yamlConfig["Logging"]["JSONFile"].as("");
-            portduino_config.JSONFileRotate = yamlConfig["Logging"]["JSONFileRotate"].as(0);
-            portduino_config.JSONFilter = (_meshtastic_PortNum)yamlConfig["Logging"]["JSONFilter"].as(0);
-            if (yamlConfig["Logging"]["JSONFilter"].as("") == "textmessage")
-                portduino_config.JSONFilter = meshtastic_PortNum_TEXT_MESSAGE_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "telemetry")
-                portduino_config.JSONFilter = meshtastic_PortNum_TELEMETRY_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "nodeinfo")
-                portduino_config.JSONFilter = meshtastic_PortNum_NODEINFO_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "position")
-                portduino_config.JSONFilter = meshtastic_PortNum_POSITION_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "waypoint")
-                portduino_config.JSONFilter = meshtastic_PortNum_WAYPOINT_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "neighborinfo")
-                portduino_config.JSONFilter = meshtastic_PortNum_NEIGHBORINFO_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "traceroute")
-                portduino_config.JSONFilter = meshtastic_PortNum_TRACEROUTE_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "detection")
-                portduino_config.JSONFilter = meshtastic_PortNum_DETECTION_SENSOR_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "paxcounter")
-                portduino_config.JSONFilter = meshtastic_PortNum_PAXCOUNTER_APP;
-            else if (yamlConfig["Logging"]["JSONFilter"].as("") == "remotehardware")
-                portduino_config.JSONFilter = meshtastic_PortNum_REMOTE_HARDWARE_APP;
-
             if (yamlConfig["Logging"]["AsciiLogs"]) {
                 // Default is !isatty(1) but can be set explicitly in config.yaml
                 portduino_config.ascii_logs = yamlConfig["Logging"]["AsciiLogs"].as();
diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h
index b38cfca25..d3720a56b 100644
--- a/src/platform/portduino/PortduinoGlue.h
+++ b/src/platform/portduino/PortduinoGlue.h
@@ -55,9 +55,6 @@ struct pinMapping {
     bool default_high = false;
 };
 
-extern std::ofstream traceFile;
-extern std::ofstream JSONFile;
-
 extern Ch341Hal *ch341Hal;
 int initGPIOPin(int pinNum, const std::string &gpioChipname, int line);
 bool loadConfig(const char *configPath);
@@ -160,14 +157,9 @@ extern struct portduino_config_struct {
 
     // Logging
     portduino_log_level logoutputlevel = level_debug;
-    std::string traceFilename;
     bool ascii_logs = !isatty(1);
     bool ascii_logs_explicit = false;
 
-    std::string JSONFilename;
-    int JSONFileRotate = 0;
-    meshtastic_PortNum JSONFilter = (_meshtastic_PortNum)0;
-
     // Webserver
     std::string webserver_root_path = "";
     std::string webserver_ssl_key_path = "/etc/meshtasticd/ssl/private_key.pem";
@@ -469,34 +461,6 @@ extern struct portduino_config_struct {
             out << "trace";
             break;
         }
-        if (traceFilename != "")
-            out << YAML::Key << "TraceFile" << YAML::Value << traceFilename;
-        if (JSONFilename != "") {
-            out << YAML::Key << "JSONFile" << YAML::Value << JSONFilename;
-            if (JSONFileRotate != 0)
-                out << YAML::Key << "JSONFileRotate" << YAML::Value << JSONFileRotate;
-
-            if (JSONFilter == meshtastic_PortNum_TEXT_MESSAGE_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "textmessage";
-            else if (JSONFilter == meshtastic_PortNum_TELEMETRY_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "telemetry";
-            else if (JSONFilter == meshtastic_PortNum_NODEINFO_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "nodeinfo";
-            else if (JSONFilter == meshtastic_PortNum_POSITION_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "position";
-            else if (JSONFilter == meshtastic_PortNum_WAYPOINT_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "waypoint";
-            else if (JSONFilter == meshtastic_PortNum_NEIGHBORINFO_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "neighborinfo";
-            else if (JSONFilter == meshtastic_PortNum_TRACEROUTE_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "traceroute";
-            else if (JSONFilter == meshtastic_PortNum_DETECTION_SENSOR_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "detection";
-            else if (JSONFilter == meshtastic_PortNum_PAXCOUNTER_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "paxcounter";
-            else if (JSONFilter == meshtastic_PortNum_REMOTE_HARDWARE_APP)
-                out << YAML::Key << "JSONFilter" << YAML::Value << "remotehardware";
-        }
         if (ascii_logs_explicit) {
             out << YAML::Key << "AsciiLogs" << YAML::Value << ascii_logs;
         }
diff --git a/src/serialization/JSON.cpp b/src/serialization/JSON.cpp
deleted file mode 100644
index 42e615108..000000000
--- a/src/serialization/JSON.cpp
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * File JSON.cpp part of the SimpleJSON Library - http://mjpa.in/json
- *
- * Copyright (C) 2010 Mike Anchor
- *
- * 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:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#include "JSON.h"
-
-/**
- * Blocks off the public constructor
- *
- * @access private
- *
- */
-JSON::JSON() {}
-
-/**
- * Parses a complete JSON encoded string
- *
- * @access public
- *
- * @param char* data The JSON text
- *
- * @return JSONValue* Returns a JSON Value representing the root, or NULL on error
- */
-JSONValue *JSON::Parse(const char *data)
-{
-    // Skip any preceding whitespace, end of data = no JSON = fail
-    if (!SkipWhitespace(&data))
-        return NULL;
-
-    // We need the start of a value here now...
-    JSONValue *value = JSONValue::Parse(&data);
-    if (value == NULL)
-        return NULL;
-
-    // Can be white space now and should be at the end of the string then...
-    if (SkipWhitespace(&data)) {
-        delete value;
-        return NULL;
-    }
-
-    // We're now at the end of the string
-    return value;
-}
-
-/**
- * Turns the passed in JSONValue into a JSON encode string
- *
- * @access public
- *
- * @param JSONValue* value The root value
- *
- * @return std::string Returns a JSON encoded string representation of the given value
- */
-std::string JSON::Stringify(const JSONValue *value)
-{
-    if (value != NULL)
-        return value->Stringify();
-    else
-        return "";
-}
-
-/**
- * Skips over any whitespace characters (space, tab, \r or \n) defined by the JSON spec
- *
- * @access protected
- *
- * @param char** data Pointer to a char* that contains the JSON text
- *
- * @return bool Returns true if there is more data, or false if the end of the text was reached
- */
-bool JSON::SkipWhitespace(const char **data)
-{
-    while (**data != 0 && (**data == ' ' || **data == '\t' || **data == '\r' || **data == '\n'))
-        (*data)++;
-
-    return **data != 0;
-}
-
-/**
- * Extracts a JSON String as defined by the spec - ""
- * Any escaped characters are swapped out for their unescaped values
- *
- * @access protected
- *
- * @param char** data Pointer to a char* that contains the JSON text
- * @param std::string& str Reference to a std::string to receive the extracted string
- *
- * @return bool Returns true on success, false on failure
- */
-bool JSON::ExtractString(const char **data, std::string &str)
-{
-    str = "";
-
-    while (**data != 0) {
-        // Save the char so we can change it if need be
-        char next_char = **data;
-
-        // Escaping something?
-        if (next_char == '\\') {
-            // Move over the escape char
-            (*data)++;
-
-            // Deal with the escaped char
-            switch (**data) {
-            case '"':
-                next_char = '"';
-                break;
-            case '\\':
-                next_char = '\\';
-                break;
-            case '/':
-                next_char = '/';
-                break;
-            case 'b':
-                next_char = '\b';
-                break;
-            case 'f':
-                next_char = '\f';
-                break;
-            case 'n':
-                next_char = '\n';
-                break;
-            case 'r':
-                next_char = '\r';
-                break;
-            case 't':
-                next_char = '\t';
-                break;
-            case 'u': {
-                // We need 5 chars (4 hex + the 'u') or its not valid
-                if (!simplejson_csnlen(*data, 5))
-                    return false;
-
-                // Deal with the chars
-                next_char = 0;
-                for (int i = 0; i < 4; i++) {
-                    // Do it first to move off the 'u' and leave us on the
-                    // final hex digit as we move on by one later on
-                    (*data)++;
-
-                    next_char <<= 4;
-
-                    // Parse the hex digit
-                    if (**data >= '0' && **data <= '9')
-                        next_char |= (**data - '0');
-                    else if (**data >= 'A' && **data <= 'F')
-                        next_char |= (10 + (**data - 'A'));
-                    else if (**data >= 'a' && **data <= 'f')
-                        next_char |= (10 + (**data - 'a'));
-                    else {
-                        // Invalid hex digit = invalid JSON
-                        return false;
-                    }
-                }
-                break;
-            }
-
-            // By the spec, only the above cases are allowed
-            default:
-                return false;
-            }
-        }
-
-        // End of the string?
-        else if (next_char == '"') {
-            (*data)++;
-            str.shrink_to_fit(); // Remove unused capacity
-            return true;
-        }
-
-        // Disallowed char?
-        else if (next_char < ' ' && next_char != '\t') {
-            // SPEC Violation: Allow tabs due to real world cases
-            return false;
-        }
-
-        // Add the next char
-        str += next_char;
-
-        // Move on
-        (*data)++;
-    }
-
-    // If we're here, the string ended incorrectly
-    return false;
-}
-
-/**
- * Parses some text as though it is an integer
- *
- * @access protected
- *
- * @param char** data Pointer to a char* that contains the JSON text
- *
- * @return double Returns the double value of the number found
- */
-double JSON::ParseInt(const char **data)
-{
-    double integer = 0;
-    while (**data != 0 && **data >= '0' && **data <= '9')
-        integer = integer * 10 + (*(*data)++ - '0');
-
-    return integer;
-}
-
-/**
- * Parses some text as though it is a decimal
- *
- * @access protected
- *
- * @param char** data Pointer to a char* that contains the JSON text
- *
- * @return double Returns the double value of the decimal found
- */
-double JSON::ParseDecimal(const char **data)
-{
-    double decimal = 0.0;
-    double factor = 0.1;
-    while (**data != 0 && **data >= '0' && **data <= '9') {
-        int digit = (*(*data)++ - '0');
-        decimal = decimal + digit * factor;
-        factor *= 0.1;
-    }
-    return decimal;
-}
diff --git a/src/serialization/JSON.h b/src/serialization/JSON.h
deleted file mode 100644
index 33f34e684..000000000
--- a/src/serialization/JSON.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * File JSON.h part of the SimpleJSON Library - http://mjpa.in/json
- *
- * Copyright (C) 2010 Mike Anchor
- *
- * 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:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#ifndef _JSON_H_
-#define _JSON_H_
-
-#include 
-#include 
-#include 
-#include 
-
-// Simple function to check a string 's' has at least 'n' characters
-static inline bool simplejson_csnlen(const char *s, size_t n)
-{
-    if (s == 0)
-        return false;
-
-    const char *save = s;
-    while (n-- > 0) {
-        if (*(save++) == 0)
-            return false;
-    }
-
-    return true;
-}
-
-// Custom types
-class JSONValue;
-typedef std::vector JSONArray;
-typedef std::map JSONObject;
-
-#include "JSONValue.h"
-
-class JSON
-{
-    friend class JSONValue;
-
-  public:
-    static JSONValue *Parse(const char *data);
-    static std::string Stringify(const JSONValue *value);
-
-  protected:
-    static bool SkipWhitespace(const char **data);
-    static bool ExtractString(const char **data, std::string &str);
-    static double ParseInt(const char **data);
-    static double ParseDecimal(const char **data);
-
-  private:
-    JSON();
-};
-
-#endif
diff --git a/src/serialization/JSONValue.cpp b/src/serialization/JSONValue.cpp
deleted file mode 100644
index 20cd90373..000000000
--- a/src/serialization/JSONValue.cpp
+++ /dev/null
@@ -1,897 +0,0 @@
-/*
- * File JSONValue.cpp part of the SimpleJSON Library - http://mjpa.in/json
- *
- * Copyright (C) 2010 Mike Anchor
- *
- * 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:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-
-#include "JSONValue.h"
-
-// Macros to free an array/object
-#define FREE_ARRAY(x)                                                                                                            \
-    {                                                                                                                            \
-        JSONArray::iterator iter;                                                                                                \
-        for (iter = x.begin(); iter != x.end(); ++iter) {                                                                        \
-            delete *iter;                                                                                                        \
-        }                                                                                                                        \
-    }
-#define FREE_OBJECT(x)                                                                                                           \
-    {                                                                                                                            \
-        JSONObject::iterator iter;                                                                                               \
-        for (iter = x.begin(); iter != x.end(); ++iter) {                                                                        \
-            delete (*iter).second;                                                                                               \
-        }                                                                                                                        \
-    }
-
-/**
- * Parses a JSON encoded value to a JSONValue object
- *
- * @access protected
- *
- * @param char** data Pointer to a char* that contains the data
- *
- * @return JSONValue* Returns a pointer to a JSONValue object on success, NULL on error
- */
-JSONValue *JSONValue::Parse(const char **data)
-{
-    // Is it a string?
-    if (**data == '"') {
-        std::string str;
-        if (!JSON::ExtractString(&(++(*data)), str))
-            return NULL;
-        else
-            return new JSONValue(str);
-    }
-
-    // Is it a boolean?
-    else if ((simplejson_csnlen(*data, 4) && strncasecmp(*data, "true", 4) == 0) ||
-             (simplejson_csnlen(*data, 5) && strncasecmp(*data, "false", 5) == 0)) {
-        bool value = strncasecmp(*data, "true", 4) == 0;
-        (*data) += value ? 4 : 5;
-        return new JSONValue(value);
-    }
-
-    // Is it a null?
-    else if (simplejson_csnlen(*data, 4) && strncasecmp(*data, "null", 4) == 0) {
-        (*data) += 4;
-        return new JSONValue();
-    }
-
-    // Is it a number?
-    else if (**data == '-' || (**data >= '0' && **data <= '9')) {
-        // Negative?
-        bool neg = **data == '-';
-        if (neg)
-            (*data)++;
-
-        double number = 0.0;
-
-        // Parse the whole part of the number - only if it wasn't 0
-        if (**data == '0')
-            (*data)++;
-        else if (**data >= '1' && **data <= '9')
-            number = JSON::ParseInt(data);
-        else
-            return NULL;
-
-        // Could be a decimal now...
-        if (**data == '.') {
-            (*data)++;
-
-            // Not get any digits?
-            if (!(**data >= '0' && **data <= '9'))
-                return NULL;
-
-            // Find the decimal and sort the decimal place out
-            // Use ParseDecimal as ParseInt won't work with decimals less than 0.1
-            // thanks to Javier Abadia for the report & fix
-            double decimal = JSON::ParseDecimal(data);
-
-            // Save the number
-            number += decimal;
-        }
-
-        // Could be an exponent now...
-        if (**data == 'E' || **data == 'e') {
-            (*data)++;
-
-            // Check signage of expo
-            bool neg_expo = false;
-            if (**data == '-' || **data == '+') {
-                neg_expo = **data == '-';
-                (*data)++;
-            }
-
-            // Not get any digits?
-            if (!(**data >= '0' && **data <= '9'))
-                return NULL;
-
-            // Sort the expo out
-            double expo = JSON::ParseInt(data);
-            for (double i = 0.0; i < expo; i++)
-                number = neg_expo ? (number / 10.0) : (number * 10.0);
-        }
-
-        // Was it neg?
-        if (neg)
-            number *= -1;
-
-        return new JSONValue(number);
-    }
-
-    // An object?
-    else if (**data == '{') {
-        JSONObject object;
-
-        (*data)++;
-
-        while (**data != 0) {
-            // Whitespace at the start?
-            if (!JSON::SkipWhitespace(data)) {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            // Special case - empty object
-            if (object.size() == 0 && **data == '}') {
-                (*data)++;
-                return new JSONValue(object);
-            }
-
-            // We want a string now...
-            std::string name;
-            if (!JSON::ExtractString(&(++(*data)), name)) {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            // More whitespace?
-            if (!JSON::SkipWhitespace(data)) {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            // Need a : now
-            if (*((*data)++) != ':') {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            // More whitespace?
-            if (!JSON::SkipWhitespace(data)) {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            // The value is here
-            JSONValue *value = Parse(data);
-            if (value == NULL) {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            // Add the name:value
-            if (object.find(name) != object.end())
-                delete object[name];
-            object[name] = value;
-
-            // More whitespace?
-            if (!JSON::SkipWhitespace(data)) {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            // End of object?
-            if (**data == '}') {
-                (*data)++;
-                return new JSONValue(object);
-            }
-
-            // Want a , now
-            if (**data != ',') {
-                FREE_OBJECT(object);
-                return NULL;
-            }
-
-            (*data)++;
-        }
-
-        // Only here if we ran out of data
-        FREE_OBJECT(object);
-        return NULL;
-    }
-
-    // An array?
-    else if (**data == '[') {
-        JSONArray array;
-
-        (*data)++;
-
-        while (**data != 0) {
-            // Whitespace at the start?
-            if (!JSON::SkipWhitespace(data)) {
-                FREE_ARRAY(array);
-                return NULL;
-            }
-
-            // Special case - empty array
-            if (array.size() == 0 && **data == ']') {
-                (*data)++;
-                return new JSONValue(array);
-            }
-
-            // Get the value
-            JSONValue *value = Parse(data);
-            if (value == NULL) {
-                FREE_ARRAY(array);
-                return NULL;
-            }
-
-            // Add the value
-            array.push_back(value);
-
-            // More whitespace?
-            if (!JSON::SkipWhitespace(data)) {
-                FREE_ARRAY(array);
-                return NULL;
-            }
-
-            // End of array?
-            if (**data == ']') {
-                (*data)++;
-                return new JSONValue(array);
-            }
-
-            // Want a , now
-            if (**data != ',') {
-                FREE_ARRAY(array);
-                return NULL;
-            }
-
-            (*data)++;
-        }
-
-        // Only here if we ran out of data
-        FREE_ARRAY(array);
-        return NULL;
-    }
-
-    // Ran out of possibilities, it's bad!
-    else {
-        return NULL;
-    }
-}
-
-/**
- * Basic constructor for creating a JSON Value of type NULL
- *
- * @access public
- */
-JSONValue::JSONValue(/*NULL*/)
-{
-    type = JSONType_Null;
-}
-
-/**
- * Basic constructor for creating a JSON Value of type String
- *
- * @access public
- *
- * @param char* m_char_value The string to use as the value
- */
-JSONValue::JSONValue(const char *m_char_value)
-{
-    type = JSONType_String;
-    string_value = new std::string(std::string(m_char_value));
-}
-
-/**
- * Basic constructor for creating a JSON Value of type String
- *
- * @access public
- *
- * @param std::string m_string_value The string to use as the value
- */
-JSONValue::JSONValue(const std::string &m_string_value)
-{
-    type = JSONType_String;
-    string_value = new std::string(m_string_value);
-}
-
-/**
- * Basic constructor for creating a JSON Value of type Bool
- *
- * @access public
- *
- * @param bool m_bool_value The bool to use as the value
- */
-JSONValue::JSONValue(bool m_bool_value)
-{
-    type = JSONType_Bool;
-    bool_value = m_bool_value;
-}
-
-/**
- * Basic constructor for creating a JSON Value of type Number
- *
- * @access public
- *
- * @param double m_number_value The number to use as the value
- */
-JSONValue::JSONValue(double m_number_value)
-{
-    type = JSONType_Number;
-    number_value = m_number_value;
-}
-
-/**
- * Basic constructor for creating a JSON Value of type Number
- *
- * @access public
- *
- * @param int m_integer_value The number to use as the value
- */
-JSONValue::JSONValue(int m_integer_value)
-{
-    type = JSONType_Number;
-    number_value = (double)m_integer_value;
-}
-
-/**
- * Basic constructor for creating a JSON Value of type Number
- *
- * @access public
- *
- * @param unsigned int m_integer_value The number to use as the value
- */
-JSONValue::JSONValue(unsigned int m_integer_value)
-{
-    type = JSONType_Number;
-    number_value = (double)m_integer_value;
-}
-
-/**
- * Basic constructor for creating a JSON Value of type Array
- *
- * @access public
- *
- * @param JSONArray m_array_value The JSONArray to use as the value
- */
-JSONValue::JSONValue(const JSONArray &m_array_value)
-{
-    type = JSONType_Array;
-    array_value = new JSONArray(m_array_value);
-}
-
-/**
- * Basic constructor for creating a JSON Value of type Object
- *
- * @access public
- *
- * @param JSONObject m_object_value The JSONObject to use as the value
- */
-JSONValue::JSONValue(const JSONObject &m_object_value)
-{
-    type = JSONType_Object;
-    object_value = new JSONObject(m_object_value);
-}
-
-/**
- * Copy constructor to perform a deep copy of array / object values
- *
- * @access public
- *
- * @param JSONValue m_source The source JSONValue that is being copied
- */
-JSONValue::JSONValue(const JSONValue &m_source)
-{
-    type = m_source.type;
-
-    switch (type) {
-    case JSONType_String:
-        string_value = new std::string(*m_source.string_value);
-        break;
-
-    case JSONType_Bool:
-        bool_value = m_source.bool_value;
-        break;
-
-    case JSONType_Number:
-        number_value = m_source.number_value;
-        break;
-
-    case JSONType_Array: {
-        JSONArray source_array = *m_source.array_value;
-        JSONArray::iterator iter;
-        array_value = new JSONArray();
-        for (iter = source_array.begin(); iter != source_array.end(); ++iter)
-            array_value->push_back(new JSONValue(**iter));
-        break;
-    }
-
-    case JSONType_Object: {
-        JSONObject source_object = *m_source.object_value;
-        object_value = new JSONObject();
-        JSONObject::iterator iter;
-        for (iter = source_object.begin(); iter != source_object.end(); ++iter) {
-            std::string name = (*iter).first;
-            (*object_value)[name] = new JSONValue(*((*iter).second));
-        }
-        break;
-    }
-
-    case JSONType_Null:
-        // Nothing to do.
-        break;
-    }
-}
-
-/**
- * The destructor for the JSON Value object
- * Handles deleting the objects in the array or the object value
- *
- * @access public
- */
-JSONValue::~JSONValue()
-{
-    if (type == JSONType_Array) {
-        JSONArray::iterator iter;
-        for (iter = array_value->begin(); iter != array_value->end(); ++iter)
-            delete *iter;
-        delete array_value;
-    } else if (type == JSONType_Object) {
-        JSONObject::iterator iter;
-        for (iter = object_value->begin(); iter != object_value->end(); ++iter) {
-            delete (*iter).second;
-        }
-        delete object_value;
-    } else if (type == JSONType_String) {
-        delete string_value;
-    }
-}
-
-/**
- * Checks if the value is a NULL
- *
- * @access public
- *
- * @return bool Returns true if it is a NULL value, false otherwise
- */
-bool JSONValue::IsNull() const
-{
-    return type == JSONType_Null;
-}
-
-/**
- * Checks if the value is a String
- *
- * @access public
- *
- * @return bool Returns true if it is a String value, false otherwise
- */
-bool JSONValue::IsString() const
-{
-    return type == JSONType_String;
-}
-
-/**
- * Checks if the value is a Bool
- *
- * @access public
- *
- * @return bool Returns true if it is a Bool value, false otherwise
- */
-bool JSONValue::IsBool() const
-{
-    return type == JSONType_Bool;
-}
-
-/**
- * Checks if the value is a Number
- *
- * @access public
- *
- * @return bool Returns true if it is a Number value, false otherwise
- */
-bool JSONValue::IsNumber() const
-{
-    return type == JSONType_Number;
-}
-
-/**
- * Checks if the value is an Array
- *
- * @access public
- *
- * @return bool Returns true if it is an Array value, false otherwise
- */
-bool JSONValue::IsArray() const
-{
-    return type == JSONType_Array;
-}
-
-/**
- * Checks if the value is an Object
- *
- * @access public
- *
- * @return bool Returns true if it is an Object value, false otherwise
- */
-bool JSONValue::IsObject() const
-{
-    return type == JSONType_Object;
-}
-
-/**
- * Retrieves the String value of this JSONValue
- * Use IsString() before using this method.
- *
- * @access public
- *
- * @return std::string Returns the string value
- */
-const std::string &JSONValue::AsString() const
-{
-    return (*string_value);
-}
-
-/**
- * Retrieves the Bool value of this JSONValue
- * Use IsBool() before using this method.
- *
- * @access public
- *
- * @return bool Returns the bool value
- */
-bool JSONValue::AsBool() const
-{
-    return bool_value;
-}
-
-/**
- * Retrieves the Number value of this JSONValue
- * Use IsNumber() before using this method.
- *
- * @access public
- *
- * @return double Returns the number value
- */
-double JSONValue::AsNumber() const
-{
-    return number_value;
-}
-
-/**
- * Retrieves the Array value of this JSONValue
- * Use IsArray() before using this method.
- *
- * @access public
- *
- * @return JSONArray Returns the array value
- */
-const JSONArray &JSONValue::AsArray() const
-{
-    return (*array_value);
-}
-
-/**
- * Retrieves the Object value of this JSONValue
- * Use IsObject() before using this method.
- *
- * @access public
- *
- * @return JSONObject Returns the object value
- */
-const JSONObject &JSONValue::AsObject() const
-{
-    return (*object_value);
-}
-
-/**
- * Retrieves the number of children of this JSONValue.
- * This number will be 0 or the actual number of children
- * if IsArray() or IsObject().
- *
- * @access public
- *
- * @return The number of children.
- */
-std::size_t JSONValue::CountChildren() const
-{
-    switch (type) {
-    case JSONType_Array:
-        return array_value->size();
-    case JSONType_Object:
-        return object_value->size();
-    default:
-        return 0;
-    }
-}
-
-/**
- * Checks if this JSONValue has a child at the given index.
- * Use IsArray() before using this method.
- *
- * @access public
- *
- * @return bool Returns true if the array has a value at the given index.
- */
-bool JSONValue::HasChild(std::size_t index) const
-{
-    if (type == JSONType_Array) {
-        return index < array_value->size();
-    } else {
-        return false;
-    }
-}
-
-/**
- * Retrieves the child of this JSONValue at the given index.
- * Use IsArray() before using this method.
- *
- * @access public
- *
- * @return JSONValue* Returns JSONValue at the given index or NULL
- *                    if it doesn't exist.
- */
-JSONValue *JSONValue::Child(std::size_t index)
-{
-    if (index < array_value->size()) {
-        return (*array_value)[index];
-    } else {
-        return NULL;
-    }
-}
-
-/**
- * Checks if this JSONValue has a child at the given key.
- * Use IsObject() before using this method.
- *
- * @access public
- *
- * @return bool Returns true if the object has a value at the given key.
- */
-bool JSONValue::HasChild(const char *name) const
-{
-    if (type == JSONType_Object) {
-        return object_value->find(name) != object_value->end();
-    } else {
-        return false;
-    }
-}
-
-/**
- * Retrieves the child of this JSONValue at the given key.
- * Use IsObject() before using this method.
- *
- * @access public
- *
- * @return JSONValue* Returns JSONValue for the given key in the object
- *                    or NULL if it doesn't exist.
- */
-JSONValue *JSONValue::Child(const char *name)
-{
-    JSONObject::const_iterator it = object_value->find(name);
-    if (it != object_value->end()) {
-        return it->second;
-    } else {
-        return NULL;
-    }
-}
-
-/**
- * Retrieves the keys of the JSON Object or an empty vector
- * if this value is not an object.
- *
- * @access public
- *
- * @return std::vector A vector containing the keys.
- */
-std::vector JSONValue::ObjectKeys() const
-{
-    std::vector keys;
-
-    if (type == JSONType_Object) {
-        JSONObject::const_iterator iter = object_value->begin();
-        while (iter != object_value->end()) {
-            keys.push_back(iter->first);
-
-            ++iter;
-        }
-    }
-
-    return keys;
-}
-
-/**
- * Creates a JSON encoded string for the value with all necessary characters escaped
- *
- * @access public
- *
- * @param bool prettyprint Enable prettyprint
- *
- * @return std::string Returns the JSON string
- */
-std::string JSONValue::Stringify(bool const prettyprint) const
-{
-    size_t const indentDepth = prettyprint ? 1 : 0;
-    return StringifyImpl(indentDepth);
-}
-
-/**
- * Creates a JSON encoded string for the value with all necessary characters escaped
- *
- * @access private
- *
- * @param size_t indentDepth The prettyprint indentation depth (0 : no prettyprint)
- *
- * @return std::string Returns the JSON string
- */
-std::string JSONValue::StringifyImpl(size_t const indentDepth) const
-{
-    std::string ret_string;
-    size_t const indentDepth1 = indentDepth ? indentDepth + 1 : 0;
-    std::string const indentStr = Indent(indentDepth);
-    std::string const indentStr1 = Indent(indentDepth1);
-
-    switch (type) {
-    case JSONType_Null:
-        ret_string = "null";
-        break;
-
-    case JSONType_String:
-        ret_string = StringifyString(*string_value);
-        break;
-
-    case JSONType_Bool:
-        ret_string = bool_value ? "true" : "false";
-        break;
-
-    case JSONType_Number: {
-        if (isinf(number_value) || isnan(number_value))
-            ret_string = "null";
-        else {
-            std::stringstream ss;
-            ss.precision(15);
-            ss << number_value;
-            ret_string = ss.str();
-        }
-        break;
-    }
-
-    case JSONType_Array: {
-        ret_string = indentDepth ? "[\n" + indentStr1 : "[";
-        JSONArray::const_iterator iter = array_value->begin();
-        while (iter != array_value->end()) {
-            ret_string += (*iter)->StringifyImpl(indentDepth1);
-
-            // Not at the end - add a separator
-            if (++iter != array_value->end())
-                ret_string += ",";
-        }
-        ret_string += indentDepth ? "\n" + indentStr + "]" : "]";
-        break;
-    }
-
-    case JSONType_Object: {
-        ret_string = indentDepth ? "{\n" + indentStr1 : "{";
-        JSONObject::const_iterator iter = object_value->begin();
-        while (iter != object_value->end()) {
-            ret_string += StringifyString((*iter).first);
-            ret_string += ":";
-            ret_string += (*iter).second->StringifyImpl(indentDepth1);
-
-            // Not at the end - add a separator
-            if (++iter != object_value->end())
-                ret_string += ",";
-        }
-        ret_string += indentDepth ? "\n" + indentStr + "}" : "}";
-        break;
-    }
-    }
-
-    return ret_string;
-}
-
-/**
- * Creates a JSON encoded string with all required fields escaped
- * Works from http://www.ecma-internationl.org/publications/files/ECMA-ST/ECMA-262.pdf
- * Section 15.12.3.
- *
- * @access private
- *
- * @param std::string str The string that needs to have the characters escaped
- *
- * @return std::string Returns the JSON string
- */
-std::string JSONValue::StringifyString(const std::string &str)
-{
-    std::string str_out = "\"";
-
-    std::string::const_iterator iter = str.begin();
-    while (iter != str.end()) {
-        char chr = *iter;
-
-        if (chr == '"' || chr == '\\' || chr == '/') {
-            str_out += '\\';
-            str_out += chr;
-        } else if (chr == '\b') {
-            str_out += "\\b";
-        } else if (chr == '\f') {
-            str_out += "\\f";
-        } else if (chr == '\n') {
-            str_out += "\\n";
-        } else if (chr == '\r') {
-            str_out += "\\r";
-        } else if (chr == '\t') {
-            str_out += "\\t";
-        } else if (chr < 0x20 || chr == 0x7F) {
-            char buf[7];
-            snprintf(buf, sizeof(buf), "\\u%04x", chr);
-            str_out += buf;
-        } else if (chr < 0x80) {
-            str_out += chr;
-        } else {
-            str_out += chr;
-            size_t remain = str.end() - iter - 1;
-            if ((chr & 0xE0) == 0xC0 && remain >= 1) {
-                ++iter;
-                str_out += *iter;
-            } else if ((chr & 0xF0) == 0xE0 && remain >= 2) {
-                str_out += *(++iter);
-                str_out += *(++iter);
-            } else if ((chr & 0xF8) == 0xF0 && remain >= 3) {
-                str_out += *(++iter);
-                str_out += *(++iter);
-                str_out += *(++iter);
-            }
-        }
-
-        ++iter;
-    }
-
-    str_out += "\"";
-    return str_out;
-}
-
-/**
- * Creates the indentation string for the depth given
- *
- * @access private
- *
- * @param size_t indent The prettyprint indentation depth (0 : no indentation)
- *
- * @return std::string Returns the string
- */
-std::string JSONValue::Indent(size_t depth)
-{
-    const size_t indent_step = 2;
-    depth ? --depth : 0;
-    std::string indentStr(depth * indent_step, ' ');
-    return indentStr;
-}
\ No newline at end of file
diff --git a/src/serialization/JSONValue.h b/src/serialization/JSONValue.h
deleted file mode 100644
index 16d53e89f..000000000
--- a/src/serialization/JSONValue.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * File JSONValue.h part of the SimpleJSON Library - http://mjpa.in/json
- *
- * Copyright (C) 2010 Mike Anchor
- *
- * 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:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#ifndef _JSONVALUE_H_
-#define _JSONVALUE_H_
-
-#include 
-#include 
-
-#include "JSON.h"
-
-class JSON;
-
-enum JSONType { JSONType_Null, JSONType_String, JSONType_Bool, JSONType_Number, JSONType_Array, JSONType_Object };
-
-class JSONValue
-{
-    friend class JSON;
-
-  public:
-    JSONValue(/*NULL*/);
-    explicit JSONValue(const char *m_char_value);
-    explicit JSONValue(const std::string &m_string_value);
-    explicit JSONValue(bool m_bool_value);
-    explicit JSONValue(double m_number_value);
-    explicit JSONValue(int m_integer_value);
-    explicit JSONValue(unsigned int m_integer_value);
-    explicit JSONValue(const JSONArray &m_array_value);
-    explicit JSONValue(const JSONObject &m_object_value);
-    explicit JSONValue(const JSONValue &m_source);
-    ~JSONValue();
-
-    bool IsNull() const;
-    bool IsString() const;
-    bool IsBool() const;
-    bool IsNumber() const;
-    bool IsArray() const;
-    bool IsObject() const;
-
-    const std::string &AsString() const;
-    bool AsBool() const;
-    double AsNumber() const;
-    const JSONArray &AsArray() const;
-    const JSONObject &AsObject() const;
-
-    std::size_t CountChildren() const;
-    bool HasChild(std::size_t index) const;
-    JSONValue *Child(std::size_t index);
-    bool HasChild(const char *name) const;
-    JSONValue *Child(const char *name);
-    std::vector ObjectKeys() const;
-
-    std::string Stringify(bool const prettyprint = false) const;
-
-  protected:
-    static JSONValue *Parse(const char **data);
-
-  private:
-    static std::string StringifyString(const std::string &str);
-    std::string StringifyImpl(size_t const indentDepth) const;
-    static std::string Indent(size_t depth);
-
-    JSONType type;
-
-    union {
-        bool bool_value;
-        double number_value;
-        std::string *string_value;
-        JSONArray *array_value;
-        JSONObject *object_value;
-    };
-};
-
-#endif
\ No newline at end of file
diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp
deleted file mode 100644
index 819ba3da5..000000000
--- a/src/serialization/MeshPacketSerializer.cpp
+++ /dev/null
@@ -1,476 +0,0 @@
-#ifndef NRF52_USE_JSON
-#include "MeshPacketSerializer.h"
-#include "JSON.h"
-#include "NodeDB.h"
-#include "mesh/generated/meshtastic/mqtt.pb.h"
-#include "mesh/generated/meshtastic/telemetry.pb.h"
-#include "modules/RoutingModule.h"
-#include 
-#include 
-#if defined(ARCH_ESP32)
-#include "../mesh/generated/meshtastic/paxcount.pb.h"
-#endif
-#include "mesh/generated/meshtastic/remote_hardware.pb.h"
-#include 
-
-static const char *errStr = "Error decoding proto for %s message!";
-
-std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog)
-{
-    // the created jsonObj is immutable after creation, so
-    // we need to do the heavy lifting before assembling it.
-    std::string msgType;
-    JSONObject jsonObj;
-
-    if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
-        JSONObject msgPayload;
-        switch (mp->decoded.portnum) {
-        case meshtastic_PortNum_TEXT_MESSAGE_APP: {
-            msgType = "text";
-            // convert bytes to string
-            if (shouldLog)
-                LOG_DEBUG("got text message of size %u", mp->decoded.payload.size);
-
-            char payloadStr[(mp->decoded.payload.size) + 1];
-            memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size);
-            payloadStr[mp->decoded.payload.size] = 0; // null terminated string
-            // check if this is a JSON payload
-            JSONValue *json_value = JSON::Parse(payloadStr);
-            if (json_value != NULL) {
-                if (shouldLog)
-                    LOG_INFO("text message payload is of type json");
-
-                // if it is, then we can just use the json object
-                jsonObj["payload"] = json_value;
-            } else {
-                // if it isn't, then we need to create a json object
-                // with the string as the value
-                if (shouldLog)
-                    LOG_INFO("text message payload is of type plaintext");
-
-                msgPayload["text"] = new JSONValue(payloadStr);
-                jsonObj["payload"] = new JSONValue(msgPayload);
-            }
-            break;
-        }
-        case meshtastic_PortNum_TELEMETRY_APP: {
-            msgType = "telemetry";
-            meshtastic_Telemetry scratch;
-            meshtastic_Telemetry *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) {
-                decoded = &scratch;
-                if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) {
-                    // If battery is present, encode the battery level value
-                    // TODO - Add a condition to send a code for a non-present value
-                    if (decoded->variant.device_metrics.has_battery_level) {
-                        msgPayload["battery_level"] = new JSONValue((int)decoded->variant.device_metrics.battery_level);
-                    }
-                    msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage);
-                    msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization);
-                    msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx);
-                    msgPayload["uptime_seconds"] = new JSONValue((unsigned int)decoded->variant.device_metrics.uptime_seconds);
-                } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) {
-                    // Avoid sending 0s for sensors that could be 0
-                    if (decoded->variant.environment_metrics.has_temperature) {
-                        msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature);
-                    }
-                    if (decoded->variant.environment_metrics.has_relative_humidity) {
-                        msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity);
-                    }
-                    if (decoded->variant.environment_metrics.has_barometric_pressure) {
-                        msgPayload["barometric_pressure"] =
-                            new JSONValue(decoded->variant.environment_metrics.barometric_pressure);
-                    }
-                    if (decoded->variant.environment_metrics.has_gas_resistance) {
-                        msgPayload["gas_resistance"] = new JSONValue(decoded->variant.environment_metrics.gas_resistance);
-                    }
-                    if (decoded->variant.environment_metrics.has_voltage) {
-                        msgPayload["voltage"] = new JSONValue(decoded->variant.environment_metrics.voltage);
-                    }
-                    if (decoded->variant.environment_metrics.has_current) {
-                        msgPayload["current"] = new JSONValue(decoded->variant.environment_metrics.current);
-                    }
-                    if (decoded->variant.environment_metrics.has_lux) {
-                        msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux);
-                    }
-                    if (decoded->variant.environment_metrics.has_white_lux) {
-                        msgPayload["white_lux"] = new JSONValue(decoded->variant.environment_metrics.white_lux);
-                    }
-                    if (decoded->variant.environment_metrics.has_iaq) {
-                        msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq);
-                    }
-                    if (decoded->variant.environment_metrics.has_distance) {
-                        msgPayload["distance"] = new JSONValue(decoded->variant.environment_metrics.distance);
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_speed) {
-                        msgPayload["wind_speed"] = new JSONValue(decoded->variant.environment_metrics.wind_speed);
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_direction) {
-                        msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction);
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_gust) {
-                        msgPayload["wind_gust"] = new JSONValue(decoded->variant.environment_metrics.wind_gust);
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_lull) {
-                        msgPayload["wind_lull"] = new JSONValue(decoded->variant.environment_metrics.wind_lull);
-                    }
-                    if (decoded->variant.environment_metrics.has_radiation) {
-                        msgPayload["radiation"] = new JSONValue(decoded->variant.environment_metrics.radiation);
-                    }
-                    if (decoded->variant.environment_metrics.has_ir_lux) {
-                        msgPayload["ir_lux"] = new JSONValue(decoded->variant.environment_metrics.ir_lux);
-                    }
-                    if (decoded->variant.environment_metrics.has_uv_lux) {
-                        msgPayload["uv_lux"] = new JSONValue(decoded->variant.environment_metrics.uv_lux);
-                    }
-                    if (decoded->variant.environment_metrics.has_weight) {
-                        msgPayload["weight"] = new JSONValue(decoded->variant.environment_metrics.weight);
-                    }
-                    if (decoded->variant.environment_metrics.has_rainfall_1h) {
-                        msgPayload["rainfall_1h"] = new JSONValue(decoded->variant.environment_metrics.rainfall_1h);
-                    }
-                    if (decoded->variant.environment_metrics.has_rainfall_24h) {
-                        msgPayload["rainfall_24h"] = new JSONValue(decoded->variant.environment_metrics.rainfall_24h);
-                    }
-                    if (decoded->variant.environment_metrics.has_soil_moisture) {
-                        msgPayload["soil_moisture"] = new JSONValue((uint)decoded->variant.environment_metrics.soil_moisture);
-                    }
-                    if (decoded->variant.environment_metrics.has_soil_temperature) {
-                        msgPayload["soil_temperature"] = new JSONValue(decoded->variant.environment_metrics.soil_temperature);
-                    }
-                } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
-                    if (decoded->variant.air_quality_metrics.has_pm10_standard) {
-                        msgPayload["pm10"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_standard);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_pm25_standard) {
-                        msgPayload["pm25"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_standard);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_pm100_standard) {
-                        msgPayload["pm100"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_standard);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_co2) {
-                        msgPayload["co2"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.co2);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_co2_temperature) {
-                        msgPayload["co2_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.co2_temperature);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_co2_humidity) {
-                        msgPayload["co2_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.co2_humidity);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_form_formaldehyde) {
-                        msgPayload["form_formaldehyde"] = new JSONValue(decoded->variant.air_quality_metrics.form_formaldehyde);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_form_temperature) {
-                        msgPayload["form_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.form_temperature);
-                    }
-                    if (decoded->variant.air_quality_metrics.has_form_humidity) {
-                        msgPayload["form_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.form_humidity);
-                    }
-                } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
-                    if (decoded->variant.power_metrics.has_ch1_voltage) {
-                        msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage);
-                    }
-                    if (decoded->variant.power_metrics.has_ch1_current) {
-                        msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current);
-                    }
-                    if (decoded->variant.power_metrics.has_ch2_voltage) {
-                        msgPayload["voltage_ch2"] = new JSONValue(decoded->variant.power_metrics.ch2_voltage);
-                    }
-                    if (decoded->variant.power_metrics.has_ch2_current) {
-                        msgPayload["current_ch2"] = new JSONValue(decoded->variant.power_metrics.ch2_current);
-                    }
-                    if (decoded->variant.power_metrics.has_ch3_voltage) {
-                        msgPayload["voltage_ch3"] = new JSONValue(decoded->variant.power_metrics.ch3_voltage);
-                    }
-                    if (decoded->variant.power_metrics.has_ch3_current) {
-                        msgPayload["current_ch3"] = new JSONValue(decoded->variant.power_metrics.ch3_current);
-                    }
-                }
-                jsonObj["payload"] = new JSONValue(msgPayload);
-            } else if (shouldLog) {
-                LOG_ERROR(errStr, msgType.c_str());
-            }
-            break;
-        }
-        case meshtastic_PortNum_NODEINFO_APP: {
-            msgType = "nodeinfo";
-            meshtastic_User scratch;
-            meshtastic_User *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) {
-                decoded = &scratch;
-                msgPayload["id"] = new JSONValue(decoded->id);
-                msgPayload["longname"] = new JSONValue(decoded->long_name);
-                msgPayload["shortname"] = new JSONValue(decoded->short_name);
-                msgPayload["hardware"] = new JSONValue(decoded->hw_model);
-                msgPayload["role"] = new JSONValue((int)decoded->role);
-                jsonObj["payload"] = new JSONValue(msgPayload);
-            } else if (shouldLog) {
-                LOG_ERROR(errStr, msgType.c_str());
-            }
-            break;
-        }
-        case meshtastic_PortNum_POSITION_APP: {
-            msgType = "position";
-            meshtastic_Position scratch;
-            meshtastic_Position *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) {
-                decoded = &scratch;
-                if ((int)decoded->time) {
-                    msgPayload["time"] = new JSONValue((unsigned int)decoded->time);
-                }
-                if ((int)decoded->timestamp) {
-                    msgPayload["timestamp"] = new JSONValue((unsigned int)decoded->timestamp);
-                }
-                msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i);
-                msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i);
-                if ((int)decoded->altitude) {
-                    msgPayload["altitude"] = new JSONValue((int)decoded->altitude);
-                }
-                if ((int)decoded->ground_speed) {
-                    msgPayload["ground_speed"] = new JSONValue((unsigned int)decoded->ground_speed);
-                }
-                if (int(decoded->ground_track)) {
-                    msgPayload["ground_track"] = new JSONValue((unsigned int)decoded->ground_track);
-                }
-                if (int(decoded->sats_in_view)) {
-                    msgPayload["sats_in_view"] = new JSONValue((unsigned int)decoded->sats_in_view);
-                }
-                if ((int)decoded->PDOP) {
-                    msgPayload["PDOP"] = new JSONValue((int)decoded->PDOP);
-                }
-                if ((int)decoded->HDOP) {
-                    msgPayload["HDOP"] = new JSONValue((int)decoded->HDOP);
-                }
-                if ((int)decoded->VDOP) {
-                    msgPayload["VDOP"] = new JSONValue((int)decoded->VDOP);
-                }
-                if ((int)decoded->precision_bits) {
-                    msgPayload["precision_bits"] = new JSONValue((int)decoded->precision_bits);
-                }
-                jsonObj["payload"] = new JSONValue(msgPayload);
-            } else if (shouldLog) {
-                LOG_ERROR(errStr, msgType.c_str());
-            }
-            break;
-        }
-        case meshtastic_PortNum_WAYPOINT_APP: {
-            msgType = "waypoint";
-            meshtastic_Waypoint scratch;
-            meshtastic_Waypoint *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) {
-                decoded = &scratch;
-                msgPayload["id"] = new JSONValue((unsigned int)decoded->id);
-                msgPayload["name"] = new JSONValue(decoded->name);
-                msgPayload["description"] = new JSONValue(decoded->description);
-                msgPayload["expire"] = new JSONValue((unsigned int)decoded->expire);
-                msgPayload["locked_to"] = new JSONValue((unsigned int)decoded->locked_to);
-                msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i);
-                msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i);
-                jsonObj["payload"] = new JSONValue(msgPayload);
-            } else if (shouldLog) {
-                LOG_ERROR(errStr, msgType.c_str());
-            }
-            break;
-        }
-        case meshtastic_PortNum_NEIGHBORINFO_APP: {
-            msgType = "neighborinfo";
-            meshtastic_NeighborInfo scratch;
-            meshtastic_NeighborInfo *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg,
-                                     &scratch)) {
-                decoded = &scratch;
-                msgPayload["node_id"] = new JSONValue((unsigned int)decoded->node_id);
-                msgPayload["node_broadcast_interval_secs"] = new JSONValue((unsigned int)decoded->node_broadcast_interval_secs);
-                msgPayload["last_sent_by_id"] = new JSONValue((unsigned int)decoded->last_sent_by_id);
-                msgPayload["neighbors_count"] = new JSONValue(decoded->neighbors_count);
-                JSONArray neighbors;
-                for (uint8_t i = 0; i < decoded->neighbors_count; i++) {
-                    JSONObject neighborObj;
-                    neighborObj["node_id"] = new JSONValue((unsigned int)decoded->neighbors[i].node_id);
-                    neighborObj["snr"] = new JSONValue((int)decoded->neighbors[i].snr);
-                    neighbors.push_back(new JSONValue(neighborObj));
-                }
-                msgPayload["neighbors"] = new JSONValue(neighbors);
-                jsonObj["payload"] = new JSONValue(msgPayload);
-            } else if (shouldLog) {
-                LOG_ERROR(errStr, msgType.c_str());
-            }
-            break;
-        }
-        case meshtastic_PortNum_TRACEROUTE_APP: {
-            if (mp->decoded.request_id) { // Only report the traceroute response
-                msgType = "traceroute";
-                meshtastic_RouteDiscovery scratch;
-                meshtastic_RouteDiscovery *decoded = NULL;
-                memset(&scratch, 0, sizeof(scratch));
-                if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg,
-                                         &scratch)) {
-                    decoded = &scratch;
-                    JSONArray route;      // Route this message took
-                    JSONArray routeBack;  // Route this message took back
-                    JSONArray snrTowards; // Snr for forward route
-                    JSONArray snrBack;    // Snr for reverse route
-
-                    // Lambda function for adding a long name to the route
-                    auto addToRoute = [](JSONArray *route, NodeNum num) {
-                        char long_name[40] = "Unknown";
-                        meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num);
-                        bool name_known = node ? node->has_user : false;
-                        if (name_known)
-                            memcpy(long_name, node->user.long_name, sizeof(long_name));
-                        route->push_back(new JSONValue(long_name));
-                    };
-                    addToRoute(&route, mp->to); // Started at the original transmitter (destination of response)
-                    for (uint8_t i = 0; i < decoded->route_count; i++) {
-                        addToRoute(&route, decoded->route[i]);
-                    }
-                    addToRoute(&route, mp->from); // Ended at the original destination (source of response)
-
-                    addToRoute(&routeBack, mp->from); // Started at the original destination (source of response)
-                    for (uint8_t i = 0; i < decoded->route_back_count; i++) {
-                        addToRoute(&routeBack, decoded->route_back[i]);
-                    }
-                    addToRoute(&routeBack, mp->to); // Ended at the original transmitter (destination of response)
-
-                    for (uint8_t i = 0; i < decoded->snr_back_count; i++) {
-                        snrBack.push_back(new JSONValue((float)decoded->snr_back[i] / 4));
-                    }
-
-                    for (uint8_t i = 0; i < decoded->snr_towards_count; i++) {
-                        snrTowards.push_back(new JSONValue((float)decoded->snr_towards[i] / 4));
-                    }
-
-                    msgPayload["route"] = new JSONValue(route);
-                    msgPayload["route_back"] = new JSONValue(routeBack);
-                    msgPayload["snr_back"] = new JSONValue(snrBack);
-                    msgPayload["snr_towards"] = new JSONValue(snrTowards);
-                    jsonObj["payload"] = new JSONValue(msgPayload);
-                } else if (shouldLog) {
-                    LOG_ERROR(errStr, msgType.c_str());
-                }
-            }
-            break;
-        }
-        case meshtastic_PortNum_DETECTION_SENSOR_APP: {
-            msgType = "detection";
-            char payloadStr[(mp->decoded.payload.size) + 1];
-            memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size);
-            payloadStr[mp->decoded.payload.size] = 0; // null terminated string
-            msgPayload["text"] = new JSONValue(payloadStr);
-            jsonObj["payload"] = new JSONValue(msgPayload);
-            break;
-        }
-#ifdef ARCH_ESP32
-        case meshtastic_PortNum_PAXCOUNTER_APP: {
-            msgType = "paxcounter";
-            meshtastic_Paxcount scratch;
-            meshtastic_Paxcount *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Paxcount_msg, &scratch)) {
-                decoded = &scratch;
-                msgPayload["wifi_count"] = new JSONValue((unsigned int)decoded->wifi);
-                msgPayload["ble_count"] = new JSONValue((unsigned int)decoded->ble);
-                msgPayload["uptime"] = new JSONValue((unsigned int)decoded->uptime);
-                jsonObj["payload"] = new JSONValue(msgPayload);
-            } else if (shouldLog) {
-                LOG_ERROR(errStr, msgType.c_str());
-            }
-            break;
-        }
-#endif
-        case meshtastic_PortNum_REMOTE_HARDWARE_APP: {
-            meshtastic_HardwareMessage scratch;
-            meshtastic_HardwareMessage *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg,
-                                     &scratch)) {
-                decoded = &scratch;
-                if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) {
-                    msgType = "gpios_changed";
-                    msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value);
-                    jsonObj["payload"] = new JSONValue(msgPayload);
-                } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) {
-                    msgType = "gpios_read_reply";
-                    msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value);
-                    msgPayload["gpio_mask"] = new JSONValue((unsigned int)decoded->gpio_mask);
-                    jsonObj["payload"] = new JSONValue(msgPayload);
-                }
-            } else if (shouldLog) {
-                LOG_ERROR(errStr, "RemoteHardware");
-            }
-            break;
-        }
-        // add more packet types here if needed
-        default:
-            break;
-        }
-    } else if (shouldLog) {
-        LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON");
-    }
-
-    jsonObj["id"] = new JSONValue((unsigned int)mp->id);
-    jsonObj["timestamp"] = new JSONValue((unsigned int)mp->rx_time);
-    jsonObj["to"] = new JSONValue((unsigned int)mp->to);
-    jsonObj["from"] = new JSONValue((unsigned int)mp->from);
-    jsonObj["channel"] = new JSONValue((unsigned int)mp->channel);
-    jsonObj["type"] = new JSONValue(msgType.c_str());
-    jsonObj["sender"] = new JSONValue(nodeDB->getNodeId().c_str());
-    if (mp->rx_rssi != 0)
-        jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
-    if (mp->rx_snr != 0)
-        jsonObj["snr"] = new JSONValue((float)mp->rx_snr);
-    const int8_t hopsAway = getHopsAway(*mp);
-    if (hopsAway >= 0) {
-        jsonObj["hops_away"] = new JSONValue((unsigned int)(hopsAway));
-        jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start));
-    }
-
-    // serialize and write it to the stream
-    JSONValue *value = new JSONValue(jsonObj);
-    std::string jsonStr = value->Stringify();
-
-    if (shouldLog)
-        LOG_INFO("serialized json message: %s", jsonStr.c_str());
-
-    delete value;
-    return jsonStr;
-}
-
-std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp)
-{
-    JSONObject jsonObj;
-
-    jsonObj["id"] = new JSONValue((unsigned int)mp->id);
-    jsonObj["time_ms"] = new JSONValue((double)millis());
-    jsonObj["timestamp"] = new JSONValue((unsigned int)mp->rx_time);
-    jsonObj["to"] = new JSONValue((unsigned int)mp->to);
-    jsonObj["from"] = new JSONValue((unsigned int)mp->from);
-    jsonObj["channel"] = new JSONValue((unsigned int)mp->channel);
-    jsonObj["want_ack"] = new JSONValue(mp->want_ack);
-
-    if (mp->rx_rssi != 0)
-        jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
-    if (mp->rx_snr != 0)
-        jsonObj["snr"] = new JSONValue((float)mp->rx_snr);
-    const int8_t hopsAway = getHopsAway(*mp);
-    if (hopsAway >= 0) {
-        jsonObj["hops_away"] = new JSONValue((unsigned int)(hopsAway));
-        jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start));
-    }
-    jsonObj["size"] = new JSONValue((unsigned int)mp->encrypted.size);
-    auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size);
-    jsonObj["bytes"] = new JSONValue(encryptedStr.c_str());
-
-    // serialize and write it to the stream
-    JSONValue *value = new JSONValue(jsonObj);
-    std::string jsonStr = value->Stringify();
-
-    delete value;
-    return jsonStr;
-}
-#endif
\ No newline at end of file
diff --git a/src/serialization/MeshPacketSerializer.h b/src/serialization/MeshPacketSerializer.h
deleted file mode 100644
index 03860ab35..000000000
--- a/src/serialization/MeshPacketSerializer.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#include 
-#include 
-
-static const char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
-
-class MeshPacketSerializer
-{
-  public:
-    static std::string JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog = true);
-    static std::string JsonSerializeEncrypted(const meshtastic_MeshPacket *mp);
-
-  private:
-    static std::string bytesToHex(const uint8_t *bytes, int len)
-    {
-        std::string result = "";
-        for (int i = 0; i < len; ++i) {
-            char const byte = bytes[i];
-            result += hexChars[(byte & 0xF0) >> 4];
-            result += hexChars[(byte & 0x0F) >> 0];
-        }
-        return result;
-    }
-};
\ No newline at end of file
diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp
deleted file mode 100644
index bd0a29c51..000000000
--- a/src/serialization/MeshPacketSerializer_nRF52.cpp
+++ /dev/null
@@ -1,421 +0,0 @@
-#ifdef NRF52_USE_JSON
-#warning 'Using nRF52 Serializer'
-
-#include "ArduinoJson.h"
-#include "MeshPacketSerializer.h"
-#include "NodeDB.h"
-#include "mesh/generated/meshtastic/mqtt.pb.h"
-#include "mesh/generated/meshtastic/remote_hardware.pb.h"
-#include "mesh/generated/meshtastic/telemetry.pb.h"
-#include "modules/RoutingModule.h"
-#include 
-#include 
-
-StaticJsonDocument<1024> jsonObj;
-StaticJsonDocument<1024> arrayObj;
-
-std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog)
-{
-    // the created jsonObj is immutable after creation, so
-    // we need to do the heavy lifting before assembling it.
-    std::string msgType;
-    jsonObj.clear();
-    arrayObj.clear();
-
-    if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
-        switch (mp->decoded.portnum) {
-        case meshtastic_PortNum_TEXT_MESSAGE_APP: {
-            msgType = "text";
-            // convert bytes to string
-            if (shouldLog)
-                LOG_DEBUG("got text message of size %u", mp->decoded.payload.size);
-
-            char payloadStr[(mp->decoded.payload.size) + 1];
-            memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size);
-            payloadStr[mp->decoded.payload.size] = 0; // null terminated string
-            // check if this is a JSON payload
-            StaticJsonDocument<512> text_doc;
-            DeserializationError error = deserializeJson(text_doc, payloadStr);
-            if (error) {
-                // if it isn't, then we need to create a json object
-                // with the string as the value
-                if (shouldLog)
-                    LOG_INFO("text message payload is of type plaintext");
-                jsonObj["payload"]["text"] = payloadStr;
-            } else {
-                // if it is, then we can just use the json object
-                if (shouldLog)
-                    LOG_INFO("text message payload is of type json");
-                jsonObj["payload"] = text_doc;
-            }
-            break;
-        }
-        case meshtastic_PortNum_TELEMETRY_APP: {
-            msgType = "telemetry";
-            meshtastic_Telemetry scratch;
-            meshtastic_Telemetry *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) {
-                decoded = &scratch;
-                if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) {
-                    // If battery is present, encode the battery level value
-                    // TODO - Add a condition to send a code for a non-present value
-                    if (decoded->variant.device_metrics.has_battery_level) {
-                        jsonObj["payload"]["battery_level"] = (int)decoded->variant.device_metrics.battery_level;
-                    }
-                    jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage;
-                    jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization;
-                    jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx;
-                    jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds;
-                } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) {
-                    if (decoded->variant.environment_metrics.has_temperature) {
-                        jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature;
-                    }
-                    if (decoded->variant.environment_metrics.has_relative_humidity) {
-                        jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity;
-                    }
-                    if (decoded->variant.environment_metrics.has_barometric_pressure) {
-                        jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure;
-                    }
-                    if (decoded->variant.environment_metrics.has_gas_resistance) {
-                        jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance;
-                    }
-                    if (decoded->variant.environment_metrics.has_voltage) {
-                        jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage;
-                    }
-                    if (decoded->variant.environment_metrics.has_current) {
-                        jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current;
-                    }
-                    if (decoded->variant.environment_metrics.has_lux) {
-                        jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux;
-                    }
-                    if (decoded->variant.environment_metrics.has_white_lux) {
-                        jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux;
-                    }
-                    if (decoded->variant.environment_metrics.has_iaq) {
-                        jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq;
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_speed) {
-                        jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed;
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_direction) {
-                        jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction;
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_gust) {
-                        jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust;
-                    }
-                    if (decoded->variant.environment_metrics.has_wind_lull) {
-                        jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull;
-                    }
-                    if (decoded->variant.environment_metrics.has_radiation) {
-                        jsonObj["payload"]["radiation"] = decoded->variant.environment_metrics.radiation;
-                    }
-                } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
-                    if (decoded->variant.air_quality_metrics.has_pm10_standard) {
-                        jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_pm25_standard) {
-                        jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_pm100_standard) {
-                        jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_co2) {
-                        jsonObj["payload"]["co2"] = (unsigned int)decoded->variant.air_quality_metrics.co2;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_co2_temperature) {
-                        jsonObj["payload"]["co2_temperature"] = decoded->variant.air_quality_metrics.co2_temperature;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_co2_humidity) {
-                        jsonObj["payload"]["co2_humidity"] = decoded->variant.air_quality_metrics.co2_humidity;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_form_formaldehyde) {
-                        jsonObj["payload"]["form_formaldehyde"] = decoded->variant.air_quality_metrics.form_formaldehyde;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_form_temperature) {
-                        jsonObj["payload"]["form_temperature"] = decoded->variant.air_quality_metrics.form_temperature;
-                    }
-                    if (decoded->variant.air_quality_metrics.has_form_humidity) {
-                        jsonObj["payload"]["form_humidity"] = decoded->variant.air_quality_metrics.form_humidity;
-                    }
-                } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
-                    if (decoded->variant.power_metrics.has_ch1_voltage) {
-                        jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage;
-                    }
-                    if (decoded->variant.power_metrics.has_ch1_current) {
-                        jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current;
-                    }
-                    if (decoded->variant.power_metrics.has_ch2_voltage) {
-                        jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage;
-                    }
-                    if (decoded->variant.power_metrics.has_ch2_current) {
-                        jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current;
-                    }
-                    if (decoded->variant.power_metrics.has_ch3_voltage) {
-                        jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage;
-                    }
-                    if (decoded->variant.power_metrics.has_ch3_current) {
-                        jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current;
-                    }
-                }
-            } else if (shouldLog) {
-                LOG_ERROR("Error decoding proto for telemetry message!");
-                return "";
-            }
-            break;
-        }
-        case meshtastic_PortNum_NODEINFO_APP: {
-            msgType = "nodeinfo";
-            meshtastic_User scratch;
-            meshtastic_User *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) {
-                decoded = &scratch;
-                jsonObj["payload"]["id"] = decoded->id;
-                jsonObj["payload"]["longname"] = decoded->long_name;
-                jsonObj["payload"]["shortname"] = decoded->short_name;
-                jsonObj["payload"]["hardware"] = decoded->hw_model;
-                jsonObj["payload"]["role"] = (int)decoded->role;
-            } else if (shouldLog) {
-                LOG_ERROR("Error decoding proto for nodeinfo message!");
-                return "";
-            }
-            break;
-        }
-        case meshtastic_PortNum_POSITION_APP: {
-            msgType = "position";
-            meshtastic_Position scratch;
-            meshtastic_Position *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) {
-                decoded = &scratch;
-                if ((int)decoded->time) {
-                    jsonObj["payload"]["time"] = (unsigned int)decoded->time;
-                }
-                if ((int)decoded->timestamp) {
-                    jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp;
-                }
-                jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i;
-                jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i;
-                if ((int)decoded->altitude) {
-                    jsonObj["payload"]["altitude"] = (int)decoded->altitude;
-                }
-                if ((int)decoded->ground_speed) {
-                    jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed;
-                }
-                if (int(decoded->ground_track)) {
-                    jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track;
-                }
-                if (int(decoded->sats_in_view)) {
-                    jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view;
-                }
-                if ((int)decoded->PDOP) {
-                    jsonObj["payload"]["PDOP"] = (int)decoded->PDOP;
-                }
-                if ((int)decoded->HDOP) {
-                    jsonObj["payload"]["HDOP"] = (int)decoded->HDOP;
-                }
-                if ((int)decoded->VDOP) {
-                    jsonObj["payload"]["VDOP"] = (int)decoded->VDOP;
-                }
-                if ((int)decoded->precision_bits) {
-                    jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits;
-                }
-            } else if (shouldLog) {
-                LOG_ERROR("Error decoding proto for position message!");
-                return "";
-            }
-            break;
-        }
-        case meshtastic_PortNum_WAYPOINT_APP: {
-            msgType = "position";
-            meshtastic_Waypoint scratch;
-            meshtastic_Waypoint *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) {
-                decoded = &scratch;
-                jsonObj["payload"]["id"] = (unsigned int)decoded->id;
-                jsonObj["payload"]["name"] = decoded->name;
-                jsonObj["payload"]["description"] = decoded->description;
-                jsonObj["payload"]["expire"] = (unsigned int)decoded->expire;
-                jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to;
-                jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i;
-                jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i;
-            } else if (shouldLog) {
-                LOG_ERROR("Error decoding proto for position message!");
-                return "";
-            }
-            break;
-        }
-        case meshtastic_PortNum_NEIGHBORINFO_APP: {
-            msgType = "neighborinfo";
-            meshtastic_NeighborInfo scratch;
-            meshtastic_NeighborInfo *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg,
-                                     &scratch)) {
-                decoded = &scratch;
-                jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id;
-                jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs;
-                jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id;
-                jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count;
-
-                JsonObject neighbors_obj = arrayObj.to();
-                JsonArray neighbors = neighbors_obj.createNestedArray("neighbors");
-                JsonObject neighbors_0 = neighbors.createNestedObject();
-
-                for (uint8_t i = 0; i < decoded->neighbors_count; i++) {
-                    neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id;
-                    neighbors_0["snr"] = (int)decoded->neighbors[i].snr;
-                    neighbors[i + 1] = neighbors_0;
-                    neighbors_0.clear();
-                }
-                neighbors.remove(0);
-                jsonObj["payload"]["neighbors"] = neighbors;
-            } else if (shouldLog) {
-                LOG_ERROR("Error decoding proto for neighborinfo message!");
-                return "";
-            }
-            break;
-        }
-        case meshtastic_PortNum_TRACEROUTE_APP: {
-            if (mp->decoded.request_id) { // Only report the traceroute response
-                msgType = "traceroute";
-                meshtastic_RouteDiscovery scratch;
-                meshtastic_RouteDiscovery *decoded = NULL;
-                memset(&scratch, 0, sizeof(scratch));
-                if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg,
-                                         &scratch)) {
-                    decoded = &scratch;
-                    JsonArray route = arrayObj.createNestedArray("route");
-
-                    auto addToRoute = [](JsonArray *route, NodeNum num) {
-                        char long_name[40] = "Unknown";
-                        meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num);
-                        bool name_known = node ? node->has_user : false;
-                        if (name_known)
-                            memcpy(long_name, node->user.long_name, sizeof(long_name));
-                        route->add(long_name);
-                    };
-
-                    addToRoute(&route, mp->to); // route.add(mp->to);
-                    for (uint8_t i = 0; i < decoded->route_count; i++) {
-                        addToRoute(&route, decoded->route[i]); // route.add(decoded->route[i]);
-                    }
-                    addToRoute(&route,
-                               mp->from); // route.add(mp->from); // Ended at the original destination (source of response)
-
-                    jsonObj["payload"]["route"] = route;
-                } else if (shouldLog) {
-                    LOG_ERROR("Error decoding proto for traceroute message!");
-                    return "";
-                }
-            } else {
-                LOG_WARN("Traceroute response not reported");
-                return "";
-            }
-            break;
-        }
-        case meshtastic_PortNum_DETECTION_SENSOR_APP: {
-            msgType = "detection";
-            char payloadStr[(mp->decoded.payload.size) + 1];
-            memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size);
-            payloadStr[mp->decoded.payload.size] = 0; // null terminated string
-            jsonObj["payload"]["text"] = payloadStr;
-            break;
-        }
-        case meshtastic_PortNum_REMOTE_HARDWARE_APP: {
-            meshtastic_HardwareMessage scratch;
-            meshtastic_HardwareMessage *decoded = NULL;
-            memset(&scratch, 0, sizeof(scratch));
-            if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg,
-                                     &scratch)) {
-                decoded = &scratch;
-                if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) {
-                    msgType = "gpios_changed";
-                    jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value;
-                } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) {
-                    msgType = "gpios_read_reply";
-                    jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value;
-                    jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask;
-                }
-            } else if (shouldLog) {
-                LOG_ERROR("Error decoding proto for RemoteHardware message!");
-                return "";
-            }
-            break;
-        }
-        // add more packet types here if needed
-        default:
-            LOG_WARN("Unsupported packet type %d", mp->decoded.portnum);
-            return "";
-            break;
-        }
-    } else if (shouldLog) {
-        LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON");
-        return "";
-    }
-
-    jsonObj["id"] = (unsigned int)mp->id;
-    jsonObj["timestamp"] = (unsigned int)mp->rx_time;
-    jsonObj["to"] = (unsigned int)mp->to;
-    jsonObj["from"] = (unsigned int)mp->from;
-    jsonObj["channel"] = (unsigned int)mp->channel;
-    jsonObj["type"] = msgType.c_str();
-    jsonObj["sender"] = nodeDB->getNodeId().c_str();
-    if (mp->rx_rssi != 0)
-        jsonObj["rssi"] = (int)mp->rx_rssi;
-    if (mp->rx_snr != 0)
-        jsonObj["snr"] = (float)mp->rx_snr;
-    const int8_t hopsAway = getHopsAway(*mp);
-    if (hopsAway >= 0) {
-        jsonObj["hops_away"] = (unsigned int)(hopsAway);
-        jsonObj["hop_start"] = (unsigned int)(mp->hop_start);
-    }
-
-    // serialize and write it to the stream
-
-    // Serial.printf("serialized json message: \r");
-    // serializeJson(jsonObj, Serial);
-    // Serial.println("");
-
-    std::string jsonStr = "";
-    serializeJson(jsonObj, jsonStr);
-
-    if (shouldLog)
-        LOG_INFO("serialized json message: %s", jsonStr.c_str());
-
-    return jsonStr;
-}
-
-std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp)
-{
-    jsonObj.clear();
-    jsonObj["id"] = (unsigned int)mp->id;
-    jsonObj["time_ms"] = (double)millis();
-    jsonObj["timestamp"] = (unsigned int)mp->rx_time;
-    jsonObj["to"] = (unsigned int)mp->to;
-    jsonObj["from"] = (unsigned int)mp->from;
-    jsonObj["channel"] = (unsigned int)mp->channel;
-    jsonObj["want_ack"] = mp->want_ack;
-
-    if (mp->rx_rssi != 0)
-        jsonObj["rssi"] = (int)mp->rx_rssi;
-    if (mp->rx_snr != 0)
-        jsonObj["snr"] = (float)mp->rx_snr;
-    const int8_t hopsAway = getHopsAway(*mp);
-    if (hopsAway >= 0) {
-        jsonObj["hops_away"] = (unsigned int)(hopsAway);
-        jsonObj["hop_start"] = (unsigned int)(mp->hop_start);
-    }
-    jsonObj["size"] = (unsigned int)mp->encrypted.size;
-    auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size);
-    jsonObj["bytes"] = encryptedStr.c_str();
-
-    // serialize and write it to the stream
-    std::string jsonStr = "";
-    serializeJson(jsonObj, jsonStr);
-
-    return jsonStr;
-}
-#endif
\ No newline at end of file
diff --git a/test/test_meshpacket_serializer/ports/test_encrypted.cpp b/test/test_meshpacket_serializer/ports/test_encrypted.cpp
deleted file mode 100644
index 37cfc1626..000000000
--- a/test/test_meshpacket_serializer/ports/test_encrypted.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-#include "../test_helpers.h"
-
-// Helper function for all encrypted packet assertions
-void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket packet)
-{
-    // Parse and validate JSON
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Assert basic packet fields
-    TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
-    TEST_ASSERT_EQUAL(packet.from, (uint32_t)jsonObj.at("from")->AsNumber());
-
-    TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
-    TEST_ASSERT_EQUAL(packet.to, (uint32_t)jsonObj.at("to")->AsNumber());
-
-    TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
-    TEST_ASSERT_EQUAL(packet.id, (uint32_t)jsonObj.at("id")->AsNumber());
-
-    // Assert encrypted data fields
-    TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj.at("bytes")->IsString());
-
-    TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end());
-    TEST_ASSERT_EQUAL(packet.encrypted.size, (int)jsonObj.at("size")->AsNumber());
-
-    // Assert hex encoding
-    std::string encrypted_hex = jsonObj["bytes"]->AsString();
-    TEST_ASSERT_EQUAL(packet.encrypted.size * 2, encrypted_hex.length());
-
-    delete root;
-}
-
-// Test encrypted packet serialization
-void test_encrypted_packet_serialization()
-{
-    const char *data = "encrypted_payload_data";
-    meshtastic_MeshPacket packet =
-        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(data), strlen(data),
-                           meshtastic_MeshPacket_encrypted_tag);
-    std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
-
-    assert_encrypted_packet(json, packet);
-}
-
-// Test empty encrypted packet
-void test_empty_encrypted_packet()
-{
-    meshtastic_MeshPacket packet =
-        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0, meshtastic_MeshPacket_encrypted_tag);
-    std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
-
-    assert_encrypted_packet(json, packet);
-}
diff --git a/test/test_meshpacket_serializer/ports/test_nodeinfo.cpp b/test/test_meshpacket_serializer/ports/test_nodeinfo.cpp
deleted file mode 100644
index febda9950..000000000
--- a/test/test_meshpacket_serializer/ports/test_nodeinfo.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-#include "../test_helpers.h"
-
-static size_t encode_user_info(uint8_t *buffer, size_t buffer_size)
-{
-    meshtastic_User user = meshtastic_User_init_zero;
-    strcpy(user.short_name, "TEST");
-    strcpy(user.long_name, "Test User");
-    strcpy(user.id, "!12345678");
-    user.hw_model = meshtastic_HardwareModel_HELTEC_V3;
-
-    pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
-    pb_encode(&stream, &meshtastic_User_msg, &user);
-    return stream.bytes_written;
-}
-
-// Test NODEINFO_APP port
-void test_nodeinfo_serialization()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_user_info(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_NODEINFO_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check message type
-    TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
-    TEST_ASSERT_EQUAL_STRING("nodeinfo", jsonObj["type"]->AsString().c_str());
-
-    // Check payload
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // Verify user data
-    TEST_ASSERT_TRUE(payload.find("shortname") != payload.end());
-    TEST_ASSERT_EQUAL_STRING("TEST", payload["shortname"]->AsString().c_str());
-
-    TEST_ASSERT_TRUE(payload.find("longname") != payload.end());
-    TEST_ASSERT_EQUAL_STRING("Test User", payload["longname"]->AsString().c_str());
-
-    delete root;
-}
diff --git a/test/test_meshpacket_serializer/ports/test_position.cpp b/test/test_meshpacket_serializer/ports/test_position.cpp
deleted file mode 100644
index f0dcc0709..000000000
--- a/test/test_meshpacket_serializer/ports/test_position.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#include "../test_helpers.h"
-
-static size_t encode_position(uint8_t *buffer, size_t buffer_size)
-{
-    meshtastic_Position position = meshtastic_Position_init_zero;
-    position.latitude_i = 374208000;    // 37.4208 degrees * 1e7
-    position.longitude_i = -1221981000; // -122.1981 degrees * 1e7
-    position.altitude = 123;
-    position.time = 1609459200;
-    position.has_altitude = true;
-    position.has_latitude_i = true;
-    position.has_longitude_i = true;
-
-    pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
-    pb_encode(&stream, &meshtastic_Position_msg, &position);
-    return stream.bytes_written;
-}
-
-// Test POSITION_APP port
-void test_position_serialization()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_position(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_POSITION_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check message type
-    TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
-    TEST_ASSERT_EQUAL_STRING("position", jsonObj["type"]->AsString().c_str());
-
-    // Check payload
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // Verify position data
-    TEST_ASSERT_TRUE(payload.find("latitude_i") != payload.end());
-    TEST_ASSERT_EQUAL(374208000, (int)payload["latitude_i"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("longitude_i") != payload.end());
-    TEST_ASSERT_EQUAL(-1221981000, (int)payload["longitude_i"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("altitude") != payload.end());
-    TEST_ASSERT_EQUAL(123, (int)payload["altitude"]->AsNumber());
-
-    delete root;
-}
diff --git a/test/test_meshpacket_serializer/ports/test_telemetry.cpp b/test/test_meshpacket_serializer/ports/test_telemetry.cpp
deleted file mode 100644
index a813aaab5..000000000
--- a/test/test_meshpacket_serializer/ports/test_telemetry.cpp
+++ /dev/null
@@ -1,528 +0,0 @@
-#include "../test_helpers.h"
-
-// Helper function to create and encode device metrics
-static size_t encode_telemetry_device_metrics(uint8_t *buffer, size_t buffer_size)
-{
-    meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
-    telemetry.time = 1609459200;
-    telemetry.which_variant = meshtastic_Telemetry_device_metrics_tag;
-    telemetry.variant.device_metrics.battery_level = 85;
-    telemetry.variant.device_metrics.has_battery_level = true;
-    telemetry.variant.device_metrics.voltage = 3.72f;
-    telemetry.variant.device_metrics.has_voltage = true;
-    telemetry.variant.device_metrics.channel_utilization = 15.56f;
-    telemetry.variant.device_metrics.has_channel_utilization = true;
-    telemetry.variant.device_metrics.air_util_tx = 8.23f;
-    telemetry.variant.device_metrics.has_air_util_tx = true;
-    telemetry.variant.device_metrics.uptime_seconds = 12345;
-    telemetry.variant.device_metrics.has_uptime_seconds = true;
-
-    pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
-    pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
-    return stream.bytes_written;
-}
-
-// Helper function to create and encode empty environment metrics (no fields set)
-static size_t encode_telemetry_environment_metrics_empty(uint8_t *buffer, size_t buffer_size)
-{
-    meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
-    telemetry.time = 1609459200;
-    telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
-
-    // NO fields are set - all has_* flags remain false
-    // This tests that empty environment metrics don't produce any JSON fields
-
-    pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
-    pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
-    return stream.bytes_written;
-}
-
-// Helper function to create environment metrics with ALL possible fields set
-// This function should be updated whenever new fields are added to the protobuf
-static size_t encode_telemetry_environment_metrics_all_fields(uint8_t *buffer, size_t buffer_size)
-{
-    meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
-    telemetry.time = 1609459200;
-    telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
-
-    // Basic environment metrics
-    telemetry.variant.environment_metrics.temperature = 23.56f;
-    telemetry.variant.environment_metrics.has_temperature = true;
-    telemetry.variant.environment_metrics.relative_humidity = 65.43f;
-    telemetry.variant.environment_metrics.has_relative_humidity = true;
-    telemetry.variant.environment_metrics.barometric_pressure = 1013.27f;
-    telemetry.variant.environment_metrics.has_barometric_pressure = true;
-
-    // Gas and air quality
-    telemetry.variant.environment_metrics.gas_resistance = 50.58f;
-    telemetry.variant.environment_metrics.has_gas_resistance = true;
-    telemetry.variant.environment_metrics.iaq = 120;
-    telemetry.variant.environment_metrics.has_iaq = true;
-
-    // Power measurements
-    telemetry.variant.environment_metrics.voltage = 3.34f;
-    telemetry.variant.environment_metrics.has_voltage = true;
-    telemetry.variant.environment_metrics.current = 0.53f;
-    telemetry.variant.environment_metrics.has_current = true;
-
-    // Light measurements (ALL 4 types)
-    telemetry.variant.environment_metrics.lux = 450.12f;
-    telemetry.variant.environment_metrics.has_lux = true;
-    telemetry.variant.environment_metrics.white_lux = 380.95f;
-    telemetry.variant.environment_metrics.has_white_lux = true;
-    telemetry.variant.environment_metrics.ir_lux = 25.37f;
-    telemetry.variant.environment_metrics.has_ir_lux = true;
-    telemetry.variant.environment_metrics.uv_lux = 15.68f;
-    telemetry.variant.environment_metrics.has_uv_lux = true;
-
-    // Distance measurement
-    telemetry.variant.environment_metrics.distance = 150.29f;
-    telemetry.variant.environment_metrics.has_distance = true;
-
-    // Wind measurements (ALL 4 types)
-    telemetry.variant.environment_metrics.wind_direction = 180;
-    telemetry.variant.environment_metrics.has_wind_direction = true;
-    telemetry.variant.environment_metrics.wind_speed = 5.52f;
-    telemetry.variant.environment_metrics.has_wind_speed = true;
-    telemetry.variant.environment_metrics.wind_gust = 8.24f;
-    telemetry.variant.environment_metrics.has_wind_gust = true;
-    telemetry.variant.environment_metrics.wind_lull = 2.13f;
-    telemetry.variant.environment_metrics.has_wind_lull = true;
-
-    // Weight measurement
-    telemetry.variant.environment_metrics.weight = 75.56f;
-    telemetry.variant.environment_metrics.has_weight = true;
-
-    // Radiation measurement
-    telemetry.variant.environment_metrics.radiation = 0.13f;
-    telemetry.variant.environment_metrics.has_radiation = true;
-
-    // Rainfall measurements (BOTH types)
-    telemetry.variant.environment_metrics.rainfall_1h = 2.57f;
-    telemetry.variant.environment_metrics.has_rainfall_1h = true;
-    telemetry.variant.environment_metrics.rainfall_24h = 15.89f;
-    telemetry.variant.environment_metrics.has_rainfall_24h = true;
-
-    // Soil measurements (BOTH types)
-    telemetry.variant.environment_metrics.soil_moisture = 85;
-    telemetry.variant.environment_metrics.has_soil_moisture = true;
-    telemetry.variant.environment_metrics.soil_temperature = 18.54f;
-    telemetry.variant.environment_metrics.has_soil_temperature = true;
-
-    // IMPORTANT: When new environment fields are added to the protobuf,
-    // they MUST be added here too, or the coverage test will fail!
-
-    pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
-    pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
-    return stream.bytes_written;
-}
-
-// Helper function to create and encode environment metrics with all current fields
-static size_t encode_telemetry_environment_metrics(uint8_t *buffer, size_t buffer_size)
-{
-    meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
-    telemetry.time = 1609459200;
-    telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
-
-    // Basic environment metrics
-    telemetry.variant.environment_metrics.temperature = 23.56f;
-    telemetry.variant.environment_metrics.has_temperature = true;
-    telemetry.variant.environment_metrics.relative_humidity = 65.43f;
-    telemetry.variant.environment_metrics.has_relative_humidity = true;
-    telemetry.variant.environment_metrics.barometric_pressure = 1013.27f;
-    telemetry.variant.environment_metrics.has_barometric_pressure = true;
-
-    // Gas and air quality
-    telemetry.variant.environment_metrics.gas_resistance = 50.58f;
-    telemetry.variant.environment_metrics.has_gas_resistance = true;
-    telemetry.variant.environment_metrics.iaq = 120;
-    telemetry.variant.environment_metrics.has_iaq = true;
-
-    // Power measurements
-    telemetry.variant.environment_metrics.voltage = 3.34f;
-    telemetry.variant.environment_metrics.has_voltage = true;
-    telemetry.variant.environment_metrics.current = 0.53f;
-    telemetry.variant.environment_metrics.has_current = true;
-
-    // Light measurements
-    telemetry.variant.environment_metrics.lux = 450.12f;
-    telemetry.variant.environment_metrics.has_lux = true;
-    telemetry.variant.environment_metrics.white_lux = 380.95f;
-    telemetry.variant.environment_metrics.has_white_lux = true;
-    telemetry.variant.environment_metrics.ir_lux = 25.37f;
-    telemetry.variant.environment_metrics.has_ir_lux = true;
-    telemetry.variant.environment_metrics.uv_lux = 15.68f;
-    telemetry.variant.environment_metrics.has_uv_lux = true;
-
-    // Distance measurement
-    telemetry.variant.environment_metrics.distance = 150.29f;
-    telemetry.variant.environment_metrics.has_distance = true;
-
-    // Wind measurements
-    telemetry.variant.environment_metrics.wind_direction = 180;
-    telemetry.variant.environment_metrics.has_wind_direction = true;
-    telemetry.variant.environment_metrics.wind_speed = 5.52f;
-    telemetry.variant.environment_metrics.has_wind_speed = true;
-    telemetry.variant.environment_metrics.wind_gust = 8.24f;
-    telemetry.variant.environment_metrics.has_wind_gust = true;
-    telemetry.variant.environment_metrics.wind_lull = 2.13f;
-    telemetry.variant.environment_metrics.has_wind_lull = true;
-
-    // Weight measurement
-    telemetry.variant.environment_metrics.weight = 75.56f;
-    telemetry.variant.environment_metrics.has_weight = true;
-
-    // Radiation measurement
-    telemetry.variant.environment_metrics.radiation = 0.13f;
-    telemetry.variant.environment_metrics.has_radiation = true;
-
-    // Rainfall measurements
-    telemetry.variant.environment_metrics.rainfall_1h = 2.57f;
-    telemetry.variant.environment_metrics.has_rainfall_1h = true;
-    telemetry.variant.environment_metrics.rainfall_24h = 15.89f;
-    telemetry.variant.environment_metrics.has_rainfall_24h = true;
-
-    // Soil measurements
-    telemetry.variant.environment_metrics.soil_moisture = 85;
-    telemetry.variant.environment_metrics.has_soil_moisture = true;
-    telemetry.variant.environment_metrics.soil_temperature = 18.54f;
-    telemetry.variant.environment_metrics.has_soil_temperature = true;
-
-    pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
-    pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
-    return stream.bytes_written;
-}
-
-// Test TELEMETRY_APP port with device metrics
-void test_telemetry_device_metrics_serialization()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_telemetry_device_metrics(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check message type
-    TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
-    TEST_ASSERT_EQUAL_STRING("telemetry", jsonObj["type"]->AsString().c_str());
-
-    // Check payload
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // Verify telemetry data
-    TEST_ASSERT_TRUE(payload.find("battery_level") != payload.end());
-    TEST_ASSERT_EQUAL(85, (int)payload["battery_level"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.72f, payload["voltage"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("channel_utilization") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.56f, payload["channel_utilization"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("uptime_seconds") != payload.end());
-    TEST_ASSERT_EQUAL(12345, (int)payload["uptime_seconds"]->AsNumber());
-
-    // Note: JSON serialization may not preserve exact 2-decimal formatting due to float precision
-    // We verify the numeric values are correct within tolerance
-
-    delete root;
-}
-
-// Test that telemetry environment metrics are properly serialized
-void test_telemetry_environment_metrics_serialization()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check payload exists
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // Test key fields that should be present in the serializer
-    TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber());
-
-    // Note: JSON serialization may have float precision limitations
-    // We focus on verifying numeric accuracy rather than exact string formatting
-
-    delete root;
-}
-
-// Test comprehensive environment metrics coverage
-void test_telemetry_environment_metrics_comprehensive()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check payload exists
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // Check all 15 originally supported fields
-    TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("current") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("iaq") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("lux") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end());
-    TEST_ASSERT_TRUE(payload.find("radiation") != payload.end());
-
-    delete root;
-}
-
-// Test for the 7 environment fields that were added to complete coverage
-void test_telemetry_environment_metrics_missing_fields()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check payload exists
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // Check the 7 fields that were previously missing
-    TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("weight") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end());
-    TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber());
-
-    // Note: JSON float serialization may not preserve exact decimal formatting
-    // We verify the values are numerically correct within tolerance
-
-    delete root;
-}
-
-// Test that ALL environment fields are serialized (canary test for forgotten fields)
-// This test will FAIL if a new environment field is added to the protobuf but not to the serializer
-void test_telemetry_environment_metrics_complete_coverage()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_telemetry_environment_metrics_all_fields(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check payload exists
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // ✅ ALL 22 environment fields MUST be present and correct
-    // If this test fails, it means either:
-    // 1. A new field was added to the protobuf but not to the serializer
-    // 2. The encode_telemetry_environment_metrics_all_fields() function wasn't updated
-
-    // Basic environment (3 fields)
-    TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 1013.27f, payload["barometric_pressure"]->AsNumber());
-
-    // Gas and air quality (2 fields)
-    TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 50.58f, payload["gas_resistance"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("iaq") != payload.end());
-    TEST_ASSERT_EQUAL(120, (int)payload["iaq"]->AsNumber());
-
-    // Power measurements (2 fields)
-    TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.34f, payload["voltage"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("current") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.53f, payload["current"]->AsNumber());
-
-    // Light measurements (4 fields)
-    TEST_ASSERT_TRUE(payload.find("lux") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 450.12f, payload["lux"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 380.95f, payload["white_lux"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber());
-
-    // Distance measurement (1 field)
-    TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber());
-
-    // Wind measurements (4 fields)
-    TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end());
-    TEST_ASSERT_EQUAL(180, (int)payload["wind_direction"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 5.52f, payload["wind_speed"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 8.24f, payload["wind_gust"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.13f, payload["wind_lull"]->AsNumber());
-
-    // Weight measurement (1 field)
-    TEST_ASSERT_TRUE(payload.find("weight") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber());
-
-    // Radiation measurement (1 field)
-    TEST_ASSERT_TRUE(payload.find("radiation") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.13f, payload["radiation"]->AsNumber());
-
-    // Rainfall measurements (2 fields)
-    TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber());
-
-    // Soil measurements (2 fields)
-    TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end());
-    TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber());
-    TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end());
-    TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber());
-
-    // Total: 22 environment fields
-    // This test ensures 100% coverage of environment metrics
-
-    // Note: JSON float serialization precision may vary due to the underlying library
-    // The important aspect is that all values are numerically accurate within tolerance
-
-    delete root;
-}
-
-// Test that unset environment fields are not present in JSON
-void test_telemetry_environment_metrics_unset_fields()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_telemetry_environment_metrics_empty(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check payload exists
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // With completely empty environment metrics, NO fields should be present
-    // Only basic telemetry fields like "time" might be present
-
-    // All 22 environment fields should be absent (none were set)
-    TEST_ASSERT_TRUE(payload.find("temperature") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("relative_humidity") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("barometric_pressure") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("gas_resistance") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("iaq") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("voltage") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("current") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("lux") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("white_lux") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("ir_lux") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("uv_lux") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("distance") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_direction") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_speed") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_gust") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("wind_lull") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("weight") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("radiation") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("rainfall_1h") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("rainfall_24h") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("soil_moisture") == payload.end());
-    TEST_ASSERT_TRUE(payload.find("soil_temperature") == payload.end());
-
-    delete root;
-}
diff --git a/test/test_meshpacket_serializer/ports/test_text_message.cpp b/test/test_meshpacket_serializer/ports/test_text_message.cpp
deleted file mode 100644
index 0f3b0bc6d..000000000
--- a/test/test_meshpacket_serializer/ports/test_text_message.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-#include "../test_helpers.h"
-#include 
-
-// Helper function to test common packet fields and structure
-void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
-{
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    // Use smart pointer for automatic memory management
-    std::unique_ptr root(JSON::Parse(json.c_str()));
-    TEST_ASSERT_NOT_NULL(root.get());
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check basic packet fields - use helper function to reduce duplication
-    auto check_field = [&](const char *field, uint32_t expected_value) {
-        auto it = jsonObj.find(field);
-        TEST_ASSERT_TRUE(it != jsonObj.end());
-        TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber());
-    };
-
-    check_field("from", 0x11223344);
-    check_field("to", 0x55667788);
-    check_field("id", 0x9999);
-
-    // Check message type
-    auto type_it = jsonObj.find("type");
-    TEST_ASSERT_TRUE(type_it != jsonObj.end());
-    TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str());
-
-    // Check payload
-    auto payload_it = jsonObj.find("payload");
-    TEST_ASSERT_TRUE(payload_it != jsonObj.end());
-    TEST_ASSERT_TRUE(payload_it->second->IsObject());
-
-    JSONObject payload = payload_it->second->AsObject();
-    auto text_it = payload.find("text");
-    TEST_ASSERT_TRUE(text_it != payload.end());
-    TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str());
-
-    // No need for manual delete with smart pointer
-}
-
-// Test TEXT_MESSAGE_APP port
-void test_text_message_serialization()
-{
-    const char *test_text = "Hello Meshtastic!";
-    meshtastic_MeshPacket packet =
-        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(test_text), strlen(test_text));
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    verify_text_message_packet_structure(json, test_text);
-}
-
-// Test with nullptr to check robustness
-void test_text_message_serialization_null()
-{
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    verify_text_message_packet_structure(json, "");
-}
-
-// Test TEXT_MESSAGE_APP port with very long message (boundary testing)
-void test_text_message_serialization_long_text()
-{
-    // Test with actual message size limits
-    constexpr size_t MAX_MESSAGE_SIZE = 200; // Typical LoRa payload limit
-    std::string long_text(MAX_MESSAGE_SIZE, 'A');
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP,
-                                                      reinterpret_cast(long_text.c_str()), long_text.length());
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    verify_text_message_packet_structure(json, long_text.c_str());
-}
-
-// Test with message over size limit (should fail)
-void test_text_message_serialization_oversized()
-{
-    constexpr size_t OVERSIZED_MESSAGE = 250; // Over the limit
-    std::string oversized_text(OVERSIZED_MESSAGE, 'B');
-
-    meshtastic_MeshPacket packet = create_test_packet(
-        meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(oversized_text.c_str()), oversized_text.length());
-
-    // Should fail or return empty/error
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    // Should only verify first 234 characters for oversized messages
-    std::string expected_text = oversized_text.substr(0, 234);
-    verify_text_message_packet_structure(json, expected_text.c_str());
-}
-
-// Add test for malformed UTF-8 sequences
-void test_text_message_serialization_invalid_utf8()
-{
-    const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00}; // Invalid UTF-8
-    meshtastic_MeshPacket packet =
-        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, invalid_utf8, sizeof(invalid_utf8) - 1);
-
-    // Should not crash, may produce replacement characters
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-}
\ No newline at end of file
diff --git a/test/test_meshpacket_serializer/ports/test_waypoint.cpp b/test/test_meshpacket_serializer/ports/test_waypoint.cpp
deleted file mode 100644
index b7e811d70..000000000
--- a/test/test_meshpacket_serializer/ports/test_waypoint.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "../test_helpers.h"
-
-static size_t encode_waypoint(uint8_t *buffer, size_t buffer_size)
-{
-    meshtastic_Waypoint waypoint = meshtastic_Waypoint_init_zero;
-    waypoint.id = 12345;
-    waypoint.latitude_i = 374208000;
-    waypoint.longitude_i = -1221981000;
-    waypoint.expire = 1609459200 + 3600; // 1 hour from now
-    strcpy(waypoint.name, "Test Point");
-    strcpy(waypoint.description, "Test waypoint description");
-
-    pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
-    pb_encode(&stream, &meshtastic_Waypoint_msg, &waypoint);
-    return stream.bytes_written;
-}
-
-// Test WAYPOINT_APP port
-void test_waypoint_serialization()
-{
-    uint8_t buffer[256];
-    size_t payload_size = encode_waypoint(buffer, sizeof(buffer));
-
-    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_WAYPOINT_APP, buffer, payload_size);
-
-    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check message type
-    TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
-    TEST_ASSERT_EQUAL_STRING("waypoint", jsonObj["type"]->AsString().c_str());
-
-    // Check payload
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-
-    // Verify waypoint data
-    TEST_ASSERT_TRUE(payload.find("id") != payload.end());
-    TEST_ASSERT_EQUAL(12345, (int)payload["id"]->AsNumber());
-
-    TEST_ASSERT_TRUE(payload.find("name") != payload.end());
-    TEST_ASSERT_EQUAL_STRING("Test Point", payload["name"]->AsString().c_str());
-
-    delete root;
-}
diff --git a/test/test_meshpacket_serializer/test_helpers.h b/test/test_meshpacket_serializer/test_helpers.h
deleted file mode 100644
index 12245b85d..000000000
--- a/test/test_meshpacket_serializer/test_helpers.h
+++ /dev/null
@@ -1,49 +0,0 @@
-#pragma once
-
-#include "serialization/JSON.h"
-#include "serialization/MeshPacketSerializer.h"
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-
-// Helper function to create a test packet with the given port and payload
-static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size,
-                                                int payload_variant = meshtastic_MeshPacket_decoded_tag)
-{
-    meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
-
-    packet.id = 0x9999;
-    packet.from = 0x11223344;
-    packet.to = 0x55667788;
-    packet.channel = 0;
-    packet.hop_limit = 3;
-    packet.want_ack = false;
-    packet.priority = meshtastic_MeshPacket_Priority_UNSET;
-    packet.rx_time = 1609459200;
-    packet.rx_snr = 10.5f;
-    packet.hop_start = 3;
-    packet.rx_rssi = -85;
-    packet.delayed = meshtastic_MeshPacket_Delayed_NO_DELAY;
-
-    // Set decoded variant
-    packet.which_payload_variant = payload_variant;
-    packet.decoded.portnum = port;
-    if (payload_variant == meshtastic_MeshPacket_encrypted_tag && payload) {
-        packet.encrypted.size = payload_size;
-        memcpy(packet.encrypted.bytes, payload, packet.encrypted.size);
-    }
-    memcpy(packet.decoded.payload.bytes, payload, payload_size);
-    packet.decoded.payload.size = payload_size;
-    packet.decoded.want_response = false;
-    packet.decoded.dest = 0x55667788;
-    packet.decoded.source = 0x11223344;
-    packet.decoded.request_id = 0;
-    packet.decoded.reply_id = 0;
-    packet.decoded.emoji = 0;
-
-    return packet;
-}
diff --git a/test/test_meshpacket_serializer/test_serializer.cpp b/test/test_meshpacket_serializer/test_serializer.cpp
deleted file mode 100644
index 484db8d74..000000000
--- a/test/test_meshpacket_serializer/test_serializer.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-#include "test_helpers.h"
-#include 
-#include 
-
-// Forward declarations for test functions
-void test_text_message_serialization();
-void test_text_message_serialization_null();
-void test_text_message_serialization_long_text();
-void test_text_message_serialization_oversized();
-void test_text_message_serialization_invalid_utf8();
-void test_position_serialization();
-void test_nodeinfo_serialization();
-void test_waypoint_serialization();
-void test_telemetry_device_metrics_serialization();
-void test_telemetry_environment_metrics_serialization();
-void test_telemetry_environment_metrics_comprehensive();
-void test_telemetry_environment_metrics_missing_fields();
-void test_telemetry_environment_metrics_complete_coverage();
-void test_telemetry_environment_metrics_unset_fields();
-void test_encrypted_packet_serialization();
-void test_empty_encrypted_packet();
-
-void setup()
-{
-    UNITY_BEGIN();
-
-    // Text message tests
-    RUN_TEST(test_text_message_serialization);
-    RUN_TEST(test_text_message_serialization_null);
-    RUN_TEST(test_text_message_serialization_long_text);
-    RUN_TEST(test_text_message_serialization_oversized);
-    RUN_TEST(test_text_message_serialization_invalid_utf8);
-
-    // Position tests
-    RUN_TEST(test_position_serialization);
-
-    // Nodeinfo tests
-    RUN_TEST(test_nodeinfo_serialization);
-
-    // Waypoint tests
-    RUN_TEST(test_waypoint_serialization);
-
-    // Telemetry tests
-    RUN_TEST(test_telemetry_device_metrics_serialization);
-    RUN_TEST(test_telemetry_environment_metrics_serialization);
-    RUN_TEST(test_telemetry_environment_metrics_comprehensive);
-    RUN_TEST(test_telemetry_environment_metrics_missing_fields);
-    RUN_TEST(test_telemetry_environment_metrics_complete_coverage);
-    RUN_TEST(test_telemetry_environment_metrics_unset_fields);
-
-    // Encrypted packet test
-    RUN_TEST(test_encrypted_packet_serialization);
-    RUN_TEST(test_empty_encrypted_packet);
-
-    UNITY_END();
-}
-
-void loop()
-{
-    delay(1000);
-}
diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini
index 0fded96f4..033d1d9b8 100644
--- a/variants/nrf52840/rak4631_eth_gw/platformio.ini
+++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini
@@ -10,7 +10,6 @@ build_flags = ${nrf52840_base.build_flags}
   -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
   -DEINK_WIDTH=250
   -DEINK_HEIGHT=122
-  -DNRF52_USE_JSON=1
   -DMESHTASTIC_EXCLUDE_WIFI=1
   -DMESHTASTIC_EXCLUDE_SCREEN=1
 ;   -DMESHTASTIC_EXCLUDE_PKI=1
@@ -34,8 +33,6 @@ lib_deps =
   rakwireless/RAKwireless NCP5623 RGB LED library@1.0.3
   # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
   https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
-  # renovate: datasource=custom.pio depName=ArduinoJson packageName=bblanchon/library/ArduinoJson
-  bblanchon/ArduinoJson@6.21.6
 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
 ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
 ;upload_protocol = jlink