diff --git a/platformio.ini b/platformio.ini index 94e3e9f5b..0581a84ec 100644 --- a/platformio.ini +++ b/platformio.ini @@ -94,7 +94,6 @@ build_src_filter = ${env.build_src_filter} - lib_deps = knolleary/PubSubClient@^2.8 arduino-libraries/NTPClient@^3.1.0 - meshtastic/json11@^1.0.2 ; Common libs for environmental measurements in telemetry module ; (not included in native / portduino) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 82ac8feef..d01f3c4a5 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include "mqtt/JSON.h" #ifdef ARCH_ESP32 #include "esp_task_wdt.h" @@ -246,15 +246,15 @@ void htmlDeleteDir(const char *dirname) root.close(); } -std::vector> *htmlListDir(std::vector> *fileList, const char *dirname, - uint8_t levels) +JSONArray htmlListDir(const char *dirname, uint8_t levels) { File root = FSCom.open(dirname, FILE_O_READ); + JSONArray fileList; if (!root) { - return NULL; + return fileList; } if (!root.isDirectory()) { - return NULL; + return fileList; } // iterate over the file list @@ -263,19 +263,19 @@ std::vector> *htmlListDir(std::vector thisFileMap; - thisFileMap[strdup("size")] = strdup(String(file.size()).c_str()); + JSONObject thisFileMap; + thisFileMap["size"] = new JSONValue((int)file.size()); #ifdef ARCH_ESP32 - thisFileMap[strdup("name")] = strdup(String(file.path()).substring(1).c_str()); + thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str()); #else - thisFileMap[strdup("name")] = strdup(String(file.name()).substring(1).c_str()); + thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str()); #endif if (String(file.name()).substring(1).endsWith(".gz")) { #ifdef ARCH_ESP32 @@ -284,9 +284,9 @@ std::vector> *htmlListDir(std::vectorpush_back(thisFileMap); + fileList.push_back(new JSONValue(thisFileMap)); } file.close(); file = root.openNextFile(); @@ -301,29 +301,31 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); - using namespace json11; - auto fileList = htmlListDir(new std::vector>(), "/static", 10); + auto fileList = htmlListDir("/static", 10); // create json output structure - Json filesystemObj = Json::object{ - {"total", String(FSCom.totalBytes()).c_str()}, - {"used", String(FSCom.usedBytes()).c_str()}, - {"free", String(FSCom.totalBytes() - FSCom.usedBytes()).c_str()}, - }; + 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())); - Json jsonObjInner = Json::object{{"files", Json(*fileList)}, {"filesystem", filesystemObj}}; + JSONObject jsonObjInner; + jsonObjInner["files"] = new JSONValue(fileList); + jsonObjInner["filesystem"] = new JSONValue(filesystemObj); - Json jsonObjOuter = Json::object{{"data", jsonObjInner}, {"status", "ok"}}; + JSONObject jsonObjOuter; + jsonObjOuter["data"] = new JSONValue(jsonObjInner); + jsonObjOuter["status"] = new JSONValue("ok"); - // serialize and write it to the stream - std::string jsonStr = jsonObjOuter.dump(); - res->print(jsonStr.c_str()); + JSONValue *value = new JSONValue(jsonObjOuter); + + res->print(value->Stringify().c_str()); + + delete value; } void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) { - using namespace json11; - ResourceParameters *params = req->getParams(); std::string paramValDelete; @@ -334,15 +336,19 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) std::string pathDelete = "/" + paramValDelete; if (FSCom.remove(pathDelete.c_str())) { Serial.println(pathDelete.c_str()); - Json jsonObjOuter = Json::object{{"status", "ok"}}; - std::string jsonStr = jsonObjOuter.dump(); - res->print(jsonStr.c_str()); + JSONObject jsonObjOuter; + jsonObjOuter["status"] = new JSONValue("ok"); + JSONValue *value = new JSONValue(jsonObjOuter); + res->print(value->Stringify().c_str()); + delete value; return; } else { Serial.println(pathDelete.c_str()); - Json jsonObjOuter = Json::object{{"status", "Error"}}; - std::string jsonStr = jsonObjOuter.dump(); - res->print(jsonStr.c_str()); + JSONObject jsonObjOuter; + jsonObjOuter["status"] = new JSONValue("Error"); + JSONValue *value = new JSONValue(jsonObjOuter); + res->print(value->Stringify().c_str()); + delete value; return; } } @@ -559,8 +565,6 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res) void handleReport(HTTPRequest *req, HTTPResponse *res) { - using namespace json11; - ResourceParameters *params = req->getParams(); std::string content; @@ -579,81 +583,87 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) } // data->airtime->tx_log - std::vector txLogValues; + JSONArray txLogValues; uint32_t *logArray; logArray = airTime->airtimeReport(TX_LOG); for (int i = 0; i < airTime->getPeriodsToLog(); i++) { - uint32_t tmp; - tmp = *(logArray + i); - txLogValues.push_back(String(tmp)); + txLogValues.push_back(new JSONValue((int)logArray[i])); } // data->airtime->rx_log - std::vector rxLogValues; + JSONArray rxLogValues; logArray = airTime->airtimeReport(RX_LOG); for (int i = 0; i < airTime->getPeriodsToLog(); i++) { - uint32_t tmp; - tmp = *(logArray + i); - rxLogValues.push_back(String(tmp)); + rxLogValues.push_back(new JSONValue((int)logArray[i])); } // data->airtime->rx_all_log - std::vector rxAllLogValues; + JSONArray rxAllLogValues; logArray = airTime->airtimeReport(RX_ALL_LOG); for (int i = 0; i < airTime->getPeriodsToLog(); i++) { - uint32_t tmp; - tmp = *(logArray + i); - rxAllLogValues.push_back(String(tmp)); + rxAllLogValues.push_back(new JSONValue((int)logArray[i])); } - Json jsonObjAirtime = Json::object{ - {"tx_log", Json(txLogValues)}, - {"rx_log", Json(rxLogValues)}, - {"rx_all_log", Json(rxAllLogValues)}, - {"channel_utilization", Json(airTime->channelUtilizationPercent())}, - {"utilization_tx", Json(airTime->utilizationTXPercent())}, - {"seconds_since_boot", Json(int(airTime->getSecondsSinceBoot()))}, - {"seconds_per_period", Json(int(airTime->getSecondsPerPeriod()))}, - {"periods_to_log", Json(airTime->getPeriodsToLog())}, - }; + // data->airtime + JSONObject jsonObjAirtime; + jsonObjAirtime["tx_log"] = new JSONValue(txLogValues); + jsonObjAirtime["rx_log"] = new JSONValue(rxLogValues); + jsonObjAirtime["rx_all_log"] = new JSONValue(rxAllLogValues); + 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 - String ipStr = String(WiFi.localIP().toString()); - - Json jsonObjWifi = Json::object{{"rssi", String(WiFi.RSSI())}, {"ip", ipStr.c_str()}}; + JSONObject jsonObjWifi; + jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI()); + jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str()); // data->memory - Json jsonObjMemory = Json::object{{"heap_total", Json(int(ESP.getHeapSize()))}, - {"heap_free", Json(int(ESP.getFreeHeap()))}, - {"psram_total", Json(int(ESP.getPsramSize()))}, - {"psram_free", Json(int(ESP.getFreePsram()))}, - {"fs_total", String(FSCom.totalBytes()).c_str()}, - {"fs_used", String(FSCom.usedBytes()).c_str()}, - {"fs_free", String(FSCom.totalBytes() - FSCom.usedBytes()).c_str()}}; + JSONObject jsonObjMemory; + jsonObjMemory["heap_total"] = new JSONValue((int)ESP.getHeapSize()); + jsonObjMemory["heap_free"] = new JSONValue((int)ESP.getFreeHeap()); + jsonObjMemory["psram_total"] = new JSONValue((int)ESP.getPsramSize()); + jsonObjMemory["psram_free"] = new JSONValue((int)ESP.getFreePsram()); + 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())); // data->power - Json jsonObjPower = Json::object{{"battery_percent", Json(powerStatus->getBatteryChargePercent())}, - {"battery_voltage_mv", Json(powerStatus->getBatteryVoltageMv())}, - {"has_battery", BoolToString(powerStatus->getHasBattery())}, - {"has_usb", BoolToString(powerStatus->getHasUSB())}, - {"is_charging", BoolToString(powerStatus->getIsCharging())}}; + 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())); // data->device - Json jsonObjDevice = Json::object{{"reboot_counter", Json(int(myNodeInfo.reboot_count))}}; + JSONObject jsonObjDevice; + jsonObjDevice["reboot_counter"] = new JSONValue((int)myNodeInfo.reboot_count); // data->radio - Json jsonObjRadio = Json::object{{"frequency", Json(RadioLibInterface::instance->getFreq())}, - {"lora_channel", Json(int(RadioLibInterface::instance->getChannelNum()))}}; + JSONObject jsonObjRadio; + jsonObjRadio["frequency"] = new JSONValue(RadioLibInterface::instance->getFreq()); + jsonObjRadio["lora_channel"] = new JSONValue((int)RadioLibInterface::instance->getChannelNum()); // collect data to inner data object - Json jsonObjInner = Json::object{{"airtime", jsonObjAirtime}, {"wifi", jsonObjWifi}, {"memory", jsonObjMemory}, - {"power", jsonObjPower}, {"device", jsonObjDevice}, {"radio", jsonObjRadio}}; + 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); // create json output structure - Json jsonObjOuter = Json::object{{"data", jsonObjInner}, {"status", "ok"}}; + JSONObject jsonObjOuter; + jsonObjOuter["data"] = new JSONValue(jsonObjInner); + jsonObjOuter["status"] = new JSONValue("ok"); // serialize and write it to the stream - std::string jsonStr = jsonObjOuter.dump(); - res->print(jsonStr.c_str()); + JSONValue *value = new JSONValue(jsonObjOuter); + res->print(value->Stringify().c_str()); + delete value; } /* @@ -767,8 +777,6 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res) void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) { - using namespace json11; - res->setHeader("Content-Type", "application/json"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "POST"); @@ -797,15 +805,15 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) #endif } - Json jsonObjOuter = Json::object{{"status", "ok"}}; - std::string jsonStr = jsonObjOuter.dump(); - res->print(jsonStr.c_str()); + JSONObject jsonObjOuter; + jsonObjOuter["status"] = new JSONValue("ok"); + JSONValue *value = new JSONValue(jsonObjOuter); + res->print(value->Stringify().c_str()); + delete value; } void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) { - using namespace json11; - res->setHeader("Content-Type", "application/json"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -814,7 +822,7 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) int n = WiFi.scanNetworks(); // build list of network objects - std::vector networkObjs; + JSONArray networkObjs; if (n > 0) { for (int i = 0; i < n; ++i) { char ssidArray[50]; @@ -823,8 +831,10 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) ssidString.toCharArray(ssidArray, 50); if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) { - Json thisNetwork = Json::object{{"ssid", ssidArray}, {"rssi", WiFi.RSSI(i)}}; - networkObjs.push_back(thisNetwork); + JSONObject thisNetwork; + thisNetwork["ssid"] = new JSONValue(ssidArray); + thisNetwork["rssi"] = new JSONValue(WiFi.RSSI(i)); + networkObjs.push_back(new JSONValue(thisNetwork)); } // Yield some cpu cycles to IP stack. // This is important in case the list is large and it takes us time to return @@ -834,9 +844,12 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) } // build output structure - Json jsonObjOuter = Json::object{{"data", networkObjs}, {"status", "ok"}}; + JSONObject jsonObjOuter; + jsonObjOuter["data"] = new JSONValue(networkObjs); + jsonObjOuter["status"] = new JSONValue("ok"); // serialize and write it to the stream - std::string jsonStr = jsonObjOuter.dump(); - res->print(jsonStr.c_str()); + JSONValue *value = new JSONValue(jsonObjOuter); + res->print(value->Stringify().c_str()); + delete value; } diff --git a/src/mqtt/JSON.cpp b/src/mqtt/JSON.cpp new file mode 100644 index 000000000..65bff304a --- /dev/null +++ b/src/mqtt/JSON.cpp @@ -0,0 +1,241 @@ +/* + * 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.reserve(); // 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/mqtt/JSON.h b/src/mqtt/JSON.h new file mode 100644 index 000000000..d6532d696 --- /dev/null +++ b/src/mqtt/JSON.h @@ -0,0 +1,70 @@ +/* + * 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/mqtt/JSONValue.cpp b/src/mqtt/JSONValue.cpp new file mode 100644 index 000000000..13a01511e --- /dev/null +++ b/src/mqtt/JSONValue.cpp @@ -0,0 +1,940 @@ +/* + * 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 + +#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 possibilites, 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 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 < ' ' || chr > 126) + { + str_out += "\\u"; + for (int i = 0; i < 4; i++) + { + int value = (chr >> 12) & 0xf; + if (value >= 0 && value <= 9) + str_out += (char)('0' + value); + else if (value >= 10 && value <= 15) + str_out += (char)('A' + (value - 10)); + chr <<= 4; + } + } + else + { + str_out += chr; + } + + 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; +} diff --git a/src/mqtt/JSONValue.h b/src/mqtt/JSONValue.h new file mode 100644 index 000000000..c915a10a6 --- /dev/null +++ b/src/mqtt/JSONValue.h @@ -0,0 +1,95 @@ +/* + * 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*/); + JSONValue(const char *m_char_value); + JSONValue(const std::string &m_string_value); + JSONValue(bool m_bool_value); + JSONValue(double m_number_value); + JSONValue(int m_integer_value); + JSONValue(const JSONArray &m_array_value); + JSONValue(const JSONObject &m_object_value); + 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 diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index fa1a66593..663ef6912 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -12,7 +12,7 @@ #include #endif #include -#include +#include "json.h" MQTT *mqtt; @@ -32,19 +32,19 @@ void MQTT::onPublish(char *topic, byte *payload, unsigned int length) if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) { // check if this is a json payload message by comparing the topic start - using namespace json11; char payloadStr[length + 1]; memcpy(payloadStr, payload, length); payloadStr[length] = 0; // null terminated string - std::string err; - auto json = Json::parse(payloadStr, err); - if (err.empty()) { + JSONValue *json_value = JSON::Parse(payloadStr); + if (json_value != NULL) { DEBUG_MSG("JSON Received on MQTT, parsing..\n"); // check if it is a valid envelope - if (json.object_items().count("sender") != 0 && json.object_items().count("payload") != 0 && json["type"].string_value().compare("sendtext") == 0) { + JSONObject json; + json = json_value->AsObject(); + if ((json.find("sender") != json.end()) && (json.find("payload") != json.end()) && (json.find("type") != json.end()) && json["type"]->IsString() && (json["type"]->AsString().compare("sendtext") == 0)) { // this is a valid envelope - if (json["sender"].string_value().compare(owner.id) != 0) { - std::string jsonPayloadStr = json["payload"].dump(); + if (json["payload"]->IsString() && json["type"]->IsString() && (json["sender"]->AsString().compare(owner.id) != 0)) { + std::string jsonPayloadStr = json["payload"]->AsString(); DEBUG_MSG("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh @@ -68,6 +68,7 @@ void MQTT::onPublish(char *topic, byte *payload, unsigned int length) // no json, this is an invalid payload DEBUG_MSG("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); } + delete json_value; } else { if (!pb_decode_from_bytes(payload, length, ServiceEnvelope_fields, &e)) { DEBUG_MSG("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); @@ -250,7 +251,6 @@ void MQTT::onSend(const MeshPacket &mp, ChannelIndex chIndex) if (moduleConfig.mqtt.json_enabled) { // handle json topic - using namespace json11; auto jsonString = this->downstreamPacketToJson((MeshPacket *)&mp); if (jsonString.length() != 0) { String topicJson = jsonTopic + channelId + "/" + owner.id; @@ -264,12 +264,11 @@ void MQTT::onSend(const MeshPacket &mp, ChannelIndex chIndex) // converts a downstream packet into a json message std::string MQTT::downstreamPacketToJson(MeshPacket *mp) { - using namespace json11; - // the created jsonObj is immutable after creation, so // we need to do the heavy lifting before assembling it. String msgType; - Json msgPayload; + JSONObject msgPayload; + JSONObject jsonObj; switch (mp->decoded.portnum) { case PortNum_TEXT_MESSAGE_APP: { @@ -280,17 +279,17 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) 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 - std::string err; - auto json = Json::parse(payloadStr, err); - if (err.empty()) { + JSONValue *json_value = JSON::Parse(payloadStr); + if (json_value != NULL) { DEBUG_MSG("text message payload is of type json\n"); // if it is, then we can just use the json object - msgPayload = json; + jsonObj["payload"] = json_value; } else { // if it isn't, then we need to create a json object // with the string as the value DEBUG_MSG("text message payload is of type plaintext\n"); - msgPayload = Json::object{{"text", payloadStr}}; + msgPayload["text"] = new JSONValue(payloadStr); + jsonObj["payload"] = new JSONValue(msgPayload); } break; } @@ -303,22 +302,19 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &Telemetry_msg, &scratch)) { decoded = &scratch; if (decoded->which_variant == Telemetry_device_metrics_tag) { - msgPayload = Json::object{ - {"battery_level", (int)decoded->variant.device_metrics.battery_level}, - {"voltage", decoded->variant.device_metrics.voltage}, - {"channel_utilization", decoded->variant.device_metrics.channel_utilization}, - {"air_util_tx", decoded->variant.device_metrics.air_util_tx}, - }; + 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); } else if (decoded->which_variant == Telemetry_environment_metrics_tag) { - msgPayload = Json::object{ - {"temperature", decoded->variant.environment_metrics.temperature}, - {"relative_humidity", decoded->variant.environment_metrics.relative_humidity}, - {"barometric_pressure", decoded->variant.environment_metrics.barometric_pressure}, - {"gas_resistance", decoded->variant.environment_metrics.gas_resistance}, - {"voltage", decoded->variant.environment_metrics.voltage}, - {"current", decoded->variant.environment_metrics.current}, - }; + msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature); + msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity); + msgPayload["barometric_pressure"] = new JSONValue(decoded->variant.environment_metrics.barometric_pressure); + msgPayload["gas_resistance"] = new JSONValue(decoded->variant.environment_metrics.gas_resistance); + msgPayload["voltage"] = new JSONValue(decoded->variant.environment_metrics.voltage); + msgPayload["current"] = new JSONValue(decoded->variant.environment_metrics.current); } + jsonObj["payload"] = new JSONValue(msgPayload); } else DEBUG_MSG("Error decoding protobuf for telemetry message!\n"); }; @@ -332,13 +328,11 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &User_msg, &scratch)) { decoded = &scratch; - msgPayload = Json::object{ - {"id", decoded->id}, - {"longname", decoded->long_name}, - {"shortname", decoded->short_name}, - {"hardware", decoded->hw_model} - }; - + 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); + jsonObj["payload"] = new JSONValue(msgPayload); } else DEBUG_MSG("Error decoding protobuf for nodeinfo message!\n"); }; @@ -352,13 +346,12 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &Position_msg, &scratch)) { decoded = &scratch; - msgPayload = Json::object{ - {"time", (int)decoded->time}, - {"pos_timestamp", (int)decoded->timestamp}, - {"latitude_i", (int)decoded->latitude_i}, - {"longitude_i", (int)decoded->longitude_i}, - {"altitude", (int)decoded->altitude} - }; + msgPayload["time"] = new JSONValue((int)decoded->time); + msgPayload["pos_timestamp"] = new JSONValue((int)decoded->timestamp); + msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); + msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); + msgPayload["altitude"] = new JSONValue((int)decoded->altitude); + jsonObj["payload"] = new JSONValue(msgPayload); } else { DEBUG_MSG("Error decoding protobuf for position message!\n"); } @@ -374,15 +367,14 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &Waypoint_msg, &scratch)) { decoded = &scratch; - msgPayload = Json::object{ - {"id", (int)decoded->id}, - {"name", decoded->name}, - {"description", decoded->description}, - {"expire", (int)decoded->expire}, - {"locked", decoded->locked}, - {"latitude_i", (int)decoded->latitude_i}, - {"longitude_i", (int)decoded->longitude_i}, - }; + msgPayload["id"] = new JSONValue((int)decoded->id); + msgPayload["name"] = new JSONValue(decoded->name); + msgPayload["description"] = new JSONValue(decoded->description); + msgPayload["expire"] = new JSONValue((int)decoded->expire); + msgPayload["locked"] = new JSONValue(decoded->locked); + msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); + msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); + jsonObj["payload"] = new JSONValue(msgPayload); } else { DEBUG_MSG("Error decoding protobuf for position message!\n"); } @@ -394,21 +386,20 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) break; } - // assemble the final jsonObj - Json jsonObj = Json::object{ - {"id", Json((int)mp->id)}, - {"timestamp", Json((int)mp->rx_time)}, - {"to", Json((int)mp->to)}, - {"from", Json((int)mp->from)}, - {"channel", Json((int)mp->channel)}, - {"type", msgType.c_str()}, - {"sender", owner.id}, - {"payload", msgPayload} - }; + jsonObj["id"] = new JSONValue((int)mp->id); + jsonObj["timestamp"] = new JSONValue((int)mp->rx_time); + jsonObj["to"] = new JSONValue((int)mp->to); + jsonObj["from"] = new JSONValue((int)mp->from); + jsonObj["channel"] = new JSONValue((int)mp->channel); + jsonObj["type"] = new JSONValue(msgType.c_str()); + jsonObj["sender"] = new JSONValue(owner.id); + + // serialize and write it to the stream + JSONValue *value = new JSONValue(jsonObj); + std::string jsonStr = value->Stringify(); - // serialize and return it - std::string jsonStr = jsonObj.dump(); DEBUG_MSG("serialized json message: %s\n", jsonStr.c_str()); + delete value; return jsonStr; }