diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index fb1e45af..cd962b67 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#develop +platform = https://github.com/meshtastic/platform-native.git#096b3c3e9c5c8e19d4c3b6cd803fffef2a9be4c5 framework = arduino build_src_filter = ${env.build_src_filter} @@ -18,6 +18,4 @@ lib_deps = ${env.lib_deps} ${networking_base.lib_deps} rweather/Crypto@^0.4.0 - ; jgromes/RadioLib@5.4.1 - https://github.com/jgromes/RadioLib.git#63208f1e89d4dac6eedaafbe234bf90f1fd5402b ; 5.4.1 with some fixes, remove when 5.4.2 is released build_flags = ${arduino_base.build_flags} -Isrc/platform/portduino diff --git a/platformio.ini b/platformio.ini index 94e3e9f5..dbddf753 100644 --- a/platformio.ini +++ b/platformio.ini @@ -42,7 +42,7 @@ build_flags = -Wno-missing-field-initializers -Wno-format -Isrc -Isrc/mesh -Isrc/gps -Isrc/buzz -Wl,-Map,.pio/build/output.map -DUSE_THREAD_NAMES -; -DTINYGPS_OPTION_NO_CUSTOM_FIELDS // this should work now... + -DTINYGPS_OPTION_NO_CUSTOM_FIELDS -DPB_ENABLE_MALLOC=1 -DRADIOLIB_EXCLUDE_CC1101 -DRADIOLIB_EXCLUDE_NRF24 @@ -66,6 +66,7 @@ lib_deps = https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3 nanopb/Nanopb@^0.4.6 erriez/ErriezCRC32@^1.0.1 + jgromes/RadioLib@^5.5.0 ; Used for the code analysis in PIO Home / Inspect check_tool = cppcheck @@ -73,20 +74,17 @@ check_skip_packages = yes check_flags = -DAPP_VERSION=1.0.0 --suppressions-list=suppressions.txt + --inline-suppr ; Common settings for conventional (non Portduino) Arduino targets [arduino_base] framework = arduino lib_deps = ${env.lib_deps} - ; Portduino is using meshtastic fork for now - jgromes/RadioLib@5.4.1 mprograms/QMC5883LCompass@^1.1.1 https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#52b5282639d08a8cbd4b748363089eed6102dc76 -build_flags = ${env.build_flags} -Os - -DRADIOLIB_SPI_PARANOID=0 -# -DRADIOLIB_GODMODE +build_flags = ${env.build_flags} -Os -DRADIOLIB_SPI_PARANOID=0 build_src_filter = ${env.build_src_filter} - ; Common libs for communicating over TCP/IP networks such as MQTT @@ -94,7 +92,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/protobufs b/protobufs index 24874086..3aca01ac 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 24874086e392b0ec504f9f89137e7b7f8b5bfcbd +Subproject commit 3aca01ac82487de8aa3d5eefdd907b4d80714501 diff --git a/src/buzz/buzz.cpp b/src/buzz/buzz.cpp index f8314fe5..262cd20b 100644 --- a/src/buzz/buzz.cpp +++ b/src/buzz/buzz.cpp @@ -2,22 +2,13 @@ #include "configuration.h" #include "NodeDB.h" -#ifndef PIN_BUZZER - -// Noop methods for boards w/o buzzer -void playBeep(){}; -void playStartMelody(){}; -void playShutdownMelody(){}; - -#else -#ifdef M5STACK -#include "Speaker.h" -TONE Tone; -#else +#if !defined(ARCH_ESP32) && !defined(ARCH_RP2040) && !defined(ARCH_PORTDUINO) #include "Tone.h" #endif +#if !defined(ARCH_PORTDUINO) extern "C" void delay(uint32_t dwMs); +#endif struct ToneDuration { int frequency_khz; @@ -43,30 +34,25 @@ const int DURATION_1_8 = 125; // 1/8 note const int DURATION_1_4 = 250; // 1/4 note void playTones(const ToneDuration *tone_durations, int size) { - if (config.network.eth_enabled != true) { +#ifdef PIN_BUZZER + if (!config.device.buzzer_gpio) + config.device.buzzer_gpio = PIN_BUZZER; +#endif + if (config.device.buzzer_gpio) { for (int i = 0; i < size; i++) { const auto &tone_duration = tone_durations[i]; -#ifdef M5STACK - Tone.tone(tone_duration.frequency_khz); - delay(tone_duration.duration_ms); - Tone.mute(); -#else - tone(PIN_BUZZER, tone_duration.frequency_khz, tone_duration.duration_ms); -#endif + tone(config.device.buzzer_gpio, tone_duration.frequency_khz, tone_duration.duration_ms); // to distinguish the notes, set a minimum time between them. delay(1.3 * tone_duration.duration_ms); } } } -#ifdef M5STACK + void playBeep() { ToneDuration melody[] = {{NOTE_B3, DURATION_1_4}}; playTones(melody, sizeof(melody) / sizeof(ToneDuration)); } -#else -void playBeep() { tone(PIN_BUZZER, NOTE_B3, DURATION_1_4); } -#endif void playStartMelody() { ToneDuration melody[] = {{NOTE_FS3, DURATION_1_8}, @@ -75,11 +61,9 @@ void playStartMelody() { playTones(melody, sizeof(melody) / sizeof(ToneDuration)); } - void playShutdownMelody() { ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_AS3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}}; playTones(melody, sizeof(melody) / sizeof(ToneDuration)); } -#endif \ No newline at end of file diff --git a/src/detect/i2cScan.h b/src/detect/i2cScan.h index 19e40513..4eac50f0 100644 --- a/src/detect/i2cScan.h +++ b/src/detect/i2cScan.h @@ -12,7 +12,7 @@ void printATECCInfo() { -#ifndef ARCH_PORTDUINO +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) atecc.readConfigZone(false); DEBUG_MSG("ATECC608B Serial Number: "); @@ -114,7 +114,7 @@ void scanI2Cdevice() DEBUG_MSG("unknown display found\n"); } } -#ifndef ARCH_PORTDUINO +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) if (addr == ATECC608B_ADDR) { keystore_found = addr; if (atecc.begin(keystore_found) == true) { diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 534a0ebe..54dc59cb 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -150,9 +150,21 @@ bool GPS::setupGPS() _serial_gps->setRxBufferSize(2048); // the default is 256 #endif +// if the overrides are not dialled in, set them from the board definitions, if they exist + +#if defined(GPS_RX_PIN) +if (!config.position.rx_gpio) + config.position.rx_gpio = GPS_RX_PIN; +#endif +#if defined(GPS_TX_PIN) +if (!config.position.tx_gpio) + config.position.tx_gpio = GPS_TX_PIN; +#endif + // ESP32 has a special set of parameters vs other arduino ports -#if defined(GPS_RX_PIN) && defined(ARCH_ESP32) - _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); +#if defined(ARCH_ESP32) + if(config.position.rx_gpio) + _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, config.position.rx_gpio, config.position.tx_gpio); #else _serial_gps->begin(GPS_BAUDRATE); #endif diff --git a/src/main.h b/src/main.h index b7b41c25..89a6bd05 100644 --- a/src/main.h +++ b/src/main.h @@ -6,7 +6,7 @@ #include "PowerStatus.h" #include "graphics/Screen.h" #include "mesh/generated/telemetry.pb.h" -#ifndef ARCH_PORTDUINO +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) #include #endif @@ -22,7 +22,7 @@ extern bool pmu_found; extern bool isCharging; extern bool isUSBPowered; -#ifndef ARCH_PORTDUINO +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) extern ATECCX08A atecc; #endif diff --git a/src/mesh/generated/config.pb.h b/src/mesh/generated/config.pb.h index 9748383c..2d6f3a1d 100644 --- a/src/mesh/generated/config.pb.h +++ b/src/mesh/generated/config.pb.h @@ -100,6 +100,8 @@ typedef struct _Config_DeviceConfig { Config_DeviceConfig_Role role; bool serial_enabled; bool debug_log_enabled; + uint32_t button_gpio; + uint32_t buzzer_gpio; } Config_DeviceConfig; typedef struct _Config_DisplayConfig { @@ -143,6 +145,8 @@ typedef struct _Config_PositionConfig { uint32_t gps_update_interval; uint32_t gps_attempt_time; uint32_t position_flags; + uint32_t rx_gpio; + uint32_t tx_gpio; } Config_PositionConfig; typedef struct _Config_PowerConfig { @@ -225,8 +229,8 @@ extern "C" { /* Initializer values for message structs */ #define Config_init_default {0, {Config_DeviceConfig_init_default}} -#define Config_DeviceConfig_init_default {_Config_DeviceConfig_Role_MIN, 0, 0} -#define Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0} +#define Config_DeviceConfig_init_default {_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0} +#define Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define Config_NetworkConfig_init_default {0, "", "", "", 0, _Config_NetworkConfig_EthMode_MIN, false, Config_NetworkConfig_IpV4Config_init_default} #define Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} @@ -234,8 +238,8 @@ extern "C" { #define Config_LoRaConfig_init_default {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, {0, 0, 0}} #define Config_BluetoothConfig_init_default {0, _Config_BluetoothConfig_PairingMode_MIN, 0} #define Config_init_zero {0, {Config_DeviceConfig_init_zero}} -#define Config_DeviceConfig_init_zero {_Config_DeviceConfig_Role_MIN, 0, 0} -#define Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0} +#define Config_DeviceConfig_init_zero {_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0} +#define Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define Config_NetworkConfig_init_zero {0, "", "", "", 0, _Config_NetworkConfig_EthMode_MIN, false, Config_NetworkConfig_IpV4Config_init_zero} #define Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} @@ -250,6 +254,8 @@ extern "C" { #define Config_DeviceConfig_role_tag 1 #define Config_DeviceConfig_serial_enabled_tag 2 #define Config_DeviceConfig_debug_log_enabled_tag 3 +#define Config_DeviceConfig_button_gpio_tag 4 +#define Config_DeviceConfig_buzzer_gpio_tag 5 #define Config_DisplayConfig_screen_on_secs_tag 1 #define Config_DisplayConfig_gps_format_tag 2 #define Config_DisplayConfig_auto_screen_carousel_secs_tag 3 @@ -280,6 +286,8 @@ extern "C" { #define Config_PositionConfig_gps_update_interval_tag 5 #define Config_PositionConfig_gps_attempt_time_tag 6 #define Config_PositionConfig_position_flags_tag 7 +#define Config_PositionConfig_rx_gpio_tag 8 +#define Config_PositionConfig_tx_gpio_tag 9 #define Config_PowerConfig_is_power_saving_tag 1 #define Config_PowerConfig_on_battery_shutdown_after_secs_tag 2 #define Config_PowerConfig_adc_multiplier_override_tag 3 @@ -325,7 +333,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bl #define Config_DeviceConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, role, 1) \ X(a, STATIC, SINGULAR, BOOL, serial_enabled, 2) \ -X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 3) +X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 3) \ +X(a, STATIC, SINGULAR, UINT32, button_gpio, 4) \ +X(a, STATIC, SINGULAR, UINT32, buzzer_gpio, 5) #define Config_DeviceConfig_CALLBACK NULL #define Config_DeviceConfig_DEFAULT NULL @@ -336,7 +346,9 @@ X(a, STATIC, SINGULAR, BOOL, fixed_position, 3) \ X(a, STATIC, SINGULAR, BOOL, gps_enabled, 4) \ X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 5) \ X(a, STATIC, SINGULAR, UINT32, gps_attempt_time, 6) \ -X(a, STATIC, SINGULAR, UINT32, position_flags, 7) +X(a, STATIC, SINGULAR, UINT32, position_flags, 7) \ +X(a, STATIC, SINGULAR, UINT32, rx_gpio, 8) \ +X(a, STATIC, SINGULAR, UINT32, tx_gpio, 9) #define Config_PositionConfig_CALLBACK NULL #define Config_PositionConfig_DEFAULT NULL @@ -429,12 +441,12 @@ extern const pb_msgdesc_t Config_BluetoothConfig_msg; /* Maximum encoded size of messages (where known) */ #define Config_BluetoothConfig_size 10 -#define Config_DeviceConfig_size 6 +#define Config_DeviceConfig_size 18 #define Config_DisplayConfig_size 22 #define Config_LoRaConfig_size 68 #define Config_NetworkConfig_IpV4Config_size 20 #define Config_NetworkConfig_size 161 -#define Config_PositionConfig_size 30 +#define Config_PositionConfig_size 42 #define Config_PowerConfig_size 43 #define Config_size 164 diff --git a/src/mesh/generated/localonly.pb.h b/src/mesh/generated/localonly.pb.h index 783c523c..f2c12b10 100644 --- a/src/mesh/generated/localonly.pb.h +++ b/src/mesh/generated/localonly.pb.h @@ -150,8 +150,8 @@ extern const pb_msgdesc_t LocalModuleConfig_msg; #define LocalModuleConfig_fields &LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define LocalConfig_size 361 -#define LocalModuleConfig_size 294 +#define LocalConfig_size 385 +#define LocalModuleConfig_size 296 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/module_config.pb.h b/src/mesh/generated/module_config.pb.h index af4d4dce..d4e54642 100644 --- a/src/mesh/generated/module_config.pb.h +++ b/src/mesh/generated/module_config.pb.h @@ -90,6 +90,7 @@ typedef struct _ModuleConfig_ExternalNotificationConfig { bool active; bool alert_message; bool alert_bell; + bool use_pwm; } ModuleConfig_ExternalNotificationConfig; typedef struct _ModuleConfig_MQTTConfig { @@ -184,7 +185,7 @@ extern "C" { #define ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0} #define ModuleConfig_AudioConfig_init_default {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN} #define ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} -#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0} +#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} #define ModuleConfig_RangeTestConfig_init_default {0, 0, 0} #define ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0} @@ -193,7 +194,7 @@ extern "C" { #define ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0} #define ModuleConfig_AudioConfig_init_zero {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN} #define ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} -#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0} +#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} #define ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} #define ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0} @@ -222,6 +223,7 @@ extern "C" { #define ModuleConfig_ExternalNotificationConfig_active_tag 4 #define ModuleConfig_ExternalNotificationConfig_alert_message_tag 5 #define ModuleConfig_ExternalNotificationConfig_alert_bell_tag 6 +#define ModuleConfig_ExternalNotificationConfig_use_pwm_tag 7 #define ModuleConfig_MQTTConfig_enabled_tag 1 #define ModuleConfig_MQTTConfig_address_tag 2 #define ModuleConfig_MQTTConfig_username_tag 3 @@ -314,7 +316,8 @@ X(a, STATIC, SINGULAR, UINT32, output_ms, 2) \ X(a, STATIC, SINGULAR, UINT32, output, 3) \ X(a, STATIC, SINGULAR, BOOL, active, 4) \ X(a, STATIC, SINGULAR, BOOL, alert_message, 5) \ -X(a, STATIC, SINGULAR, BOOL, alert_bell, 6) +X(a, STATIC, SINGULAR, BOOL, alert_bell, 6) \ +X(a, STATIC, SINGULAR, BOOL, use_pwm, 7) #define ModuleConfig_ExternalNotificationConfig_CALLBACK NULL #define ModuleConfig_ExternalNotificationConfig_DEFAULT NULL @@ -382,7 +385,7 @@ extern const pb_msgdesc_t ModuleConfig_CannedMessageConfig_msg; /* Maximum encoded size of messages (where known) */ #define ModuleConfig_AudioConfig_size 22 #define ModuleConfig_CannedMessageConfig_size 49 -#define ModuleConfig_ExternalNotificationConfig_size 20 +#define ModuleConfig_ExternalNotificationConfig_size 22 #define ModuleConfig_MQTTConfig_size 105 #define ModuleConfig_RangeTestConfig_size 10 #define ModuleConfig_SerialConfig_size 26 diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 82ac8fee..d01f3c4a 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/mesh/http/WiFiAPClient.cpp b/src/mesh/http/WiFiAPClient.cpp index c00738d1..bb454246 100644 --- a/src/mesh/http/WiFiAPClient.cpp +++ b/src/mesh/http/WiFiAPClient.cpp @@ -56,13 +56,28 @@ static int32_t reconnectWiFi() // Make sure we clear old connection credentials WiFi.disconnect(false, true); - DEBUG_MSG("... Reconnecting to WiFi access point\n"); - WiFi.mode(WIFI_MODE_STA); - WiFi.begin(wifiName, wifiPsw); + DEBUG_MSG("... Reconnecting to WiFi access point %s\n",wifiName); + + int n = WiFi.scanNetworks(); + + if (n > 0) { + for (int i = 0; i < n; ++i) { + DEBUG_MSG("Found WiFi network %s, signal strength %d\n", WiFi.SSID(i).c_str(), WiFi.RSSI(i)); + yield(); + } + WiFi.mode(WIFI_MODE_STA); + WiFi.begin(wifiName, wifiPsw); + } else { + DEBUG_MSG("No networks found during site survey. Rebooting MCU...\n"); + screen->startRebootScreen(); + rebootAtMsec = millis() + 5000; + } + + } #ifndef DISABLE_NTP - if (WiFi.isConnected() && ((millis() - lastrun_ntp) > 43200000)) { // every 12 hours + if (WiFi.isConnected() && (((millis() - lastrun_ntp) > 43200000) || (lastrun_ntp == 0))) { // every 12 hours DEBUG_MSG("Updating NTP time\n"); if (timeClient.update()) { DEBUG_MSG("NTP Request Success - Setting RTCQualityNTP if needed\n"); diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index e5d371c1..5a95ceec 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -76,7 +76,7 @@ int32_t ExternalNotificationModule::runOnce() // moduleConfig.external_notification.output_ms = 1000; // moduleConfig.external_notification.output = 13; - if (externalCurrentState) { + if (externalCurrentState && !moduleConfig.external_notification.use_pwm) { // If the output is turned on, turn it back off after the given period of time. if (externalTurnedOn + (moduleConfig.external_notification.output_ms @@ -84,13 +84,13 @@ int32_t ExternalNotificationModule::runOnce() : EXT_NOTIFICATION_MODULE_OUTPUT_MS) < millis()) { DEBUG_MSG("Turning off external notification\n"); - if (output != PIN_BUZZER) { - setExternalOff(); - } + setExternalOff(); } } - - return (25); + if (moduleConfig.external_notification.use_pwm) + return INT32_MAX; // we don't need this thread here... + else + return 25; } void ExternalNotificationModule::setExternalOn() @@ -129,11 +129,6 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.output_ms = 1000; // moduleConfig.external_notification.output = 13; - if (moduleConfig.external_notification.alert_message) { - // restrict to the gpio channel for rx - boundChannel = Channels::gpioChannel; - } - if (moduleConfig.external_notification.enabled) { DEBUG_MSG("Initializing External Notification Module\n"); @@ -142,14 +137,19 @@ ExternalNotificationModule::ExternalNotificationModule() ? moduleConfig.external_notification.output : EXT_NOTIFICATION_MODULE_OUTPUT; - if (output != PIN_BUZZER) { + if (!moduleConfig.external_notification.use_pwm) { // Set the direction of a pin DEBUG_MSG("Using Pin %i in digital mode\n", output); pinMode(output, OUTPUT); // Turn off the pin setExternalOff(); - } else{ - DEBUG_MSG("Using Pin %i in PWM mode\n", output); + } else { + config.device.buzzer_gpio = config.device.buzzer_gpio + ? config.device.buzzer_gpio + : PIN_BUZZER; + + // in PWM Mode we force the buzzer pin if it is set + DEBUG_MSG("Using Pin %i in PWM mode\n", config.device.buzzer_gpio); } } else { DEBUG_MSG("External Notification Module Disabled\n"); @@ -170,7 +170,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const MeshPacket &mp) DEBUG_MSG("externalNotificationModule - Notification Bell\n"); for (int i = 0; i < p.payload.size; i++) { if (p.payload.bytes[i] == ASCII_BELL) { - if (output != PIN_BUZZER) { + if (!moduleConfig.external_notification.use_pwm) { setExternalOn(); } else { playBeep(); @@ -181,7 +181,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const MeshPacket &mp) if (moduleConfig.external_notification.alert_message) { DEBUG_MSG("externalNotificationModule - Notification Module\n"); - if (output != PIN_BUZZER) { + if (!moduleConfig.external_notification.use_pwm) { setExternalOn(); } else { playBeep(); diff --git a/src/mqtt/JSON.cpp b/src/mqtt/JSON.cpp new file mode 100644 index 00000000..65bff304 --- /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 00000000..d6532d69 --- /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 00000000..13a01511 --- /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 00000000..c915a10a --- /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 fa1a6659..05f66244 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -12,7 +12,7 @@ #include #endif #include -#include +#include "mqtt/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["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; } diff --git a/suppressions.txt b/suppressions.txt index 42dd40c3..3db2d5b7 100644 --- a/suppressions.txt +++ b/suppressions.txt @@ -36,7 +36,10 @@ cstyleCast // ignore stuff that is not ours *:.pio/* *:*/libdeps/* +noExplicitConstructor:*/mqtt/* +postfixOperator:*/mqtt/* // these two caused issues missingOverride virtualCallInConstructor + diff --git a/variants/m5stack_core/Speaker.cpp b/variants/m5stack_core/Speaker.cpp deleted file mode 100644 index 0aaff123..00000000 --- a/variants/m5stack_core/Speaker.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Speaker.h" - -TONE::TONE(void) { - _volume = 5; - _begun = false; -} - -void TONE::begin() { - _begun = true; - ledcSetup(TONE_PIN_CHANNEL, 0, 13); - ledcAttachPin(PIN_BUZZER, TONE_PIN_CHANNEL); -} - -void TONE::end() { - mute(); - ledcDetachPin(PIN_BUZZER); - _begun = false; -} - -void TONE::tone(uint16_t frequency) { - if(!_begun) begin(); - ledcWriteTone(TONE_PIN_CHANNEL, frequency); - ledcWrite(TONE_PIN_CHANNEL, 0x400 >> _volume); -} - -void TONE::setVolume(uint8_t volume) { - _volume = 11 - volume; -} - -void TONE::mute() { - ledcWriteTone(TONE_PIN_CHANNEL, 0); - digitalWrite(PIN_BUZZER, 0); -} \ No newline at end of file diff --git a/variants/m5stack_core/Speaker.h b/variants/m5stack_core/Speaker.h deleted file mode 100644 index 2ab877d9..00000000 --- a/variants/m5stack_core/Speaker.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef _SPEAKER_H_ - #define _SPEAKER_H_ - - #include "configuration.h" - - #ifdef __cplusplus - extern "C" - { - #endif /* __cplusplus */ - #include "esp32-hal-dac.h" - #ifdef __cplusplus - } - #endif /* __cplusplus */ - - class TONE { - public: - TONE(void); - - void begin(); - void end(); - void mute(); - void tone(uint16_t frequency); - void setVolume(uint8_t volume); - - private: - uint8_t _volume; - bool _begun; - bool speaker_on; - }; -#endif diff --git a/variants/m5stack_core/platformio.ini b/variants/m5stack_core/platformio.ini index d1cda8fd..f84e0478 100644 --- a/variants/m5stack_core/platformio.ini +++ b/variants/m5stack_core/platformio.ini @@ -6,7 +6,6 @@ monitor_port = COM8 monitor_filters = esp32_exception_decoder build_src_filter = ${esp32_base.build_src_filter} - +<../variants/m5stack_core> build_flags = ${esp32_base.build_flags} -I variants/m5stack_core -DILI9341_DRIVER diff --git a/variants/m5stack_core/variant.h b/variants/m5stack_core/variant.h index f33ab08a..186788f0 100644 --- a/variants/m5stack_core/variant.h +++ b/variants/m5stack_core/variant.h @@ -11,7 +11,6 @@ #define BUTTON_PIN 38 #define PIN_BUZZER 25 -#define TONE_PIN_CHANNEL 0 #undef RF95_SCK #undef RF95_MISO diff --git a/variants/m5stack_coreink/Speaker.cpp b/variants/m5stack_coreink/Speaker.cpp deleted file mode 100644 index 0aaff123..00000000 --- a/variants/m5stack_coreink/Speaker.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Speaker.h" - -TONE::TONE(void) { - _volume = 5; - _begun = false; -} - -void TONE::begin() { - _begun = true; - ledcSetup(TONE_PIN_CHANNEL, 0, 13); - ledcAttachPin(PIN_BUZZER, TONE_PIN_CHANNEL); -} - -void TONE::end() { - mute(); - ledcDetachPin(PIN_BUZZER); - _begun = false; -} - -void TONE::tone(uint16_t frequency) { - if(!_begun) begin(); - ledcWriteTone(TONE_PIN_CHANNEL, frequency); - ledcWrite(TONE_PIN_CHANNEL, 0x400 >> _volume); -} - -void TONE::setVolume(uint8_t volume) { - _volume = 11 - volume; -} - -void TONE::mute() { - ledcWriteTone(TONE_PIN_CHANNEL, 0); - digitalWrite(PIN_BUZZER, 0); -} \ No newline at end of file diff --git a/variants/m5stack_coreink/Speaker.h b/variants/m5stack_coreink/Speaker.h deleted file mode 100644 index 2ab877d9..00000000 --- a/variants/m5stack_coreink/Speaker.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef _SPEAKER_H_ - #define _SPEAKER_H_ - - #include "configuration.h" - - #ifdef __cplusplus - extern "C" - { - #endif /* __cplusplus */ - #include "esp32-hal-dac.h" - #ifdef __cplusplus - } - #endif /* __cplusplus */ - - class TONE { - public: - TONE(void); - - void begin(); - void end(); - void mute(); - void tone(uint16_t frequency); - void setVolume(uint8_t volume); - - private: - uint8_t _volume; - bool _begun; - bool speaker_on; - }; -#endif diff --git a/variants/m5stack_coreink/platformio.ini b/variants/m5stack_coreink/platformio.ini index 796b58e6..06f970db 100644 --- a/variants/m5stack_coreink/platformio.ini +++ b/variants/m5stack_coreink/platformio.ini @@ -3,7 +3,6 @@ extends = esp32_base board = m5stack-coreink build_src_filter = ${esp32_base.build_src_filter} - +<../variants/m5stack_coreink> build_flags = ${esp32_base.build_flags} -I variants/m5stack_coreink ;-D RADIOLIB_VERBOSE diff --git a/variants/m5stack_coreink/variant.h b/variants/m5stack_coreink/variant.h index 0676fda1..e5f49c7a 100644 --- a/variants/m5stack_coreink/variant.h +++ b/variants/m5stack_coreink/variant.h @@ -21,7 +21,6 @@ //BUZZER #define PIN_BUZZER 2 -#define TONE_PIN_CHANNEL 0 #undef RF95_SCK #undef RF95_MISO diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index 904491b6..51da8590 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -205,7 +205,8 @@ static const uint8_t SCK = PIN_SPI_SCK; #define RV3028_RTC (uint8_t) 0b1010010 // RAK18001 Buzzer in Slot C -#define PIN_BUZZER 21 // IO3 is PWM2 +// #define PIN_BUZZER 21 // IO3 is PWM2 +// NEW: set this via protobuf instead! // Battery // The battery sense is hooked to pin A0 (5)