From a5b79528b35a0e7212440574e75d86c97787bc25 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Mon, 9 Sep 2024 11:56:37 +0800 Subject: [PATCH 01/11] Add RAK4631 Ethernet Gateway with working JSON output to MQTT --- platformio.ini | 3 +- src/mqtt/MQTT.cpp | 12 +- src/serialization/MeshPacketSerializer.cpp | 4 +- .../MeshPacketSerializer_nRF52.cpp | 325 ++++++++++++++++++ variants/rak4631_mqtt_json/platformio.ini | 58 ++++ variants/rak4631_mqtt_json/variant.cpp | 45 +++ variants/rak4631_mqtt_json/variant.h | 273 +++++++++++++++ 7 files changed, 712 insertions(+), 8 deletions(-) create mode 100644 src/serialization/MeshPacketSerializer_nRF52.cpp create mode 100644 variants/rak4631_mqtt_json/platformio.ini create mode 100644 variants/rak4631_mqtt_json/variant.cpp create mode 100644 variants/rak4631_mqtt_json/variant.h diff --git a/platformio.ini b/platformio.ini index 87eb0afb7..a39de33c1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +;default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,6 +29,7 @@ default_envs = tbeam ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 +default_envs = rak4631_mqtt_json ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index d14c7a923..63bdd5bba 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -379,13 +379,13 @@ void MQTT::sendSubscriptions() std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 +// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } -#endif // ARCH_NRF52 +// #endif // ARCH_NRF52 } } #if !MESHTASTIC_EXCLUDE_PKI @@ -480,7 +480,7 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); -#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 +// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); @@ -496,7 +496,7 @@ void MQTT::publishQueuedMessages() publish(topicJson.c_str(), jsonString.c_str(), false); } } -#endif // ARCH_NRF52 +// #endif // ARCH_NRF52 mqttPool.release(env); } } @@ -562,7 +562,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); -#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 +// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); @@ -573,7 +573,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topicJson.c_str(), jsonString.c_str(), false); } } -#endif // ARCH_NRF52 +// #endif // ARCH_NRF52 } else { LOG_INFO("MQTT not connected, queueing packet\n"); if (mqttQueue.numFree() == 0) { diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index e00dde024..227205d84 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -1,3 +1,4 @@ +#ifndef NRF52_USE_JSON #include "MeshPacketSerializer.h" #include "JSON.h" #include "NodeDB.h" @@ -353,4 +354,5 @@ std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPa delete value; return jsonStr; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp new file mode 100644 index 000000000..7302e0915 --- /dev/null +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -0,0 +1,325 @@ +#ifdef NRF52_USE_JSON +#warning 'Using nRF52 Serializer' + +#include "MeshPacketSerializer.h" +#include "ArduinoJson.h" +#include "NodeDB.h" +#include "mesh/generated/meshtastic/mqtt.pb.h" +#include "mesh/generated/meshtastic/telemetry.pb.h" +#include "modules/RoutingModule.h" +#include +#include +#include "mesh/generated/meshtastic/remote_hardware.pb.h" + +StaticJsonDocument<512> jsonObj; +// StaticJsonDocument<512> msgPayload; + +std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) +{ + // the created jsonObj is immutable after creation, so + // we need to do the heavy lifting before assembling it. + std::string msgType; + // JSONObject jsonObj; + jsonObj.clear(); + + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + // JSONObject msgPayload; + switch (mp->decoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: { + msgType = "text"; + // convert bytes to string + if (shouldLog) + LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); + + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + // check if this is a JSON payload + StaticJsonDocument<512> text_doc; + DeserializationError error = deserializeJson(text_doc, payloadStr); + if (error) { + // if it isn't, then we need to create a json object + // with the string as the value + if (shouldLog) + LOG_INFO("text message payload is of type plaintext\n"); + jsonObj["payload"]["text"] = payloadStr; + // jsonObj["payload"] = msgPayload; + } else { + // if it is, then we can just use the json object + if (shouldLog) + LOG_INFO("text message payload is of type json\n"); + jsonObj["payload"] = text_doc; + } + break; + } + case meshtastic_PortNum_TELEMETRY_APP: { + msgType = "telemetry"; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { + jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; + jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; + jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; + jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; + jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; + } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { + jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; + jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; + jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; + jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; + jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; + jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; + jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; + jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; + jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; + jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; + jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; + jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; + jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; + } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; + jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; + jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; + jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; + jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; + jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; + } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { + jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; + jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; + jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; + jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; + jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; + jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + } + break; + } + case meshtastic_PortNum_NODEINFO_APP: { + msgType = "nodeinfo"; + meshtastic_User scratch; + meshtastic_User *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = decoded->id; + jsonObj["payload"]["longname"] = decoded->long_name; + jsonObj["payload"]["shortname"] = decoded->short_name; + jsonObj["payload"]["hardware"] = decoded->hw_model; + jsonObj["payload"]["role"] = (int)decoded->role; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + } + break; + } + case meshtastic_PortNum_POSITION_APP: { + msgType = "position"; + meshtastic_Position scratch; + meshtastic_Position *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { + decoded = &scratch; + if ((int)decoded->time) { + jsonObj["payload"]["time"] = (unsigned int)decoded->time; + } + if ((int)decoded->timestamp) { + jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; + } + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + if ((int)decoded->altitude) { + jsonObj["payload"]["altitude"] = (int)decoded->altitude; + } + if ((int)decoded->ground_speed) { + jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; + } + if (int(decoded->ground_track)) { + jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; + } + if (int(decoded->sats_in_view)) { + jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; + } + if ((int)decoded->PDOP) { + jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; + } + if ((int)decoded->HDOP) { + jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; + } + if ((int)decoded->VDOP) { + jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; + } + if ((int)decoded->precision_bits) { + jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + } + break; + } + case meshtastic_PortNum_WAYPOINT_APP: { + msgType = "position"; + meshtastic_Waypoint scratch; + meshtastic_Waypoint *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = (unsigned int)decoded->id; + jsonObj["payload"]["name"] = decoded->name; + jsonObj["payload"]["description"] = decoded->description; + jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; + jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + } + break; + } + case meshtastic_PortNum_NEIGHBORINFO_APP: { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) { + decoded = &scratch; + jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; + jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; + jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; + jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; + + JsonArray neighbors = jsonObj.createNestedArray("neighbors"); + JsonObject neighbors_0 = neighbors.createNestedObject(); + + for (uint8_t i = 0; i < decoded->neighbors_count; i++) { + neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; + neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors_0.clear(); + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + } + break; + } + case meshtastic_PortNum_TRACEROUTE_APP: { + if (mp->decoded.request_id) { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) { + decoded = &scratch; + JsonArray route = jsonObj.createNestedArray("route"); + + route.add(mp->to); + for (uint8_t i = 0; i < decoded->route_count; i++) { + route.add(decoded->route[i]); + } + route.add(mp->from); // Ended at the original destination (source of response) + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + } + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + jsonObj["payload"]["text"] = payloadStr; + break; + } + case meshtastic_PortNum_REMOTE_HARDWARE_APP: { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { + msgType = "gpios_changed"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { + msgType = "gpios_read_reply"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + } + break; + } + // add more packet types here if needed + default: + break; + } + } else if (shouldLog) { + LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + } + + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["type"] = msgType.c_str(); + jsonObj["sender"] = owner.id; + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + + // serialize and write it to the stream + + Serial.printf("serialized json message: \r\n"); + serializeJson(jsonObj, Serial); + Serial.println(""); + + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); + + if (shouldLog) + LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + + return jsonStr; +} + +std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) +{ + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["time_ms"] = (double)millis(); + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["want_ack"] = mp->want_ack; + + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + jsonObj["size"] = (unsigned int)mp->encrypted.size; + auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); + jsonObj["bytes"] = encryptedStr.c_str(); + + // serialize and write it to the stream + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); + + return jsonStr; +} +#endif \ No newline at end of file diff --git a/variants/rak4631_mqtt_json/platformio.ini b/variants/rak4631_mqtt_json/platformio.ini new file mode 100644 index 000000000..5d459a91a --- /dev/null +++ b/variants/rak4631_mqtt_json/platformio.ini @@ -0,0 +1,58 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +[env:rak4631_mqtt_json] +extends = nrf52840_base +board = wiscore_rak4631 +board_check = true +build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_mqtt_json -D RAK_4631 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 + -DNRF52_USE_JSON=1 +; -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_WIFI=1 +; -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_PKI=1 + -DMESHTASTIC_EXCLUDE_POWER_FSM=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_mqtt_json> + + + +lib_deps = + ${nrf52840_base.lib_deps} + ${networking_base.lib_deps} + melopero/Melopero RV3028@^1.1.0 + https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 + rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + https://github.com/meshtastic/RAK12034-BMX160.git#4821355fb10390ba8557dc43ca29a023bcfbb9d9 + bblanchon/ArduinoJson @ 6.21.4 +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds +;upload_protocol = jlink + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" +[env:rak4631_mqtt_json_dbg] +extends = env:rak4631 +board_level = extra + +; if the builtin version of openocd has a buggy version of semihosting, so use the external version +; platform_packages = platformio/tool-openocd@^3.1200.0 + +build_flags = + ${env:rak4631_mqtt_json.build_flags} + -D USE_SEMIHOSTING + +lib_deps = + ${env:rak4631_mqtt_json.lib_deps} + https://github.com/geeksville/Armduino-Semihosting.git#35b538fdf208c3530c1434cd099a08e486672ee4 + +; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better. +; However the built in openocd version in platformio has buggy support for TCP to semihosting. +; +; So I'm now trying the external openocd - but the openocd scripts for nrf52.cfg assume you are using a DAP adapter not an STLINK adapter. +; In theory I could change those scripts. But for now I'm trying going back to a DAP adapter but with the external openocd. + +upload_protocol = stlink +; eventually use platformio/tool-pyocd@^2.3600.0 instad +;upload_protocol = custom +;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE \ No newline at end of file diff --git a/variants/rak4631_mqtt_json/variant.cpp b/variants/rak4631_mqtt_json/variant.cpp new file mode 100644 index 000000000..e84b60b3b --- /dev/null +++ b/variants/rak4631_mqtt_json/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/rak4631_mqtt_json/variant.h b/variants/rak4631_mqtt_json/variant.h new file mode 100644 index 000000000..bc5541336 --- /dev/null +++ b/variants/rak4631_mqtt_json/variant.h @@ -0,0 +1,273 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK4630_ +#define _VARIANT_RAK4630_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ + +#define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion +#define BUTTON_NEED_PULLUP +#define PIN_BUTTON2 12 +#define PIN_BUTTON3 24 +#define PIN_BUTTON4 25 + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * eink display pins + */ + +#define PIN_EINK_CS (0 + 26) +#define PIN_EINK_BUSY (0 + 4) +#define PIN_EINK_DC (0 + 17) +#define PIN_EINK_RES (-1) +#define PIN_EINK_SCLK (0 + 3) +#define PIN_EINK_MOSI (0 + 30) // also called SDI + +// #define USE_EINK + +// RAKRGB +#define HAS_NCP5623 + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ + +// RAK4630 LoRa module + +/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) + +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) + +Important for successful SX1262 initialization: + +* Setup DIO2 to control the antenna switch +* Setup DIO3 to control the TCXO power supply +* Setup the SX1262 to use it's DCDC regulator and not the LDO +* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the +control of the antenna switch + +SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG + +*/ + +#define DETECTION_SENSOR_EN 4 + +#define USE_SX1262 +#define SX126X_CS (42) +#define SX126X_DIO1 (47) +#define SX126X_BUSY (46) +#define SX126X_RESET (38) +// #define SX126X_TXEN (39) +// #define SX126X_RXEN (37) +#define SX126X_POWER_EN (37) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Testing USB detection +#define NRF_APM + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +// #define PIN_GPS_RESET (34) +// #define PIN_GPS_EN PIN_3V3_EN +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press + +// RAK12002 RTC Module +#define RV3028_RTC (uint8_t)0b1010010 + +// RAK18001 Buzzer in Slot C +// #define PIN_BUZZER 21 // IO3 is PWM2 +// NEW: set this via protobuf instead! + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define HAS_RTC 1 + +#define HAS_ETHERNET 1 + +#define RAK_4631 1 + +#define PIN_ETHERNET_RESET 21 +#define PIN_ETHERNET_SS PIN_EINK_CS +#define ETH_SPI_PORT SPI1 +#define AQ_SET_PIN 10 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file From c6bffd7d7f27f84f44559b6d5b4df40e08eac70a Mon Sep 17 00:00:00 2001 From: Bernd Giesecke Date: Mon, 9 Sep 2024 12:39:14 +0800 Subject: [PATCH 02/11] Update platformio.ini Fix default build environment --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index a39de33c1..c10128609 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -;default_envs = tbeam +default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,7 +29,7 @@ ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -default_envs = rak4631_mqtt_json +;default_envs = rak4631_mqtt_json ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 @@ -161,4 +161,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee From d02ba45109beea43a2a49c878ac99d356b9e92ee Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Mon, 9 Sep 2024 12:40:56 +0800 Subject: [PATCH 03/11] Fix default build platform --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index a39de33c1..552e026aa 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -;default_envs = tbeam +default_envs = tbeam ;default_envs = pico ;default_envs = tbeam-s3-core ;default_envs = tbeam0.7 @@ -29,7 +29,7 @@ ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -default_envs = rak4631_mqtt_json +;default_envs = rak4631_mqtt_json ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 From 91887865818bda76ea62a12c00ee3096a5380981 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Tue, 10 Sep 2024 11:58:25 +0800 Subject: [PATCH 04/11] Fix #ifndef and rename the variant --- platformio.ini | 2 +- src/mqtt/MQTT.cpp | 12 +++++----- .../platformio.ini | 24 ++++++++++++------- .../variant.cpp | 0 .../variant.h | 0 5 files changed, 23 insertions(+), 15 deletions(-) rename variants/{rak4631_mqtt_json => rak4631_eth_gw}/platformio.ini (80%) rename variants/{rak4631_mqtt_json => rak4631_eth_gw}/variant.cpp (100%) rename variants/{rak4631_mqtt_json => rak4631_eth_gw}/variant.h (100%) diff --git a/platformio.ini b/platformio.ini index c10128609..f47b801ce 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,7 +29,7 @@ default_envs = tbeam ;default_envs = meshtastic-dr-dev ;default_envs = m5stack-coreink ;default_envs = rak4631 -;default_envs = rak4631_mqtt_json +;default_envs = rak4631_eth_gw ;default_envs = rak2560 ;default_envs = rak10701 ;default_envs = wio-e5 diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 63bdd5bba..33a22e5d4 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -379,13 +379,13 @@ void MQTT::sendSubscriptions() std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } -// #endif // ARCH_NRF52 +#endif // ARCH_NRF52 NRF52_USE_JSON } } #if !MESHTASTIC_EXCLUDE_PKI @@ -480,7 +480,7 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); -// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); @@ -496,7 +496,7 @@ void MQTT::publishQueuedMessages() publish(topicJson.c_str(), jsonString.c_str(), false); } } -// #endif // ARCH_NRF52 +#endif // ARCH_NRF52 NRF52_USE_JSON mqttPool.release(env); } } @@ -562,7 +562,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); -// #ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); @@ -573,7 +573,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topicJson.c_str(), jsonString.c_str(), false); } } -// #endif // ARCH_NRF52 +#endif // ARCH_NRF52 NRF52_USE_JSON } else { LOG_INFO("MQTT not connected, queueing packet\n"); if (mqttQueue.numFree() == 0) { diff --git a/variants/rak4631_mqtt_json/platformio.ini b/variants/rak4631_eth_gw/platformio.ini similarity index 80% rename from variants/rak4631_mqtt_json/platformio.ini rename to variants/rak4631_eth_gw/platformio.ini index 5d459a91a..bf8713293 100644 --- a/variants/rak4631_mqtt_json/platformio.ini +++ b/variants/rak4631_eth_gw/platformio.ini @@ -1,21 +1,29 @@ ; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 -[env:rak4631_mqtt_json] +[env:rak4631_eth_gw] extends = nrf52840_base board = wiscore_rak4631 board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_mqtt_json -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_eth_gw -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 -DNRF52_USE_JSON=1 -; -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_GPS=1 -DMESHTASTIC_EXCLUDE_WIFI=1 ; -DMESHTASTIC_EXCLUDE_SCREEN=1 - -DMESHTASTIC_EXCLUDE_PKI=1 +; -DMESHTASTIC_EXCLUDE_PKI=1 -DMESHTASTIC_EXCLUDE_POWER_FSM=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_mqtt_json> + + + + -DMESHTASTIC_EXCLUDE_POWERMON=1 + -DMESHTASTIC_EXCLUDE_TZ=1 + -DMESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 + -DMESHTASTIC_EXCLUDE_STOREFORWARD=1 + -DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1 + -DMESHTASTIC_EXCLUDE_WAYPOINT=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_eth_gw> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} @@ -31,7 +39,7 @@ lib_deps = ; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) ; programming time is about the same as the bootloader version. ; For information on this see the meshtastic developers documentation for "Development on the NRF52" -[env:rak4631_mqtt_json_dbg] +[env:rak4631_eth_gw_dbg] extends = env:rak4631 board_level = extra @@ -39,11 +47,11 @@ board_level = extra ; platform_packages = platformio/tool-openocd@^3.1200.0 build_flags = - ${env:rak4631_mqtt_json.build_flags} + ${env:rak4631_eth_gw.build_flags} -D USE_SEMIHOSTING lib_deps = - ${env:rak4631_mqtt_json.lib_deps} + ${env:rak4631_eth_gw.lib_deps} https://github.com/geeksville/Armduino-Semihosting.git#35b538fdf208c3530c1434cd099a08e486672ee4 ; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better. diff --git a/variants/rak4631_mqtt_json/variant.cpp b/variants/rak4631_eth_gw/variant.cpp similarity index 100% rename from variants/rak4631_mqtt_json/variant.cpp rename to variants/rak4631_eth_gw/variant.cpp diff --git a/variants/rak4631_mqtt_json/variant.h b/variants/rak4631_eth_gw/variant.h similarity index 100% rename from variants/rak4631_mqtt_json/variant.h rename to variants/rak4631_eth_gw/variant.h From 4fc3782ea3133f6bd097053687456d00725dd512 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Tue, 10 Sep 2024 18:43:47 +0800 Subject: [PATCH 05/11] Fix traceroute, neighborinfo and waypoint --- .../MeshPacketSerializer_nRF52.cpp | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 7302e0915..777c10ada 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -11,19 +11,18 @@ #include #include "mesh/generated/meshtastic/remote_hardware.pb.h" -StaticJsonDocument<512> jsonObj; -// StaticJsonDocument<512> msgPayload; +StaticJsonDocument<1024> jsonObj; +StaticJsonDocument<1024> arrayObj; std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) { // the created jsonObj is immutable after creation, so // we need to do the heavy lifting before assembling it. std::string msgType; - // JSONObject jsonObj; jsonObj.clear(); + arrayObj.clear(); if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - // JSONObject msgPayload; switch (mp->decoded.portnum) { case meshtastic_PortNum_TEXT_MESSAGE_APP: { msgType = "text"; @@ -43,7 +42,6 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (shouldLog) LOG_INFO("text message payload is of type plaintext\n"); jsonObj["payload"]["text"] = payloadStr; - // jsonObj["payload"] = msgPayload; } else { // if it is, then we can just use the json object if (shouldLog) @@ -96,6 +94,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + return ""; } break; } @@ -113,6 +112,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["payload"]["role"] = (int)decoded->role; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + return ""; } break; } @@ -157,6 +157,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; } break; } @@ -176,6 +177,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; } break; } @@ -192,16 +194,21 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; - JsonArray neighbors = jsonObj.createNestedArray("neighbors"); + JsonObject neighbors_obj = arrayObj.to(); + JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); JsonObject neighbors_0 = neighbors.createNestedObject(); for (uint8_t i = 0; i < decoded->neighbors_count; i++) { neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors[i+1] = neighbors_0; neighbors_0.clear(); } + neighbors.remove(0); + jsonObj["payload"]["neighbors"] = neighbors; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + return ""; } break; } @@ -214,17 +221,32 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, &scratch)) { decoded = &scratch; - JsonArray route = jsonObj.createNestedArray("route"); + JsonArray route = arrayObj.createNestedArray("route"); - route.add(mp->to); + auto addToRoute = [](JsonArray *route, NodeNum num) { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->add(long_name); + }; + + addToRoute(&route,mp->to); //route.add(mp->to); for (uint8_t i = 0; i < decoded->route_count; i++) { - route.add(decoded->route[i]); + addToRoute(&route,decoded->route[i]); //route.add(decoded->route[i]); } - route.add(mp->from); // Ended at the original destination (source of response) + addToRoute(&route,mp->from); //route.add(mp->from); // Ended at the original destination (source of response) + + jsonObj["payload"]["route"] = route; } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + return ""; } - } + } else { + LOG_WARN("Traceroute response not reported"); + return ""; + } break; } case meshtastic_PortNum_DETECTION_SENSOR_APP: { @@ -252,15 +274,19 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, } } else if (shouldLog) { LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + return ""; } break; } // add more packet types here if needed default: + LOG_WARN("Unsupported packet type %d\n",mp->decoded.portnum); + return ""; break; } } else if (shouldLog) { LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + return ""; } jsonObj["id"] = (unsigned int)mp->id; @@ -281,9 +307,9 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, // serialize and write it to the stream - Serial.printf("serialized json message: \r\n"); - serializeJson(jsonObj, Serial); - Serial.println(""); + // Serial.printf("serialized json message: \r\n"); + // serializeJson(jsonObj, Serial); + // Serial.println(""); std::string jsonStr = ""; serializeJson(jsonObj, jsonStr); @@ -296,6 +322,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) { + jsonObj.clear(); jsonObj["id"] = (unsigned int)mp->id; jsonObj["time_ms"] = (double)millis(); jsonObj["timestamp"] = (unsigned int)mp->rx_time; From 35cdc81d452dddf96edecb80609957756165d16f Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 09:53:13 +0800 Subject: [PATCH 06/11] Disable SCREEN and enable TZ --- src/AccelerometerThread.h | 16 +++++++++------- src/platform/nrf52/NRF52Bluetooth.cpp | 10 ++++++---- variants/rak4631_eth_gw/platformio.ini | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index c2910007e..c39504c08 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -94,8 +94,9 @@ class AccelerometerThread : public concurrency::OSThread wakeScreen(); return 500; } -#ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { +#if defined(RAK_4631) +#if !defined (MESHTASTIC_EXCLUDE_SCREEN) + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { sBmx160SensorData_t magAccel; sBmx160SensorData_t gAccel; @@ -165,7 +166,7 @@ class AccelerometerThread : public concurrency::OSThread } screen->setHeading(heading); - +#endif #endif } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { wakeScreen(); @@ -230,9 +231,10 @@ class AccelerometerThread : public concurrency::OSThread // It corresponds to isDoubleClick interrupt bmaSensor.enableWakeupIRQ(); #ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate - +#endif #endif } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) { LOG_DEBUG("LSM6DS3 initializing\n"); @@ -265,8 +267,8 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LSM6DS3TRC lsm; SensorBMA423 bmaSensor; bool BMA_IRQ = false; -#ifdef RAK_4631 - bool showingScreen = false; +#if defined(RAK_4631) && !defined(MESHTASTIC_EXCLUDE_SCREEN) + bool showingScreen = false; RAK_BMX160 bmx160; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 1405ea4f3..177255b2f 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -310,7 +310,9 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) + screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void + { char btPIN[16] = "888888"; snprintf(btPIN, sizeof(btPIN), "%06u", configuredPasskey); int x_offset = display->width() / 2; @@ -333,9 +335,9 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke String deviceName = "Name: "; deviceName.concat(getDeviceName()); y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; - display->drawString(x_offset + x, y_offset + y, deviceName); - }); - if (match_request) { + display->drawString(x_offset + x, y_offset + y, deviceName); }); +#endif + if (match_request) { uint32_t start_time = millis(); while (millis() < start_time + 30000) { if (!Bluefruit.connected(conn_handle)) diff --git a/variants/rak4631_eth_gw/platformio.ini b/variants/rak4631_eth_gw/platformio.ini index bf8713293..62b7e737d 100644 --- a/variants/rak4631_eth_gw/platformio.ini +++ b/variants/rak4631_eth_gw/platformio.ini @@ -12,11 +12,11 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_eth_gw -D RAK_4631 -DNRF52_USE_JSON=1 -DMESHTASTIC_EXCLUDE_GPS=1 -DMESHTASTIC_EXCLUDE_WIFI=1 -; -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_SCREEN=1 ; -DMESHTASTIC_EXCLUDE_PKI=1 -DMESHTASTIC_EXCLUDE_POWER_FSM=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 - -DMESHTASTIC_EXCLUDE_TZ=1 +; -DMESHTASTIC_EXCLUDE_TZ=1 -DMESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 From a388e78842c308427635281024c32016d4e2ccb5 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 10:00:46 +0800 Subject: [PATCH 07/11] Fix platformio.ini conflict --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index f47b801ce..1847bd113 100644 --- a/platformio.ini +++ b/platformio.ini @@ -162,3 +162,4 @@ lib_deps = https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee + https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file From ca8d2204ba4ed0cf147bf64fe4dcc4d0e4846cd4 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 11:06:13 +0800 Subject: [PATCH 08/11] Fix formatting --- src/AccelerometerThread.h | 6 +- src/mqtt/MQTT.cpp | 1103 +++++++++-------- src/platform/nrf52/NRF52Bluetooth.cpp | 452 +++---- .../MeshPacketSerializer_nRF52.cpp | 647 +++++----- 4 files changed, 1194 insertions(+), 1014 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index c39504c08..37e7aab0d 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -96,7 +96,7 @@ class AccelerometerThread : public concurrency::OSThread } #if defined(RAK_4631) #if !defined (MESHTASTIC_EXCLUDE_SCREEN) - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { sBmx160SensorData_t magAccel; sBmx160SensorData_t gAccel; @@ -232,7 +232,7 @@ class AccelerometerThread : public concurrency::OSThread bmaSensor.enableWakeupIRQ(); #ifdef RAK_4631 #if !defined(MESHTASTIC_EXCLUDE_SCREEN) - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { + } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate #endif #endif @@ -268,7 +268,7 @@ class AccelerometerThread : public concurrency::OSThread SensorBMA423 bmaSensor; bool BMA_IRQ = false; #if defined(RAK_4631) && !defined(MESHTASTIC_EXCLUDE_SCREEN) - bool showingScreen = false; + bool showingScreen = false; RAK_BMX160 bmx160; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 33a22e5d4..3e0e692b4 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -33,161 +33,194 @@ Allocator &mqttPool = staticMqttPool; void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length) { - mqtt->onReceive(topic, payload, length); + mqtt->onReceive(topic, payload, length); } void MQTT::onClientProxyReceive(meshtastic_MqttClientProxyMessage msg) { - onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); + onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); } void MQTT::onReceive(char *topic, byte *payload, size_t length) { - meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; + meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; - 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 - char payloadStr[length + 1]; - memcpy(payloadStr, payload, length); - payloadStr[length] = 0; // null terminated string - JSONValue *json_value = JSON::Parse(payloadStr); - if (json_value != NULL) { - // check if it is a valid envelope - JSONObject json; - json = json_value->AsObject(); + 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 + char payloadStr[length + 1]; + memcpy(payloadStr, payload, length); + payloadStr[length] = 0; // null terminated string + JSONValue *json_value = JSON::Parse(payloadStr); + if (json_value != NULL) + { + // check if it is a valid envelope + JSONObject json; + json = json_value->AsObject(); - // parse the channel name from the topic string - // the topic has been checked above for having jsonTopic prefix, so just move past it - char *ptr = topic + jsonTopic.length(); - ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character - meshtastic_Channel sendChannel = channels.getByName(ptr); - // We allow downlink JSON packets only on a channel named "mqtt" - if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && - sendChannel.settings.downlink_enabled) { - if (isValidJsonEnvelope(json)) { - // this is a valid envelope - if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) { - std::string jsonPayloadStr = json["payload"]->AsString(); - LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); + // parse the channel name from the topic string + // the topic has been checked above for having jsonTopic prefix, so just move past it + char *ptr = topic + jsonTopic.length(); + ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character + meshtastic_Channel sendChannel = channels.getByName(ptr); + // We allow downlink JSON packets only on a channel named "mqtt" + if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && + sendChannel.settings.downlink_enabled) + { + if (isValidJsonEnvelope(json)) + { + // this is a valid envelope + if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) + { + std::string jsonPayloadStr = json["payload"]->AsString(); + LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); - // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { - memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); - p->decoded.payload.size = jsonPayloadStr.length(); - service->sendToMesh(p, RX_SRC_LOCAL); - } else { - LOG_WARN("Received MQTT json payload too long, dropping\n"); - } - } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) { - // invent the "sendposition" type for a valid envelope - JSONObject posit; - posit = json["payload"]->AsObject(); // get nested JSON Position - meshtastic_Position pos = meshtastic_Position_init_default; - if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) - pos.latitude_i = posit["latitude_i"]->AsNumber(); - if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) - pos.longitude_i = posit["longitude_i"]->AsNumber(); - if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) - pos.altitude = posit["altitude"]->AsNumber(); - if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) - pos.time = posit["time"]->AsNumber(); + // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) + { + memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); + p->decoded.payload.size = jsonPayloadStr.length(); + service->sendToMesh(p, RX_SRC_LOCAL); + } + else + { + LOG_WARN("Received MQTT json payload too long, dropping\n"); + } + } + else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) + { + // invent the "sendposition" type for a valid envelope + JSONObject posit; + posit = json["payload"]->AsObject(); // get nested JSON Position + meshtastic_Position pos = meshtastic_Position_init_default; + if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) + pos.latitude_i = posit["latitude_i"]->AsNumber(); + if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) + pos.longitude_i = posit["longitude_i"]->AsNumber(); + if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) + pos.altitude = posit["altitude"]->AsNumber(); + if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) + pos.time = posit["time"]->AsNumber(); - // construct protobuf data packet using POSITION, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_POSITION_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - p->decoded.payload.size = - pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), - &meshtastic_Position_msg, &pos); // make the Data protobuf from position - service->sendToMesh(p, RX_SRC_LOCAL); - } else { - LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); - } - } else { - LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); - } - } else { - LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); - } - } else { - // no json, this is an invalid payload - LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); - } - delete json_value; - } else { - if (length == 0) { - LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); - return; - } else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } else { - if (e.channel_id == NULL || e.gateway_id == NULL) { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } - meshtastic_Channel ch = channels.getByName(e.channel_id); - if (strcmp(e.gateway_id, owner.id) == 0) { - // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. - // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node - // receives it when we get our own packet back. Then we'll stop our retransmissions. - if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) - routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); - else - LOG_INFO("Ignoring downlink message we originally sent.\n"); - } else { - // Find channel by channel_id and check downlink_enabled - if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || - (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) { - LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); - meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); - p->via_mqtt = true; // Mark that the packet was received via MQTT + // construct protobuf data packet using POSITION, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_POSITION_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + &meshtastic_Position_msg, &pos); // make the Data protobuf from position + service->sendToMesh(p, RX_SRC_LOCAL); + } + else + { + LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); + } + } + else + { + LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); + } + } + else + { + LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); + } + } + else + { + // no json, this is an invalid payload + LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); + } + delete json_value; + } + else + { + if (length == 0) + { + LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); + return; + } + else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) + { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } + else + { + if (e.channel_id == NULL || e.gateway_id == NULL) + { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } + meshtastic_Channel ch = channels.getByName(e.channel_id); + if (strcmp(e.gateway_id, owner.id) == 0) + { + // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. + // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node + // receives it when we get our own packet back. Then we'll stop our retransmissions. + if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) + routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); + else + LOG_INFO("Ignoring downlink message we originally sent.\n"); + } + else + { + // Find channel by channel_id and check downlink_enabled + if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || + (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) + { + LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); + meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); + p->via_mqtt = true; // Mark that the packet was received via MQTT - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - p->channel = ch.index; - } + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) + { + p->channel = ch.index; + } - // PKI messages get accepted even if we can't decrypt - if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && - strcmp(e.channel_id, "PKI") == 0) { - meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); - meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); - // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's - // likely they discovered each other via a channel we have downlink enabled for - if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) - router->enqueueReceivedMessage(p); - } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key - router->enqueueReceivedMessage(p); - else - packetPool.release(p); - } - } - } - // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) - free(e.channel_id); - free(e.gateway_id); - free(e.packet); - } + // PKI messages get accepted even if we can't decrypt + if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && + strcmp(e.channel_id, "PKI") == 0) + { + meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); + meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); + // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's + // likely they discovered each other via a channel we have downlink enabled for + if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) + router->enqueueReceivedMessage(p); + } + else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key + router->enqueueReceivedMessage(p); + else + packetPool.release(p); + } + } + } + // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) + free(e.channel_id); + free(e.gateway_id); + free(e.packet); + } } void mqttInit() { - new MQTT(); + new MQTT(); } #if HAS_NETWORKING @@ -196,483 +229,551 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) #endif { - if (moduleConfig.mqtt.enabled) { - LOG_DEBUG("Initializing MQTT\n"); + if (moduleConfig.mqtt.enabled) + { + LOG_DEBUG("Initializing MQTT\n"); - assert(!mqtt); - mqtt = this; + assert(!mqtt); + mqtt = this; - if (*moduleConfig.mqtt.root) { - cryptTopic = moduleConfig.mqtt.root + cryptTopic; - jsonTopic = moduleConfig.mqtt.root + jsonTopic; - mapTopic = moduleConfig.mqtt.root + mapTopic; - } else { - cryptTopic = "msh" + cryptTopic; - jsonTopic = "msh" + jsonTopic; - mapTopic = "msh" + mapTopic; - } + if (*moduleConfig.mqtt.root) + { + cryptTopic = moduleConfig.mqtt.root + cryptTopic; + jsonTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + mapTopic; + } + else + { + cryptTopic = "msh" + cryptTopic; + jsonTopic = "msh" + jsonTopic; + mapTopic = "msh" + mapTopic; + } - if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { - map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, - default_map_position_precision); - map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( - moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); - } + if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) + { + map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, + default_map_position_precision); + map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( + moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); + } #if HAS_NETWORKING - if (!moduleConfig.mqtt.proxy_to_client_enabled) - pubSub.setCallback(mqttCallback); + if (!moduleConfig.mqtt.proxy_to_client_enabled) + pubSub.setCallback(mqttCallback); #endif - if (moduleConfig.mqtt.proxy_to_client_enabled) { - LOG_INFO("MQTT configured to use client proxy...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; - publishNodeInfo(); - } - // preflightSleepObserver.observe(&preflightSleep); - } else { - disable(); - } + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + LOG_INFO("MQTT configured to use client proxy...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; + publishNodeInfo(); + } + // preflightSleepObserver.observe(&preflightSleep); + } + else + { + disable(); + } } bool MQTT::isConnectedDirectly() { #if HAS_NETWORKING - return pubSub.connected(); + return pubSub.connected(); #else - return false; + return false; #endif } bool MQTT::publish(const char *topic, const char *payload, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; - strcpy(msg->topic, topic); - strcpy(msg->payload_variant.text, payload); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; + strcpy(msg->topic, topic); + strcpy(msg->payload_variant.text, payload); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) { - return pubSub.publish(topic, payload, retained); - } + else if (isConnectedDirectly()) + { + return pubSub.publish(topic, payload, retained); + } #endif - return false; + return false; } bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; - strcpy(msg->topic, topic); - msg->payload_variant.data.size = length; - memcpy(msg->payload_variant.data.bytes, payload, length); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; + strcpy(msg->topic, topic); + msg->payload_variant.data.size = length; + memcpy(msg->payload_variant.data.bytes, payload, length); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) { - return pubSub.publish(topic, payload, length, retained); - } + else if (isConnectedDirectly()) + { + return pubSub.publish(topic, payload, length, retained); + } #endif - return false; + return false; } void MQTT::reconnect() { - if (wantsLink()) { - if (moduleConfig.mqtt.proxy_to_client_enabled) { - LOG_INFO("MQTT connecting via client proxy instead...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; + if (wantsLink()) + { + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + LOG_INFO("MQTT connecting via client proxy instead...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - return; // Don't try to connect directly to the server - } + publishNodeInfo(); + return; // Don't try to connect directly to the server + } #if HAS_NETWORKING - // Defaults - int serverPort = 1883; - const char *serverAddr = default_mqtt_address; - const char *mqttUsername = default_mqtt_username; - const char *mqttPassword = default_mqtt_password; + // Defaults + int serverPort = 1883; + const char *serverAddr = default_mqtt_address; + const char *mqttUsername = default_mqtt_username; + const char *mqttPassword = default_mqtt_password; - if (*moduleConfig.mqtt.address) { - serverAddr = moduleConfig.mqtt.address; - mqttUsername = moduleConfig.mqtt.username; - mqttPassword = moduleConfig.mqtt.password; - } + if (*moduleConfig.mqtt.address) + { + serverAddr = moduleConfig.mqtt.address; + mqttUsername = moduleConfig.mqtt.username; + mqttPassword = moduleConfig.mqtt.password; + } #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (moduleConfig.mqtt.tls_enabled) { - // change default for encrypted to 8883 - try { - serverPort = 8883; - wifiSecureClient.setInsecure(); + if (moduleConfig.mqtt.tls_enabled) + { + // change default for encrypted to 8883 + try + { + serverPort = 8883; + wifiSecureClient.setInsecure(); - pubSub.setClient(wifiSecureClient); - LOG_INFO("Using TLS-encrypted session\n"); - } catch (const std::exception &e) { - LOG_ERROR("MQTT ERROR: %s\n", e.what()); - } - } else { - LOG_INFO("Using non-TLS-encrypted session\n"); - pubSub.setClient(mqttClient); - } + pubSub.setClient(wifiSecureClient); + LOG_INFO("Using TLS-encrypted session\n"); + } + catch (const std::exception &e) + { + LOG_ERROR("MQTT ERROR: %s\n", e.what()); + } + } + else + { + LOG_INFO("Using non-TLS-encrypted session\n"); + pubSub.setClient(mqttClient); + } #elif HAS_NETWORKING - pubSub.setClient(mqttClient); + pubSub.setClient(mqttClient); #endif - String server = String(serverAddr); - int delimIndex = server.indexOf(':'); - if (delimIndex > 0) { - String port = server.substring(delimIndex + 1, server.length()); - server[delimIndex] = 0; - serverPort = port.toInt(); - serverAddr = server.c_str(); - } - pubSub.setServer(serverAddr, serverPort); - pubSub.setBufferSize(512); + String server = String(serverAddr); + int delimIndex = server.indexOf(':'); + if (delimIndex > 0) + { + String port = server.substring(delimIndex + 1, server.length()); + server[delimIndex] = 0; + serverPort = port.toInt(); + serverAddr = server.c_str(); + } + pubSub.setServer(serverAddr, serverPort); + pubSub.setBufferSize(512); - LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, - serverPort, mqttUsername, mqttPassword); + LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, + serverPort, mqttUsername, mqttPassword); - bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); - if (connected) { - LOG_INFO("MQTT connected\n"); - enabled = true; // Start running background process again - runASAP = true; - reconnectCount = 0; + bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); + if (connected) + { + LOG_INFO("MQTT connected\n"); + enabled = true; // Start running background process again + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - sendSubscriptions(); - } else { + publishNodeInfo(); + sendSubscriptions(); + } + else + { #if HAS_WIFI && !defined(ARCH_PORTDUINO) - reconnectCount++; - LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); - if (reconnectCount >= reconnectMax) { - needReconnect = true; - wifiReconnect->setIntervalFromNow(0); - reconnectCount = 0; - } + reconnectCount++; + LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); + if (reconnectCount >= reconnectMax) + { + needReconnect = true; + wifiReconnect->setIntervalFromNow(0); + reconnectCount = 0; + } #endif - } + } #endif - } + } } void MQTT::sendSubscriptions() { #if HAS_NETWORKING - bool hasDownlink = false; - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) { - const auto &ch = channels.getByIndex(i); - if (ch.settings.downlink_enabled) { - hasDownlink = true; - std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### - if (moduleConfig.mqtt.json_enabled == true) { - std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); - pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? - } + bool hasDownlink = false; + size_t numChan = channels.getNumChannels(); + for (size_t i = 0; i < numChan; i++) + { + const auto &ch = channels.getByIndex(i); + if (ch.settings.downlink_enabled) + { + hasDownlink = true; + std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### + if (moduleConfig.mqtt.json_enabled == true) + { + std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); + pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? + } #endif // ARCH_NRF52 NRF52_USE_JSON - } - } + } + } #if !MESHTASTIC_EXCLUDE_PKI - if (hasDownlink) { - std::string topic = cryptTopic + "PKI/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); - } + if (hasDownlink) + { + std::string topic = cryptTopic + "PKI/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); + } #endif #endif } bool MQTT::wantsLink() const { - bool hasChannelorMapReport = - moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); + bool hasChannelorMapReport = + moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); - if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) - return true; + if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) + return true; #if HAS_WIFI - return hasChannelorMapReport && WiFi.isConnected(); + return hasChannelorMapReport && WiFi.isConnected(); #endif #if HAS_ETHERNET - return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; + return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; #endif - return false; + return false; } int32_t MQTT::runOnce() { #if HAS_NETWORKING - if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) - return disable(); + if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) + return disable(); - bool wantConnection = wantsLink(); + bool wantConnection = wantsLink(); - perhapsReportToMap(); + perhapsReportToMap(); - // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server - if (moduleConfig.mqtt.proxy_to_client_enabled) { - publishQueuedMessages(); - return 200; - } + // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server + if (moduleConfig.mqtt.proxy_to_client_enabled) + { + publishQueuedMessages(); + return 200; + } + else if (!pubSub.loop()) + { + if (!wantConnection) + return 5000; // If we don't want connection now, check again in 5 secs + else + { + reconnect(); + // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP + // connections are EXPENSIVE so try rarely) + if (isConnectedDirectly()) + { + publishQueuedMessages(); + return 200; + } + else + return 30000; + } + } + else + { + // we are connected to server, check often for new requests on the TCP port + if (!wantConnection) + { + LOG_INFO("MQTT link not needed, dropping\n"); + pubSub.disconnect(); + } - else if (!pubSub.loop()) { - if (!wantConnection) - return 5000; // If we don't want connection now, check again in 5 secs - else { - reconnect(); - // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP - // connections are EXPENSIVE so try rarely) - if (isConnectedDirectly()) { - publishQueuedMessages(); - return 200; - } else - return 30000; - } - } else { - // we are connected to server, check often for new requests on the TCP port - if (!wantConnection) { - LOG_INFO("MQTT link not needed, dropping\n"); - pubSub.disconnect(); - } - - powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) - return 20; - } + powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) + return 20; + } #endif - return 30000; + return 30000; } void MQTT::publishNodeInfo() { - // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) + // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) } void MQTT::publishQueuedMessages() { - if (!mqttQueue.isEmpty()) { - LOG_DEBUG("Publishing enqueued MQTT message\n"); - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic; - if (env->packet->pki_encrypted) { - topic = cryptTopic + "PKI/" + owner.id; - } else { - topic = cryptTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); + if (!mqttQueue.isEmpty()) + { + LOG_DEBUG("Publishing enqueued MQTT message\n"); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic; + if (env->packet->pki_encrypted) + { + topic = cryptTopic + "PKI/" + owner.id; + } + else + { + topic = cryptTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); - if (jsonString.length() != 0) { - std::string topicJson; - if (env->packet->pki_encrypted) { - topicJson = jsonTopic + "PKI/" + owner.id; - } else { - topicJson = jsonTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### + if (moduleConfig.mqtt.json_enabled) + { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); + if (jsonString.length() != 0) + { + std::string topicJson; + if (env->packet->pki_encrypted) + { + topicJson = jsonTopic + "PKI/" + owner.id; + } + else + { + topicJson = jsonTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - mqttPool.release(env); - } + mqttPool.release(env); + } } void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex) { - if (mp.via_mqtt) - return; // Don't send messages that came from MQTT back into MQTT - bool uplinkEnabled = false; - for (int i = 0; i <= 7; i++) { - if (channels.getByIndex(i).settings.uplink_enabled) - uplinkEnabled = true; - } - if (!uplinkEnabled) - return; // no channels have an uplink enabled - auto &ch = channels.getByIndex(chIndex); + if (mp.via_mqtt) + return; // Don't send messages that came from MQTT back into MQTT + bool uplinkEnabled = false; + for (int i = 0; i <= 7; i++) + { + if (channels.getByIndex(i).settings.uplink_enabled) + uplinkEnabled = true; + } + if (!uplinkEnabled) + return; // no channels have an uplink enabled + auto &ch = channels.getByIndex(chIndex); - if (!mp.pki_encrypted) { - if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { - LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); - return; - } + if (!mp.pki_encrypted) + { + if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) + { + LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); + return; + } - // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. - if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && - !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && - (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || - (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { - LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); - return; - } + // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. + if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && + !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && + (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || + (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) + { + LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); + return; + } - if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && - (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || - mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { - LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); - return; - } - } - if (mp.pki_encrypted || ch.settings.uplink_enabled) { - const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) + { + LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); + return; + } + } + if (mp.pki_encrypted || ch.settings.uplink_enabled) + { + const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); - meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); - env->channel_id = (char *)channelId; - env->gateway_id = owner.id; + meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); + env->channel_id = (char *)channelId; + env->gateway_id = owner.id; - LOG_DEBUG("MQTT onSend - Publishing "); - if (moduleConfig.mqtt.encryption_enabled) { - env->packet = (meshtastic_MeshPacket *)∓ - LOG_DEBUG("encrypted message\n"); - } else if (mp_decoded.which_payload_variant == - meshtastic_MeshPacket_decoded_tag) { // Don't upload a still-encrypted PKI packet - env->packet = (meshtastic_MeshPacket *)&mp_decoded; - LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); - } + LOG_DEBUG("MQTT onSend - Publishing "); + if (moduleConfig.mqtt.encryption_enabled) + { + env->packet = (meshtastic_MeshPacket *)∓ + LOG_DEBUG("encrypted message\n"); + } + else if (mp_decoded.which_payload_variant == + meshtastic_MeshPacket_decoded_tag) + { // Don't upload a still-encrypted PKI packet + env->packet = (meshtastic_MeshPacket *)&mp_decoded; + LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); + } - if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic = cryptTopic + channelId + "/" + owner.id; - LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); + if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) + { + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic = cryptTopic + channelId + "/" + owner.id; + LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); - if (jsonString.length() != 0) { - std::string topicJson = jsonTopic + channelId + "/" + owner.id; - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } +#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### + if (moduleConfig.mqtt.json_enabled) + { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); + if (jsonString.length() != 0) + { + std::string topicJson = jsonTopic + channelId + "/" + owner.id; + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - } else { - LOG_INFO("MQTT not connected, queueing packet\n"); - if (mqttQueue.numFree() == 0) { - LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); - meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); - if (d) - mqttPool.release(d); - } - // make a copy of serviceEnvelope and queue it - meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); - assert(mqttQueue.enqueue(copied, 0)); - } - mqttPool.release(env); - } + } + else + { + LOG_INFO("MQTT not connected, queueing packet\n"); + if (mqttQueue.numFree() == 0) + { + LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); + meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); + if (d) + mqttPool.release(d); + } + // make a copy of serviceEnvelope and queue it + meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); + assert(mqttQueue.enqueue(copied, 0)); + } + mqttPool.release(env); + } } void MQTT::perhapsReportToMap() { - if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) - return; + if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) + return; - if (millis() - last_report_to_map < map_publish_interval_msecs) { - return; - } else { - if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { - last_report_to_map = millis(); - if (map_position_precision == 0) - LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); - if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) - LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); - return; - } + if (millis() - last_report_to_map < map_publish_interval_msecs) + { + return; + } + else + { + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) + { + last_report_to_map = millis(); + if (map_position_precision == 0) + LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) + LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); + return; + } - // Allocate ServiceEnvelope and fill it - meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); - se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id - se->gateway_id = owner.id; + // Allocate ServiceEnvelope and fill it + meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); + se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id + se->gateway_id = owner.id; - // Allocate MeshPacket and fill it - meshtastic_MeshPacket *mp = packetPool.allocZeroed(); - mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; - mp->from = nodeDB->getNodeNum(); - mp->to = NODENUM_BROADCAST; - mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; + // Allocate MeshPacket and fill it + meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + mp->from = nodeDB->getNodeNum(); + mp->to = NODENUM_BROADCAST; + mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; - // Fill MapReport message - meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; - memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); - memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); - mapReport.role = config.device.role; - mapReport.hw_model = owner.hw_model; - strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); - mapReport.region = config.lora.region; - mapReport.modem_preset = config.lora.modem_preset; - mapReport.has_default_channel = channels.hasDefaultChannel(); + // Fill MapReport message + meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; + memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); + memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); + mapReport.role = config.device.role; + mapReport.hw_model = owner.hw_model; + strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); + mapReport.region = config.lora.region; + mapReport.modem_preset = config.lora.modem_preset; + mapReport.has_default_channel = channels.hasDefaultChannel(); - // Set position with precision (same as in PositionModule) - if (map_position_precision < 32 && map_position_precision > 0) { - mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.latitude_i += (1 << (31 - map_position_precision)); - mapReport.longitude_i += (1 << (31 - map_position_precision)); - } else { - mapReport.latitude_i = localPosition.latitude_i; - mapReport.longitude_i = localPosition.longitude_i; - } - mapReport.altitude = localPosition.altitude; - mapReport.position_precision = map_position_precision; + // Set position with precision (same as in PositionModule) + if (map_position_precision < 32 && map_position_precision > 0) + { + mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.latitude_i += (1 << (31 - map_position_precision)); + mapReport.longitude_i += (1 << (31 - map_position_precision)); + } + else + { + mapReport.latitude_i = localPosition.latitude_i; + mapReport.longitude_i = localPosition.longitude_i; + } + mapReport.altitude = localPosition.altitude; + mapReport.position_precision = map_position_precision; - mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); + mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); - // Encode MapReport message and set it to MeshPacket in ServiceEnvelope - mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), - &meshtastic_MapReport_msg, &mapReport); - se->packet = mp; + // Encode MapReport message and set it to MeshPacket in ServiceEnvelope + mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), + &meshtastic_MapReport_msg, &mapReport); + se->packet = mp; - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); - LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); - publish(mapTopic.c_str(), bytes, numBytes, false); + LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); + publish(mapTopic.c_str(), bytes, numBytes, false); - // Release the allocated memory for ServiceEnvelope and MeshPacket - mqttPool.release(se); - packetPool.release(mp); + // Release the allocated memory for ServiceEnvelope and MeshPacket + mqttPool.release(se); + packetPool.release(mp); - // Update the last report time - last_report_to_map = millis(); - } + // Update the last report time + last_report_to_map = millis(); + } } bool MQTT::isValidJsonEnvelope(JSONObject &json) { - // if "sender" is provided, avoid processing packets we uplinked - return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && - (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number - (json.find("from") != json.end()) && json["from"]->IsNumber() && - (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us - (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type - (json.find("payload") != json.end()); // should have a payload + // if "sender" is provided, avoid processing packets we uplinked + return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number + (json.find("from") != json.end()) && json["from"]->IsNumber() && + (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us + (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type + (json.find("payload") != json.end()); // should have a payload } diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 177255b2f..9ed2a0d6a 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -19,7 +19,7 @@ static BLEBas blebas; // BAS (Battery Service) helper class instance #ifndef BLE_DFU_SECURE static BLEDfu bledfu; // DFU software update helper service #else -static BLEDfuSecure bledfusecure; // DFU software update helper service +static BLEDfuSecure bledfusecure; // DFU software update helper service #endif // This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in @@ -32,23 +32,23 @@ static uint16_t connectionHandle; class BluetoothPhoneAPI : public PhoneAPI { - /** - * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) - */ - virtual void onNowHasData(uint32_t fromRadioNum) override - { - PhoneAPI::onNowHasData(fromRadioNum); + /** + * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) + */ + virtual void onNowHasData(uint32_t fromRadioNum) override + { + PhoneAPI::onNowHasData(fromRadioNum); - LOG_INFO("BLE notify fromNum\n"); - fromNum.notify32(fromRadioNum); - } + LOG_INFO("BLE notify fromNum\n"); + fromNum.notify32(fromRadioNum); + } - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override - { - BLEConnection *connection = Bluefruit.Connection(connectionHandle); - return connection->connected(); - } + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override + { + BLEConnection *connection = Bluefruit.Connection(connectionHandle); + return connection->connected(); + } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; @@ -56,12 +56,12 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI; void onConnect(uint16_t conn_handle) { - // Get the reference to current connection - BLEConnection *connection = Bluefruit.Connection(conn_handle); - connectionHandle = conn_handle; - char central_name[32] = {0}; - connection->getPeerName(central_name, sizeof(central_name)); - LOG_INFO("BLE Connected to %s\n", central_name); + // Get the reference to current connection + BLEConnection *connection = Bluefruit.Connection(conn_handle); + connectionHandle = conn_handle; + char central_name[32] = {0}; + connection->getPeerName(central_name, sizeof(central_name)); + LOG_INFO("BLE Connected to %s\n", central_name); } /** * Callback invoked when a connection is dropped @@ -70,246 +70,258 @@ void onConnect(uint16_t conn_handle) */ void onDisconnect(uint16_t conn_handle, uint8_t reason) { - // FIXME - we currently assume only one active connection - LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); + // FIXME - we currently assume only one active connection + LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { - // Display the raw request packet - LOG_INFO("CCCD Updated: %u\n", cccd_value); - // Check the characteristic this CCCD update is associated with in case - // this handler is used for multiple CCCD records. + // Display the raw request packet + LOG_INFO("CCCD Updated: %u\n", cccd_value); + // Check the characteristic this CCCD update is associated with in case + // this handler is used for multiple CCCD records. - // According to the GATT spec: cccd value = 0x0001 means notifications are enabled - // and cccd value = 0x0002 means indications are enabled + // According to the GATT spec: cccd value = 0x0001 means notifications are enabled + // and cccd value = 0x0002 means indications are enabled - if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) { - auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) : chr->notifyEnabled(conn_hdl); - if (result) { - LOG_INFO("Notify/Indicate enabled\n"); - } else { - LOG_INFO("Notify/Indicate disabled\n"); - } - } + if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) + { + auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) : chr->notifyEnabled(conn_hdl); + if (result) + { + LOG_INFO("Notify/Indicate enabled\n"); + } + else + { + LOG_INFO("Notify/Indicate disabled\n"); + } + } } void startAdv(void) { - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - // IncludeService UUID - // Bluefruit.ScanResponse.addService(meshBleService); - Bluefruit.ScanResponse.addTxPower(); - Bluefruit.ScanResponse.addName(); - // Include Name - // Bluefruit.Advertising.addName(); - Bluefruit.Advertising.addService(meshBleService); - /* Start Advertising - * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms - * - Timeout for fast mode is 30 seconds - * - Start(timeout) with timeout = 0 will advertise forever (until connected) - * - * For recommended advertising interval - * https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + // IncludeService UUID + // Bluefruit.ScanResponse.addService(meshBleService); + Bluefruit.ScanResponse.addTxPower(); + Bluefruit.ScanResponse.addName(); + // Include Name + // Bluefruit.Advertising.addName(); + Bluefruit.Advertising.addService(meshBleService); + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X } // Just ack that the caller is allowed to read static void authorizeRead(uint16_t conn_hdl) { - ble_gatts_rw_authorize_reply_params_t reply = {.type = BLE_GATTS_AUTHORIZE_TYPE_READ}; - reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; - sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); + ble_gatts_rw_authorize_reply_params_t reply = {.type = BLE_GATTS_AUTHORIZE_TYPE_READ}; + reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; + sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); } /** * client is starting read, pull the bytes from our API class */ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt_read_t *request) { - if (request->offset == 0) { - // If the read is long, we will get multiple authorize invocations - we only populate data on the first - size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); - // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue - // or make empty if the queue is empty - fromRadio.write(fromRadioBytes, numBytes); - } else { - // LOG_INFO("Ignoring successor read\n"); - } - authorizeRead(conn_hdl); + if (request->offset == 0) + { + // If the read is long, we will get multiple authorize invocations - we only populate data on the first + size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); + // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue + // or make empty if the queue is empty + fromRadio.write(fromRadioBytes, numBytes); + } + else + { + // LOG_INFO("Ignoring successor read\n"); + } + authorizeRead(conn_hdl); } void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { - LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); + LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); + bluetoothPhoneAPI->handleToRadio(data, len); } void setupMeshService(void) { - bluetoothPhoneAPI = new BluetoothPhoneAPI(); - meshBleService.begin(); - // Note: You must call .begin() on the BLEService before calling .begin() on - // any characteristic(s) within that service definition.. Calling .begin() on - // a BLECharacteristic will cause it to be added to the last BLEService that - // was 'begin()'ed! - auto secMode = - config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN ? SECMODE_OPEN : SECMODE_ENC_NO_MITM; - fromNum.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ); - fromNum.setPermission(secMode, SECMODE_NO_ACCESS); // FIXME, secure this!!! - fromNum.setFixedLen( - 0); // Variable len (either 0 or 4) FIXME consider changing protocol so it is fixed 4 byte len, where 0 means empty - fromNum.setMaxLen(4); - fromNum.setCccdWriteCallback(onCccd); // Optionally capture CCCD updates - // We don't yet need to hook the fromNum auth callback - // fromNum.setReadAuthorizeCallback(fromNumAuthorizeCb); - fromNum.write32(0); // Provide default fromNum of 0 - fromNum.begin(); + bluetoothPhoneAPI = new BluetoothPhoneAPI(); + meshBleService.begin(); + // Note: You must call .begin() on the BLEService before calling .begin() on + // any characteristic(s) within that service definition.. Calling .begin() on + // a BLECharacteristic will cause it to be added to the last BLEService that + // was 'begin()'ed! + auto secMode = + config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN ? SECMODE_OPEN : SECMODE_ENC_NO_MITM; + fromNum.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ); + fromNum.setPermission(secMode, SECMODE_NO_ACCESS); // FIXME, secure this!!! + fromNum.setFixedLen( + 0); // Variable len (either 0 or 4) FIXME consider changing protocol so it is fixed 4 byte len, where 0 means empty + fromNum.setMaxLen(4); + fromNum.setCccdWriteCallback(onCccd); // Optionally capture CCCD updates + // We don't yet need to hook the fromNum auth callback + // fromNum.setReadAuthorizeCallback(fromNumAuthorizeCb); + fromNum.write32(0); // Provide default fromNum of 0 + fromNum.begin(); - fromRadio.setProperties(CHR_PROPS_READ); - fromRadio.setPermission(secMode, SECMODE_NO_ACCESS); - fromRadio.setMaxLen(sizeof(fromRadioBytes)); - fromRadio.setReadAuthorizeCallback( - onFromRadioAuthorize, - false); // We don't call this callback via the adafruit queue, because we can safely run in the BLE context - fromRadio.setBuffer(fromRadioBytes, sizeof(fromRadioBytes)); // we preallocate our fromradio buffer so we won't waste space - // for two copies - fromRadio.begin(); + fromRadio.setProperties(CHR_PROPS_READ); + fromRadio.setPermission(secMode, SECMODE_NO_ACCESS); + fromRadio.setMaxLen(sizeof(fromRadioBytes)); + fromRadio.setReadAuthorizeCallback( + onFromRadioAuthorize, + false); // We don't call this callback via the adafruit queue, because we can safely run in the BLE context + fromRadio.setBuffer(fromRadioBytes, sizeof(fromRadioBytes)); // we preallocate our fromradio buffer so we won't waste space + // for two copies + fromRadio.begin(); - toRadio.setProperties(CHR_PROPS_WRITE); - toRadio.setPermission(secMode, secMode); // FIXME secure this! - toRadio.setFixedLen(0); - toRadio.setMaxLen(512); - toRadio.setBuffer(toRadioBytes, sizeof(toRadioBytes)); - // We don't call this callback via the adafruit queue, because we can safely run in the BLE context - toRadio.setWriteCallback(onToRadioWrite, false); - toRadio.begin(); + toRadio.setProperties(CHR_PROPS_WRITE); + toRadio.setPermission(secMode, secMode); // FIXME secure this! + toRadio.setFixedLen(0); + toRadio.setMaxLen(512); + toRadio.setBuffer(toRadioBytes, sizeof(toRadioBytes)); + // We don't call this callback via the adafruit queue, because we can safely run in the BLE context + toRadio.setWriteCallback(onToRadioWrite, false); + toRadio.begin(); - logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | CHR_PROPS_READ); - logRadio.setPermission(secMode, SECMODE_NO_ACCESS); - logRadio.setMaxLen(512); - logRadio.setCccdWriteCallback(onCccd); - logRadio.write32(0); - logRadio.begin(); + logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | CHR_PROPS_READ); + logRadio.setPermission(secMode, SECMODE_NO_ACCESS); + logRadio.setMaxLen(512); + logRadio.setCccdWriteCallback(onCccd); + logRadio.write32(0); + logRadio.begin(); } static uint32_t configuredPasskey; void NRF52Bluetooth::shutdown() { - // Shutdown bluetooth for minimum power draw - LOG_INFO("Disable NRF52 bluetooth\n"); - uint8_t connection_num = Bluefruit.connected(); - if (connection_num) { - for (uint8_t i = 0; i < connection_num; i++) { - LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); - Bluefruit.disconnect(i); - } - delay(100); // wait for ondisconnect; - } - Bluefruit.Advertising.stop(); + // Shutdown bluetooth for minimum power draw + LOG_INFO("Disable NRF52 bluetooth\n"); + uint8_t connection_num = Bluefruit.connected(); + if (connection_num) + { + for (uint8_t i = 0; i < connection_num; i++) + { + LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); + Bluefruit.disconnect(i); + } + delay(100); // wait for ondisconnect; + } + Bluefruit.Advertising.stop(); } void NRF52Bluetooth::startDisabled() { - // Setup Bluetooth - nrf52Bluetooth->setup(); - // Shutdown bluetooth for minimum power draw - Bluefruit.Advertising.stop(); - Bluefruit.setTxPower(-40); // Minimum power - LOG_INFO("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); + // Setup Bluetooth + nrf52Bluetooth->setup(); + // Shutdown bluetooth for minimum power draw + Bluefruit.Advertising.stop(); + Bluefruit.setTxPower(-40); // Minimum power + LOG_INFO("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); } bool NRF52Bluetooth::isConnected() { - return Bluefruit.connected(connectionHandle); + return Bluefruit.connected(connectionHandle); } int NRF52Bluetooth::getRssi() { - return 0; // FIXME figure out where to source this + return 0; // FIXME figure out where to source this } void NRF52Bluetooth::setup() { - // Initialise the Bluefruit module - LOG_INFO("Initialize the Bluefruit nRF52 module\n"); - Bluefruit.autoConnLed(false); - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.begin(); - // Clear existing data. - Bluefruit.Advertising.stop(); - Bluefruit.Advertising.clearData(); - Bluefruit.ScanResponse.clearData(); - if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { - configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN - ? config.bluetooth.fixed_pin - : random(100000, 999999); - auto pinString = std::to_string(configuredPasskey); - LOG_INFO("Bluetooth pin set to '%i'\n", configuredPasskey); - Bluefruit.Security.setPIN(pinString.c_str()); - Bluefruit.Security.setIOCaps(true, false, false); - Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey); - Bluefruit.Security.setPairCompleteCallback(NRF52Bluetooth::onPairingCompleted); - Bluefruit.Security.setSecuredCallback(NRF52Bluetooth::onConnectionSecured); - meshBleService.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); - } else { - Bluefruit.Security.setIOCaps(false, false, false); - meshBleService.setPermission(SECMODE_OPEN, SECMODE_OPEN); - } - // Set the advertised device name (keep it short!) - Bluefruit.setName(getDeviceName()); - // Set the connect/disconnect callback handlers - Bluefruit.Periph.setConnectCallback(onConnect); - Bluefruit.Periph.setDisconnectCallback(onDisconnect); + // Initialise the Bluefruit module + LOG_INFO("Initialize the Bluefruit nRF52 module\n"); + Bluefruit.autoConnLed(false); + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.begin(); + // Clear existing data. + Bluefruit.Advertising.stop(); + Bluefruit.Advertising.clearData(); + Bluefruit.ScanResponse.clearData(); + if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) + { + configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN + ? config.bluetooth.fixed_pin + : random(100000, 999999); + auto pinString = std::to_string(configuredPasskey); + LOG_INFO("Bluetooth pin set to '%i'\n", configuredPasskey); + Bluefruit.Security.setPIN(pinString.c_str()); + Bluefruit.Security.setIOCaps(true, false, false); + Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey); + Bluefruit.Security.setPairCompleteCallback(NRF52Bluetooth::onPairingCompleted); + Bluefruit.Security.setSecuredCallback(NRF52Bluetooth::onConnectionSecured); + meshBleService.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + } + else + { + Bluefruit.Security.setIOCaps(false, false, false); + meshBleService.setPermission(SECMODE_OPEN, SECMODE_OPEN); + } + // Set the advertised device name (keep it short!) + Bluefruit.setName(getDeviceName()); + // Set the connect/disconnect callback handlers + Bluefruit.Periph.setConnectCallback(onConnect); + Bluefruit.Periph.setDisconnectCallback(onDisconnect); #ifndef BLE_DFU_SECURE - bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); - bledfu.begin(); // Install the DFU helper + bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + bledfu.begin(); // Install the DFU helper #else - bledfusecure.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // add by WayenWeng - bledfusecure.begin(); // Install the DFU helper + bledfusecure.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // add by WayenWeng + bledfusecure.begin(); // Install the DFU helper #endif - // Configure and Start the Device Information Service - LOG_INFO("Configuring the Device Information Service\n"); - bledis.setModel(optstr(HW_VERSION)); - bledis.setFirmwareRev(optstr(APP_VERSION)); - bledis.begin(); - // Start the BLE Battery Service and set it to 100% - LOG_INFO("Configuring the Battery Service\n"); - blebas.begin(); - blebas.write(0); // Unknown battery level for now - // Setup the Heart Rate Monitor service using - // BLEService and BLECharacteristic classes - LOG_INFO("Configuring the Mesh bluetooth service\n"); - setupMeshService(); - // Setup the advertising packet(s) - LOG_INFO("Setting up the advertising payload(s)\n"); - startAdv(); - LOG_INFO("Advertising\n"); + // Configure and Start the Device Information Service + LOG_INFO("Configuring the Device Information Service\n"); + bledis.setModel(optstr(HW_VERSION)); + bledis.setFirmwareRev(optstr(APP_VERSION)); + bledis.begin(); + // Start the BLE Battery Service and set it to 100% + LOG_INFO("Configuring the Battery Service\n"); + blebas.begin(); + blebas.write(0); // Unknown battery level for now + // Setup the Heart Rate Monitor service using + // BLEService and BLECharacteristic classes + LOG_INFO("Configuring the Mesh bluetooth service\n"); + setupMeshService(); + // Setup the advertising packet(s) + LOG_INFO("Setting up the advertising payload(s)\n"); + startAdv(); + LOG_INFO("Advertising\n"); } void NRF52Bluetooth::resumeAdvertising() { - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); } /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level) { - blebas.write(level); + blebas.write(level); } void NRF52Bluetooth::clearBonds() { - LOG_INFO("Clearing bluetooth bonds!\n"); - bond_print_list(BLE_GAP_ROLE_PERIPH); - bond_print_list(BLE_GAP_ROLE_CENTRAL); - Bluefruit.Periph.clearBonds(); - Bluefruit.Central.clearBonds(); + LOG_INFO("Clearing bluetooth bonds!\n"); + bond_print_list(BLE_GAP_ROLE_PERIPH); + bond_print_list(BLE_GAP_ROLE_CENTRAL); + Bluefruit.Periph.clearBonds(); + Bluefruit.Central.clearBonds(); } void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle) { - LOG_INFO("BLE connection secured\n"); + LOG_INFO("BLE connection secured\n"); } bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { - LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); - powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); #if !defined(MESHTASTIC_EXCLUDE_SCREEN) screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { @@ -337,31 +349,33 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; display->drawString(x_offset + x, y_offset + y, deviceName); }); #endif - if (match_request) { - uint32_t start_time = millis(); - while (millis() < start_time + 30000) { - if (!Bluefruit.connected(conn_handle)) - break; - } - } - LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); - return true; + if (match_request) + { + uint32_t start_time = millis(); + while (millis() < start_time + 30000) + { + if (!Bluefruit.connected(conn_handle)) + break; + } + } + LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); + return true; } void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) { - if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) - LOG_INFO("BLE pairing success\n"); - else - LOG_INFO("BLE pairing failed\n"); - screen->endAlert(); + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + LOG_INFO("BLE pairing success\n"); + else + LOG_INFO("BLE pairing failed\n"); + screen->endAlert(); } void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length) { - if (!isConnected() || length > 512) - return; - if (logRadio.indicateEnabled()) - logRadio.indicate(logMessage, (uint16_t)length); - else - logRadio.notify(logMessage, (uint16_t)length); + if (!isConnected() || length > 512) + return; + if (logRadio.indicateEnabled()) + logRadio.indicate(logMessage, (uint16_t)length); + else + logRadio.notify(logMessage, (uint16_t)length); } \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 777c10ada..8c58fba27 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -16,296 +16,360 @@ StaticJsonDocument<1024> arrayObj; std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) { - // the created jsonObj is immutable after creation, so - // we need to do the heavy lifting before assembling it. - std::string msgType; + // the created jsonObj is immutable after creation, so + // we need to do the heavy lifting before assembling it. + std::string msgType; jsonObj.clear(); arrayObj.clear(); - if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - switch (mp->decoded.portnum) { - case meshtastic_PortNum_TEXT_MESSAGE_APP: { - msgType = "text"; - // convert bytes to string - if (shouldLog) - LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) + { + switch (mp->decoded.portnum) + { + case meshtastic_PortNum_TEXT_MESSAGE_APP: + { + msgType = "text"; + // convert bytes to string + if (shouldLog) + LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - // check if this is a JSON payload + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + // check if this is a JSON payload StaticJsonDocument<512> text_doc; DeserializationError error = deserializeJson(text_doc, payloadStr); - if (error) { + if (error) + { // if it isn't, then we need to create a json object - // with the string as the value - if (shouldLog) - LOG_INFO("text message payload is of type plaintext\n"); + // with the string as the value + if (shouldLog) + LOG_INFO("text message payload is of type plaintext\n"); jsonObj["payload"]["text"] = payloadStr; - } else { + } + else + { // if it is, then we can just use the json object - if (shouldLog) - LOG_INFO("text message payload is of type json\n"); + if (shouldLog) + LOG_INFO("text message payload is of type json\n"); jsonObj["payload"] = text_doc; - } - break; - } - case meshtastic_PortNum_TELEMETRY_APP: { - msgType = "telemetry"; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { - decoded = &scratch; - if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { - jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; - jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; - jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; - jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; - jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; - } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { - jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; - jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; - jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; - jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; - jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; - jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; - jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; - jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; - jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; - jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; - jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; - jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; - jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; - } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { - jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; - jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; - jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; - jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; - jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; - jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; - } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { - jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; - jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; - jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; - jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; - jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; - jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; - } - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for telemetry message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NODEINFO_APP: { - msgType = "nodeinfo"; - meshtastic_User scratch; - meshtastic_User *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { - decoded = &scratch; - jsonObj["payload"]["id"] = decoded->id; - jsonObj["payload"]["longname"] = decoded->long_name; - jsonObj["payload"]["shortname"] = decoded->short_name; - jsonObj["payload"]["hardware"] = decoded->hw_model; - jsonObj["payload"]["role"] = (int)decoded->role; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_POSITION_APP: { - msgType = "position"; - meshtastic_Position scratch; - meshtastic_Position *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { - decoded = &scratch; - if ((int)decoded->time) { - jsonObj["payload"]["time"] = (unsigned int)decoded->time; - } - if ((int)decoded->timestamp) { - jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; - } - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - if ((int)decoded->altitude) { - jsonObj["payload"]["altitude"] = (int)decoded->altitude; - } - if ((int)decoded->ground_speed) { - jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; - } - if (int(decoded->ground_track)) { - jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; - } - if (int(decoded->sats_in_view)) { - jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; - } - if ((int)decoded->PDOP) { - jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; - } - if ((int)decoded->HDOP) { - jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; - } - if ((int)decoded->VDOP) { - jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; - } - if ((int)decoded->precision_bits) { - jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; - } - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_WAYPOINT_APP: { - msgType = "position"; - meshtastic_Waypoint scratch; - meshtastic_Waypoint *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { - decoded = &scratch; - jsonObj["payload"]["id"] = (unsigned int)decoded->id; - jsonObj["payload"]["name"] = decoded->name; - jsonObj["payload"]["description"] = decoded->description; - jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; - jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NEIGHBORINFO_APP: { - msgType = "neighborinfo"; - meshtastic_NeighborInfo scratch; - meshtastic_NeighborInfo *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, - &scratch)) { - decoded = &scratch; - jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; - jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; - jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; - jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; - + } + break; + } + case meshtastic_PortNum_TELEMETRY_APP: + { + msgType = "telemetry"; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) + { + decoded = &scratch; + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) + { + jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; + jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; + jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; + jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; + jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; + } + else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) + { + jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; + jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; + jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; + jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; + jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; + jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; + jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; + jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; + jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; + jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; + jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; + jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; + jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; + } + else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) + { + jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; + jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; + jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; + jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; + jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; + jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; + } + else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) + { + jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; + jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; + jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; + jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; + jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; + jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; + } + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NODEINFO_APP: + { + msgType = "nodeinfo"; + meshtastic_User scratch; + meshtastic_User *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) + { + decoded = &scratch; + jsonObj["payload"]["id"] = decoded->id; + jsonObj["payload"]["longname"] = decoded->long_name; + jsonObj["payload"]["shortname"] = decoded->short_name; + jsonObj["payload"]["hardware"] = decoded->hw_model; + jsonObj["payload"]["role"] = (int)decoded->role; + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_POSITION_APP: + { + msgType = "position"; + meshtastic_Position scratch; + meshtastic_Position *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) + { + decoded = &scratch; + if ((int)decoded->time) + { + jsonObj["payload"]["time"] = (unsigned int)decoded->time; + } + if ((int)decoded->timestamp) + { + jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; + } + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + if ((int)decoded->altitude) + { + jsonObj["payload"]["altitude"] = (int)decoded->altitude; + } + if ((int)decoded->ground_speed) + { + jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; + } + if (int(decoded->ground_track)) + { + jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; + } + if (int(decoded->sats_in_view)) + { + jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; + } + if ((int)decoded->PDOP) + { + jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; + } + if ((int)decoded->HDOP) + { + jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; + } + if ((int)decoded->VDOP) + { + jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; + } + if ((int)decoded->precision_bits) + { + jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; + } + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_WAYPOINT_APP: + { + msgType = "position"; + meshtastic_Waypoint scratch; + meshtastic_Waypoint *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) + { + decoded = &scratch; + jsonObj["payload"]["id"] = (unsigned int)decoded->id; + jsonObj["payload"]["name"] = decoded->name; + jsonObj["payload"]["description"] = decoded->description; + jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; + jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NEIGHBORINFO_APP: + { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) + { + decoded = &scratch; + jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; + jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; + jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; + jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; + JsonObject neighbors_obj = arrayObj.to(); JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); JsonObject neighbors_0 = neighbors.createNestedObject(); - for (uint8_t i = 0; i < decoded->neighbors_count; i++) { - neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; - neighbors_0["snr"] = (int)decoded->neighbors[i].snr; - neighbors[i+1] = neighbors_0; - neighbors_0.clear(); - } + for (uint8_t i = 0; i < decoded->neighbors_count; i++) + { + neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; + neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors[i + 1] = neighbors_0; + neighbors_0.clear(); + } neighbors.remove(0); jsonObj["payload"]["neighbors"] = neighbors; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_TRACEROUTE_APP: { - if (mp->decoded.request_id) { // Only report the traceroute response - msgType = "traceroute"; - meshtastic_RouteDiscovery scratch; - meshtastic_RouteDiscovery *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, - &scratch)) { - decoded = &scratch; - JsonArray route = arrayObj.createNestedArray("route"); - - auto addToRoute = [](JsonArray *route, NodeNum num) { - char long_name[40] = "Unknown"; - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); - bool name_known = node ? node->has_user : false; - if (name_known) - memcpy(long_name, node->user.long_name, sizeof(long_name)); - route->add(long_name); - }; - - addToRoute(&route,mp->to); //route.add(mp->to); - for (uint8_t i = 0; i < decoded->route_count; i++) { - addToRoute(&route,decoded->route[i]); //route.add(decoded->route[i]); - } - addToRoute(&route,mp->from); //route.add(mp->from); // Ended at the original destination (source of response) - - jsonObj["payload"]["route"] = route; - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for traceroute message!\n"); - return ""; - } - } else { - LOG_WARN("Traceroute response not reported"); + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); return ""; } - break; - } - case meshtastic_PortNum_DETECTION_SENSOR_APP: { - msgType = "detection"; - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - jsonObj["payload"]["text"] = payloadStr; - break; - } - case meshtastic_PortNum_REMOTE_HARDWARE_APP: { - meshtastic_HardwareMessage scratch; - meshtastic_HardwareMessage *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, - &scratch)) { - decoded = &scratch; - if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { - msgType = "gpios_changed"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { - msgType = "gpios_read_reply"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; - } - } else if (shouldLog) { - LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); - return ""; - } - break; - } - // add more packet types here if needed - default: - LOG_WARN("Unsupported packet type %d\n",mp->decoded.portnum); + break; + } + case meshtastic_PortNum_TRACEROUTE_APP: + { + if (mp->decoded.request_id) + { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) + { + decoded = &scratch; + JsonArray route = arrayObj.createNestedArray("route"); + + auto addToRoute = [](JsonArray *route, NodeNum num) + { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->add(long_name); + }; + + addToRoute(&route, mp->to); // route.add(mp->to); + for (uint8_t i = 0; i < decoded->route_count; i++) + { + addToRoute(&route, decoded->route[i]); // route.add(decoded->route[i]); + } + addToRoute(&route, mp->from); // route.add(mp->from); // Ended at the original destination (source of response) + + jsonObj["payload"]["route"] = route; + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + return ""; + } + } + else + { + LOG_WARN("Traceroute response not reported"); + return ""; + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: + { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + jsonObj["payload"]["text"] = payloadStr; + break; + } + case meshtastic_PortNum_REMOTE_HARDWARE_APP: + { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) + { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) + { + msgType = "gpios_changed"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + } + else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) + { + msgType = "gpios_read_reply"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; + } + } + else if (shouldLog) + { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + return ""; + } + break; + } + // add more packet types here if needed + default: + LOG_WARN("Unsupported packet type %d\n", mp->decoded.portnum); return ""; - break; - } - } else if (shouldLog) { - LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + break; + } + } + else if (shouldLog) + { + LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); return ""; - } + } - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["type"] = msgType.c_str(); - jsonObj["sender"] = owner.id; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["type"] = msgType.c_str(); + jsonObj["sender"] = owner.id; + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) + { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } - // serialize and write it to the stream + // serialize and write it to the stream // Serial.printf("serialized json message: \r\n"); // serializeJson(jsonObj, Serial); @@ -314,39 +378,40 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, std::string jsonStr = ""; serializeJson(jsonObj, jsonStr); - if (shouldLog) - LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + if (shouldLog) + LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); - return jsonStr; + return jsonStr; } std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) { jsonObj.clear(); - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["time_ms"] = (double)millis(); - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["want_ack"] = mp->want_ack; + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["time_ms"] = (double)millis(); + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["want_ack"] = mp->want_ack; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } - jsonObj["size"] = (unsigned int)mp->encrypted.size; - auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); - jsonObj["bytes"] = encryptedStr.c_str(); + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) + { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + jsonObj["size"] = (unsigned int)mp->encrypted.size; + auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); + jsonObj["bytes"] = encryptedStr.c_str(); - // serialize and write it to the stream + // serialize and write it to the stream std::string jsonStr = ""; serializeJson(jsonObj, jsonStr); - return jsonStr; + return jsonStr; } #endif \ No newline at end of file From c4c85777d035402140d96270e280051185e0a218 Mon Sep 17 00:00:00 2001 From: beegee-tokyo Date: Thu, 12 Sep 2024 13:20:09 +0800 Subject: [PATCH 09/11] Another try to get the code format correct. --- src/mqtt/MQTT.cpp | 1097 +++++++++++-------------- src/platform/nrf52/NRF52Bluetooth.cpp | 456 +++++----- 2 files changed, 719 insertions(+), 834 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 3e0e692b4..a4921e684 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -33,194 +33,161 @@ Allocator &mqttPool = staticMqttPool; void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length) { - mqtt->onReceive(topic, payload, length); + mqtt->onReceive(topic, payload, length); } void MQTT::onClientProxyReceive(meshtastic_MqttClientProxyMessage msg) { - onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); + onReceive(msg.topic, msg.payload_variant.data.bytes, msg.payload_variant.data.size); } void MQTT::onReceive(char *topic, byte *payload, size_t length) { - meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; + meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default; - 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 - char payloadStr[length + 1]; - memcpy(payloadStr, payload, length); - payloadStr[length] = 0; // null terminated string - JSONValue *json_value = JSON::Parse(payloadStr); - if (json_value != NULL) - { - // check if it is a valid envelope - JSONObject json; - json = json_value->AsObject(); + 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 + char payloadStr[length + 1]; + memcpy(payloadStr, payload, length); + payloadStr[length] = 0; // null terminated string + JSONValue *json_value = JSON::Parse(payloadStr); + if (json_value != NULL) { + // check if it is a valid envelope + JSONObject json; + json = json_value->AsObject(); - // parse the channel name from the topic string - // the topic has been checked above for having jsonTopic prefix, so just move past it - char *ptr = topic + jsonTopic.length(); - ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character - meshtastic_Channel sendChannel = channels.getByName(ptr); - // We allow downlink JSON packets only on a channel named "mqtt" - if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && - sendChannel.settings.downlink_enabled) - { - if (isValidJsonEnvelope(json)) - { - // this is a valid envelope - if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) - { - std::string jsonPayloadStr = json["payload"]->AsString(); - LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); + // parse the channel name from the topic string + // the topic has been checked above for having jsonTopic prefix, so just move past it + char *ptr = topic + jsonTopic.length(); + ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character + meshtastic_Channel sendChannel = channels.getByName(ptr); + // We allow downlink JSON packets only on a channel named "mqtt" + if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && + sendChannel.settings.downlink_enabled) { + if (isValidJsonEnvelope(json)) { + // this is a valid envelope + if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) { + std::string jsonPayloadStr = json["payload"]->AsString(); + LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); - // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) - { - memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); - p->decoded.payload.size = jsonPayloadStr.length(); - service->sendToMesh(p, RX_SRC_LOCAL); - } - else - { - LOG_WARN("Received MQTT json payload too long, dropping\n"); - } - } - else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) - { - // invent the "sendposition" type for a valid envelope - JSONObject posit; - posit = json["payload"]->AsObject(); // get nested JSON Position - meshtastic_Position pos = meshtastic_Position_init_default; - if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) - pos.latitude_i = posit["latitude_i"]->AsNumber(); - if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) - pos.longitude_i = posit["longitude_i"]->AsNumber(); - if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) - pos.altitude = posit["altitude"]->AsNumber(); - if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) - pos.time = posit["time"]->AsNumber(); + // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { + memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); + p->decoded.payload.size = jsonPayloadStr.length(); + service->sendToMesh(p, RX_SRC_LOCAL); + } else { + LOG_WARN("Received MQTT json payload too long, dropping\n"); + } + } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) { + // invent the "sendposition" type for a valid envelope + JSONObject posit; + posit = json["payload"]->AsObject(); // get nested JSON Position + meshtastic_Position pos = meshtastic_Position_init_default; + if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) + pos.latitude_i = posit["latitude_i"]->AsNumber(); + if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) + pos.longitude_i = posit["longitude_i"]->AsNumber(); + if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) + pos.altitude = posit["altitude"]->AsNumber(); + if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) + pos.time = posit["time"]->AsNumber(); - // construct protobuf data packet using POSITION, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_POSITION_APP; - if (json.find("channel") != json.end() && json["channel"]->IsNumber() && - (json["channel"]->AsNumber() < channels.getNumChannels())) - p->channel = json["channel"]->AsNumber(); - if (json.find("to") != json.end() && json["to"]->IsNumber()) - p->to = json["to"]->AsNumber(); - if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) - p->hop_limit = json["hopLimit"]->AsNumber(); - p->decoded.payload.size = - pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), - &meshtastic_Position_msg, &pos); // make the Data protobuf from position - service->sendToMesh(p, RX_SRC_LOCAL); - } - else - { - LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); - } - } - else - { - LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); - } - } - else - { - LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); - } - } - else - { - // no json, this is an invalid payload - LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); - } - delete json_value; - } - else - { - if (length == 0) - { - LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); - return; - } - else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) - { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } - else - { - if (e.channel_id == NULL || e.gateway_id == NULL) - { - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); - return; - } - meshtastic_Channel ch = channels.getByName(e.channel_id); - if (strcmp(e.gateway_id, owner.id) == 0) - { - // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. - // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node - // receives it when we get our own packet back. Then we'll stop our retransmissions. - if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) - routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); - else - LOG_INFO("Ignoring downlink message we originally sent.\n"); - } - else - { - // Find channel by channel_id and check downlink_enabled - if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || - (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) - { - LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); - meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); - p->via_mqtt = true; // Mark that the packet was received via MQTT + // construct protobuf data packet using POSITION, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_POSITION_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); + if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber()) + p->hop_limit = json["hopLimit"]->AsNumber(); + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + &meshtastic_Position_msg, &pos); // make the Data protobuf from position + service->sendToMesh(p, RX_SRC_LOCAL); + } else { + LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); + } + } else { + LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); + } + } else { + LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); + } + } else { + // no json, this is an invalid payload + LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); + } + delete json_value; + } else { + if (length == 0) { + LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); + return; + } else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } else { + if (e.channel_id == NULL || e.gateway_id == NULL) { + LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + return; + } + meshtastic_Channel ch = channels.getByName(e.channel_id); + if (strcmp(e.gateway_id, owner.id) == 0) { + // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. + // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node + // receives it when we get our own packet back. Then we'll stop our retransmissions. + if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) + routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); + else + LOG_INFO("Ignoring downlink message we originally sent.\n"); + } else { + // Find channel by channel_id and check downlink_enabled + if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) || + (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) { + LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); + meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); + p->via_mqtt = true; // Mark that the packet was received via MQTT - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) - { - p->channel = ch.index; - } + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + p->channel = ch.index; + } - // PKI messages get accepted even if we can't decrypt - if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && - strcmp(e.channel_id, "PKI") == 0) - { - meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); - meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); - // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's - // likely they discovered each other via a channel we have downlink enabled for - if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) - router->enqueueReceivedMessage(p); - } - else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key - router->enqueueReceivedMessage(p); - else - packetPool.release(p); - } - } - } - // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) - free(e.channel_id); - free(e.gateway_id); - free(e.packet); - } + // PKI messages get accepted even if we can't decrypt + if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && + strcmp(e.channel_id, "PKI") == 0) { + meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); + meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); + // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's + // likely they discovered each other via a channel we have downlink enabled for + if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user)) + router->enqueueReceivedMessage(p); + } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key + router->enqueueReceivedMessage(p); + else + packetPool.release(p); + } + } + } + // make sure to free both strings and the MeshPacket (passing in NULL is acceptable) + free(e.channel_id); + free(e.gateway_id); + free(e.packet); + } } void mqttInit() { - new MQTT(); + new MQTT(); } #if HAS_NETWORKING @@ -229,551 +196,483 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) #endif { - if (moduleConfig.mqtt.enabled) - { - LOG_DEBUG("Initializing MQTT\n"); + if (moduleConfig.mqtt.enabled) { + LOG_DEBUG("Initializing MQTT\n"); - assert(!mqtt); - mqtt = this; + assert(!mqtt); + mqtt = this; - if (*moduleConfig.mqtt.root) - { - cryptTopic = moduleConfig.mqtt.root + cryptTopic; - jsonTopic = moduleConfig.mqtt.root + jsonTopic; - mapTopic = moduleConfig.mqtt.root + mapTopic; - } - else - { - cryptTopic = "msh" + cryptTopic; - jsonTopic = "msh" + jsonTopic; - mapTopic = "msh" + mapTopic; - } + if (*moduleConfig.mqtt.root) { + cryptTopic = moduleConfig.mqtt.root + cryptTopic; + jsonTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + mapTopic; + } else { + cryptTopic = "msh" + cryptTopic; + jsonTopic = "msh" + jsonTopic; + mapTopic = "msh" + mapTopic; + } - if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) - { - map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, - default_map_position_precision); - map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( - moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); - } + if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { + map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, + default_map_position_precision); + map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( + moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); + } #if HAS_NETWORKING - if (!moduleConfig.mqtt.proxy_to_client_enabled) - pubSub.setCallback(mqttCallback); + if (!moduleConfig.mqtt.proxy_to_client_enabled) + pubSub.setCallback(mqttCallback); #endif - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - LOG_INFO("MQTT configured to use client proxy...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; - publishNodeInfo(); - } - // preflightSleepObserver.observe(&preflightSleep); - } - else - { - disable(); - } + if (moduleConfig.mqtt.proxy_to_client_enabled) { + LOG_INFO("MQTT configured to use client proxy...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; + publishNodeInfo(); + } + // preflightSleepObserver.observe(&preflightSleep); + } else { + disable(); + } } bool MQTT::isConnectedDirectly() { #if HAS_NETWORKING - return pubSub.connected(); + return pubSub.connected(); #else - return false; + return false; #endif } bool MQTT::publish(const char *topic, const char *payload, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; - strcpy(msg->topic, topic); - strcpy(msg->payload_variant.text, payload); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; + strcpy(msg->topic, topic); + strcpy(msg->payload_variant.text, payload); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) - { - return pubSub.publish(topic, payload, retained); - } + else if (isConnectedDirectly()) { + return pubSub.publish(topic, payload, retained); + } #endif - return false; + return false; } bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, bool retained) { - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; - strcpy(msg->topic, topic); - msg->payload_variant.data.size = length; - memcpy(msg->payload_variant.data.bytes, payload, length); - msg->retained = retained; - service->sendMqttMessageToClientProxy(msg); - return true; - } + if (moduleConfig.mqtt.proxy_to_client_enabled) { + meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; + strcpy(msg->topic, topic); + msg->payload_variant.data.size = length; + memcpy(msg->payload_variant.data.bytes, payload, length); + msg->retained = retained; + service->sendMqttMessageToClientProxy(msg); + return true; + } #if HAS_NETWORKING - else if (isConnectedDirectly()) - { - return pubSub.publish(topic, payload, length, retained); - } + else if (isConnectedDirectly()) { + return pubSub.publish(topic, payload, length, retained); + } #endif - return false; + return false; } void MQTT::reconnect() { - if (wantsLink()) - { - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - LOG_INFO("MQTT connecting via client proxy instead...\n"); - enabled = true; - runASAP = true; - reconnectCount = 0; + if (wantsLink()) { + if (moduleConfig.mqtt.proxy_to_client_enabled) { + LOG_INFO("MQTT connecting via client proxy instead...\n"); + enabled = true; + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - return; // Don't try to connect directly to the server - } + publishNodeInfo(); + return; // Don't try to connect directly to the server + } #if HAS_NETWORKING - // Defaults - int serverPort = 1883; - const char *serverAddr = default_mqtt_address; - const char *mqttUsername = default_mqtt_username; - const char *mqttPassword = default_mqtt_password; + // Defaults + int serverPort = 1883; + const char *serverAddr = default_mqtt_address; + const char *mqttUsername = default_mqtt_username; + const char *mqttPassword = default_mqtt_password; - if (*moduleConfig.mqtt.address) - { - serverAddr = moduleConfig.mqtt.address; - mqttUsername = moduleConfig.mqtt.username; - mqttPassword = moduleConfig.mqtt.password; - } + if (*moduleConfig.mqtt.address) { + serverAddr = moduleConfig.mqtt.address; + mqttUsername = moduleConfig.mqtt.username; + mqttPassword = moduleConfig.mqtt.password; + } #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (moduleConfig.mqtt.tls_enabled) - { - // change default for encrypted to 8883 - try - { - serverPort = 8883; - wifiSecureClient.setInsecure(); + if (moduleConfig.mqtt.tls_enabled) { + // change default for encrypted to 8883 + try { + serverPort = 8883; + wifiSecureClient.setInsecure(); - pubSub.setClient(wifiSecureClient); - LOG_INFO("Using TLS-encrypted session\n"); - } - catch (const std::exception &e) - { - LOG_ERROR("MQTT ERROR: %s\n", e.what()); - } - } - else - { - LOG_INFO("Using non-TLS-encrypted session\n"); - pubSub.setClient(mqttClient); - } + pubSub.setClient(wifiSecureClient); + LOG_INFO("Using TLS-encrypted session\n"); + } catch (const std::exception &e) { + LOG_ERROR("MQTT ERROR: %s\n", e.what()); + } + } else { + LOG_INFO("Using non-TLS-encrypted session\n"); + pubSub.setClient(mqttClient); + } #elif HAS_NETWORKING - pubSub.setClient(mqttClient); + pubSub.setClient(mqttClient); #endif - String server = String(serverAddr); - int delimIndex = server.indexOf(':'); - if (delimIndex > 0) - { - String port = server.substring(delimIndex + 1, server.length()); - server[delimIndex] = 0; - serverPort = port.toInt(); - serverAddr = server.c_str(); - } - pubSub.setServer(serverAddr, serverPort); - pubSub.setBufferSize(512); + String server = String(serverAddr); + int delimIndex = server.indexOf(':'); + if (delimIndex > 0) { + String port = server.substring(delimIndex + 1, server.length()); + server[delimIndex] = 0; + serverPort = port.toInt(); + serverAddr = server.c_str(); + } + pubSub.setServer(serverAddr, serverPort); + pubSub.setBufferSize(512); - LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, - serverPort, mqttUsername, mqttPassword); + LOG_INFO("Attempting to connect directly to MQTT server %s, port: %d, username: %s, password: %s\n", serverAddr, + serverPort, mqttUsername, mqttPassword); - bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); - if (connected) - { - LOG_INFO("MQTT connected\n"); - enabled = true; // Start running background process again - runASAP = true; - reconnectCount = 0; + bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword); + if (connected) { + LOG_INFO("MQTT connected\n"); + enabled = true; // Start running background process again + runASAP = true; + reconnectCount = 0; - publishNodeInfo(); - sendSubscriptions(); - } - else - { + publishNodeInfo(); + sendSubscriptions(); + } else { #if HAS_WIFI && !defined(ARCH_PORTDUINO) - reconnectCount++; - LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); - if (reconnectCount >= reconnectMax) - { - needReconnect = true; - wifiReconnect->setIntervalFromNow(0); - reconnectCount = 0; - } + reconnectCount++; + LOG_ERROR("Failed to contact MQTT server directly (%d/%d)...\n", reconnectCount, reconnectMax); + if (reconnectCount >= reconnectMax) { + needReconnect = true; + wifiReconnect->setIntervalFromNow(0); + reconnectCount = 0; + } #endif - } + } #endif - } + } } void MQTT::sendSubscriptions() { #if HAS_NETWORKING - bool hasDownlink = false; - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) - { - const auto &ch = channels.getByIndex(i); - if (ch.settings.downlink_enabled) - { - hasDownlink = true; - std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? + bool hasDownlink = false; + size_t numChan = channels.getNumChannels(); + for (size_t i = 0; i < numChan; i++) { + const auto &ch = channels.getByIndex(i); + if (ch.settings.downlink_enabled) { + hasDownlink = true; + std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? #if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### - if (moduleConfig.mqtt.json_enabled == true) - { - std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; - LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); - pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? - } + if (moduleConfig.mqtt.json_enabled == true) { + std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; + LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); + pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? + } #endif // ARCH_NRF52 NRF52_USE_JSON - } - } + } + } #if !MESHTASTIC_EXCLUDE_PKI - if (hasDownlink) - { - std::string topic = cryptTopic + "PKI/+"; - LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); - } + if (hasDownlink) { + std::string topic = cryptTopic + "PKI/+"; + LOG_INFO("Subscribing to %s\n", topic.c_str()); + pubSub.subscribe(topic.c_str(), 1); + } #endif #endif } bool MQTT::wantsLink() const { - bool hasChannelorMapReport = - moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); + bool hasChannelorMapReport = + moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); - if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) - return true; + if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) + return true; #if HAS_WIFI - return hasChannelorMapReport && WiFi.isConnected(); + return hasChannelorMapReport && WiFi.isConnected(); #endif #if HAS_ETHERNET - return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; + return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; #endif - return false; + return false; } int32_t MQTT::runOnce() { #if HAS_NETWORKING - if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) - return disable(); + if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) + return disable(); - bool wantConnection = wantsLink(); + bool wantConnection = wantsLink(); - perhapsReportToMap(); + perhapsReportToMap(); - // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server - if (moduleConfig.mqtt.proxy_to_client_enabled) - { - publishQueuedMessages(); - return 200; - } - else if (!pubSub.loop()) - { - if (!wantConnection) - return 5000; // If we don't want connection now, check again in 5 secs - else - { - reconnect(); - // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP - // connections are EXPENSIVE so try rarely) - if (isConnectedDirectly()) - { - publishQueuedMessages(); - return 200; - } - else - return 30000; - } - } - else - { - // we are connected to server, check often for new requests on the TCP port - if (!wantConnection) - { - LOG_INFO("MQTT link not needed, dropping\n"); - pubSub.disconnect(); - } + // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server + if (moduleConfig.mqtt.proxy_to_client_enabled) { + publishQueuedMessages(); + return 200; + } - powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) - return 20; - } + else if (!pubSub.loop()) { + if (!wantConnection) + return 5000; // If we don't want connection now, check again in 5 secs + else { + reconnect(); + // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP + // connections are EXPENSIVE so try rarely) + if (isConnectedDirectly()) { + publishQueuedMessages(); + return 200; + } else + return 30000; + } + } else { + // we are connected to server, check often for new requests on the TCP port + if (!wantConnection) { + LOG_INFO("MQTT link not needed, dropping\n"); + pubSub.disconnect(); + } + + powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) + return 20; + } #endif - return 30000; + return 30000; } void MQTT::publishNodeInfo() { - // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) + // TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA) } void MQTT::publishQueuedMessages() { - if (!mqttQueue.isEmpty()) - { - LOG_DEBUG("Publishing enqueued MQTT message\n"); - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic; - if (env->packet->pki_encrypted) - { - topic = cryptTopic + "PKI/" + owner.id; - } - else - { - topic = cryptTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); + if (!mqttQueue.isEmpty()) { + LOG_DEBUG("Publishing enqueued MQTT message\n"); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0); + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic; + if (env->packet->pki_encrypted) { + topic = cryptTopic + "PKI/" + owner.id; + } else { + topic = cryptTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); #if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) - { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); - if (jsonString.length() != 0) - { - std::string topicJson; - if (env->packet->pki_encrypted) - { - topicJson = jsonTopic + "PKI/" + owner.id; - } - else - { - topicJson = jsonTopic + env->channel_id + "/" + owner.id; - } - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } + if (moduleConfig.mqtt.json_enabled) { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); + if (jsonString.length() != 0) { + std::string topicJson; + if (env->packet->pki_encrypted) { + topicJson = jsonTopic + "PKI/" + owner.id; + } else { + topicJson = jsonTopic + env->channel_id + "/" + owner.id; + } + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - mqttPool.release(env); - } + mqttPool.release(env); + } } void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex) { - if (mp.via_mqtt) - return; // Don't send messages that came from MQTT back into MQTT - bool uplinkEnabled = false; - for (int i = 0; i <= 7; i++) - { - if (channels.getByIndex(i).settings.uplink_enabled) - uplinkEnabled = true; - } - if (!uplinkEnabled) - return; // no channels have an uplink enabled - auto &ch = channels.getByIndex(chIndex); + if (mp.via_mqtt) + return; // Don't send messages that came from MQTT back into MQTT + bool uplinkEnabled = false; + for (int i = 0; i <= 7; i++) { + if (channels.getByIndex(i).settings.uplink_enabled) + uplinkEnabled = true; + } + if (!uplinkEnabled) + return; // no channels have an uplink enabled + auto &ch = channels.getByIndex(chIndex); - if (!mp.pki_encrypted) - { - if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) - { - LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); - return; - } + if (!mp.pki_encrypted) { + if (mp_decoded.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + LOG_CRIT("MQTT::onSend(): mp_decoded isn't actually decoded\n"); + return; + } - // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. - if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && - !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && - (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || - (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) - { - LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); - return; - } + // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. + if (mp_decoded.from != nodeDB->getNodeNum() && mp_decoded.decoded.has_bitfield && + !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK) && + (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || + (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { + LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag\n"); + return; + } - if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && - (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || - mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) - { - LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); - return; - } - } - if (mp.pki_encrypted || ch.settings.uplink_enabled) - { - const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); + if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { + LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); + return; + } + } + if (mp.pki_encrypted || ch.settings.uplink_enabled) { + const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex); - meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); - env->channel_id = (char *)channelId; - env->gateway_id = owner.id; + meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); + env->channel_id = (char *)channelId; + env->gateway_id = owner.id; - LOG_DEBUG("MQTT onSend - Publishing "); - if (moduleConfig.mqtt.encryption_enabled) - { - env->packet = (meshtastic_MeshPacket *)∓ - LOG_DEBUG("encrypted message\n"); - } - else if (mp_decoded.which_payload_variant == - meshtastic_MeshPacket_decoded_tag) - { // Don't upload a still-encrypted PKI packet - env->packet = (meshtastic_MeshPacket *)&mp_decoded; - LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); - } + LOG_DEBUG("MQTT onSend - Publishing "); + if (moduleConfig.mqtt.encryption_enabled) { + env->packet = (meshtastic_MeshPacket *)∓ + LOG_DEBUG("encrypted message\n"); + } else if (mp_decoded.which_payload_variant == + meshtastic_MeshPacket_decoded_tag) { // Don't upload a still-encrypted PKI packet + env->packet = (meshtastic_MeshPacket *)&mp_decoded; + LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); + } - if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) - { - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); - std::string topic = cryptTopic + channelId + "/" + owner.id; - LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); + if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); + std::string topic = cryptTopic + channelId + "/" + owner.id; + LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); - publish(topic.c_str(), bytes, numBytes, false); + publish(topic.c_str(), bytes, numBytes, false); #if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### - if (moduleConfig.mqtt.json_enabled) - { - // handle json topic - auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); - if (jsonString.length() != 0) - { - std::string topicJson = jsonTopic + channelId + "/" + owner.id; - LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), - jsonString.c_str()); - publish(topicJson.c_str(), jsonString.c_str(), false); - } - } + if (moduleConfig.mqtt.json_enabled) { + // handle json topic + auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); + if (jsonString.length() != 0) { + std::string topicJson = jsonTopic + channelId + "/" + owner.id; + LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), + jsonString.c_str()); + publish(topicJson.c_str(), jsonString.c_str(), false); + } + } #endif // ARCH_NRF52 NRF52_USE_JSON - } - else - { - LOG_INFO("MQTT not connected, queueing packet\n"); - if (mqttQueue.numFree() == 0) - { - LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); - meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); - if (d) - mqttPool.release(d); - } - // make a copy of serviceEnvelope and queue it - meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); - assert(mqttQueue.enqueue(copied, 0)); - } - mqttPool.release(env); - } + } else { + LOG_INFO("MQTT not connected, queueing packet\n"); + if (mqttQueue.numFree() == 0) { + LOG_WARN("NOTE: MQTT queue is full, discarding oldest\n"); + meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0); + if (d) + mqttPool.release(d); + } + // make a copy of serviceEnvelope and queue it + meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env); + assert(mqttQueue.enqueue(copied, 0)); + } + mqttPool.release(env); + } } void MQTT::perhapsReportToMap() { - if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) - return; + if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) + return; - if (millis() - last_report_to_map < map_publish_interval_msecs) - { - return; - } - else - { - if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) - { - last_report_to_map = millis(); - if (map_position_precision == 0) - LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); - if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) - LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); - return; - } + if (millis() - last_report_to_map < map_publish_interval_msecs) { + return; + } else { + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { + last_report_to_map = millis(); + if (map_position_precision == 0) + LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n"); + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) + LOG_WARN("MQTT Map reporting is enabled, but no position available.\n"); + return; + } - // Allocate ServiceEnvelope and fill it - meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); - se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id - se->gateway_id = owner.id; + // Allocate ServiceEnvelope and fill it + meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); + se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id + se->gateway_id = owner.id; - // Allocate MeshPacket and fill it - meshtastic_MeshPacket *mp = packetPool.allocZeroed(); - mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; - mp->from = nodeDB->getNodeNum(); - mp->to = NODENUM_BROADCAST; - mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; + // Allocate MeshPacket and fill it + meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + mp->from = nodeDB->getNodeNum(); + mp->to = NODENUM_BROADCAST; + mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; - // Fill MapReport message - meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; - memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); - memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); - mapReport.role = config.device.role; - mapReport.hw_model = owner.hw_model; - strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); - mapReport.region = config.lora.region; - mapReport.modem_preset = config.lora.modem_preset; - mapReport.has_default_channel = channels.hasDefaultChannel(); + // Fill MapReport message + meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; + memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); + memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); + mapReport.role = config.device.role; + mapReport.hw_model = owner.hw_model; + strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); + mapReport.region = config.lora.region; + mapReport.modem_preset = config.lora.modem_preset; + mapReport.has_default_channel = channels.hasDefaultChannel(); - // Set position with precision (same as in PositionModule) - if (map_position_precision < 32 && map_position_precision > 0) - { - mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); - mapReport.latitude_i += (1 << (31 - map_position_precision)); - mapReport.longitude_i += (1 << (31 - map_position_precision)); - } - else - { - mapReport.latitude_i = localPosition.latitude_i; - mapReport.longitude_i = localPosition.longitude_i; - } - mapReport.altitude = localPosition.altitude; - mapReport.position_precision = map_position_precision; + // Set position with precision (same as in PositionModule) + if (map_position_precision < 32 && map_position_precision > 0) { + mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.latitude_i += (1 << (31 - map_position_precision)); + mapReport.longitude_i += (1 << (31 - map_position_precision)); + } else { + mapReport.latitude_i = localPosition.latitude_i; + mapReport.longitude_i = localPosition.longitude_i; + } + mapReport.altitude = localPosition.altitude; + mapReport.position_precision = map_position_precision; - mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); + mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); - // Encode MapReport message and set it to MeshPacket in ServiceEnvelope - mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), - &meshtastic_MapReport_msg, &mapReport); - se->packet = mp; + // Encode MapReport message and set it to MeshPacket in ServiceEnvelope + mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), + &meshtastic_MapReport_msg, &mapReport); + se->packet = mp; - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[meshtastic_MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); - LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); - publish(mapTopic.c_str(), bytes, numBytes, false); + LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); + publish(mapTopic.c_str(), bytes, numBytes, false); - // Release the allocated memory for ServiceEnvelope and MeshPacket - mqttPool.release(se); - packetPool.release(mp); + // Release the allocated memory for ServiceEnvelope and MeshPacket + mqttPool.release(se); + packetPool.release(mp); - // Update the last report time - last_report_to_map = millis(); - } + // Update the last report time + last_report_to_map = millis(); + } } bool MQTT::isValidJsonEnvelope(JSONObject &json) { - // if "sender" is provided, avoid processing packets we uplinked - return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && - (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number - (json.find("from") != json.end()) && json["from"]->IsNumber() && - (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us - (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type - (json.find("payload") != json.end()); // should have a payload + // if "sender" is provided, avoid processing packets we uplinked + return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number + (json.find("from") != json.end()) && json["from"]->IsNumber() && + (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us + (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type + (json.find("payload") != json.end()); // should have a payload } diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 9ed2a0d6a..817583821 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -32,23 +32,23 @@ static uint16_t connectionHandle; class BluetoothPhoneAPI : public PhoneAPI { - /** - * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) - */ - virtual void onNowHasData(uint32_t fromRadioNum) override - { - PhoneAPI::onNowHasData(fromRadioNum); + /** + * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) + */ + virtual void onNowHasData(uint32_t fromRadioNum) override + { + PhoneAPI::onNowHasData(fromRadioNum); - LOG_INFO("BLE notify fromNum\n"); - fromNum.notify32(fromRadioNum); - } + LOG_INFO("BLE notify fromNum\n"); + fromNum.notify32(fromRadioNum); + } - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override - { - BLEConnection *connection = Bluefruit.Connection(connectionHandle); - return connection->connected(); - } + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override + { + BLEConnection *connection = Bluefruit.Connection(connectionHandle); + return connection->connected(); + } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; @@ -56,12 +56,12 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI; void onConnect(uint16_t conn_handle) { - // Get the reference to current connection - BLEConnection *connection = Bluefruit.Connection(conn_handle); - connectionHandle = conn_handle; - char central_name[32] = {0}; - connection->getPeerName(central_name, sizeof(central_name)); - LOG_INFO("BLE Connected to %s\n", central_name); + // Get the reference to current connection + BLEConnection *connection = Bluefruit.Connection(conn_handle); + connectionHandle = conn_handle; + char central_name[32] = {0}; + connection->getPeerName(central_name, sizeof(central_name)); + LOG_INFO("BLE Connected to %s\n", central_name); } /** * Callback invoked when a connection is dropped @@ -70,261 +70,248 @@ void onConnect(uint16_t conn_handle) */ void onDisconnect(uint16_t conn_handle, uint8_t reason) { - // FIXME - we currently assume only one active connection - LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); + // FIXME - we currently assume only one active connection + LOG_INFO("BLE Disconnected, reason = 0x%x\n", reason); } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { - // Display the raw request packet - LOG_INFO("CCCD Updated: %u\n", cccd_value); - // Check the characteristic this CCCD update is associated with in case - // this handler is used for multiple CCCD records. + // Display the raw request packet + LOG_INFO("CCCD Updated: %u\n", cccd_value); + // Check the characteristic this CCCD update is associated with in case + // this handler is used for multiple CCCD records. - // According to the GATT spec: cccd value = 0x0001 means notifications are enabled - // and cccd value = 0x0002 means indications are enabled + // According to the GATT spec: cccd value = 0x0001 means notifications are enabled + // and cccd value = 0x0002 means indications are enabled - if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) - { - auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) : chr->notifyEnabled(conn_hdl); - if (result) - { - LOG_INFO("Notify/Indicate enabled\n"); - } - else - { - LOG_INFO("Notify/Indicate disabled\n"); - } - } + if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) { + auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) : chr->notifyEnabled(conn_hdl); + if (result) { + LOG_INFO("Notify/Indicate enabled\n"); + } else { + LOG_INFO("Notify/Indicate disabled\n"); + } + } } void startAdv(void) { - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - // IncludeService UUID - // Bluefruit.ScanResponse.addService(meshBleService); - Bluefruit.ScanResponse.addTxPower(); - Bluefruit.ScanResponse.addName(); - // Include Name - // Bluefruit.Advertising.addName(); - Bluefruit.Advertising.addService(meshBleService); - /* Start Advertising - * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms - * - Timeout for fast mode is 30 seconds - * - Start(timeout) with timeout = 0 will advertise forever (until connected) - * - * For recommended advertising interval - * https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + // IncludeService UUID + // Bluefruit.ScanResponse.addService(meshBleService); + Bluefruit.ScanResponse.addTxPower(); + Bluefruit.ScanResponse.addName(); + // Include Name + // Bluefruit.Advertising.addName(); + Bluefruit.Advertising.addService(meshBleService); + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X } // Just ack that the caller is allowed to read static void authorizeRead(uint16_t conn_hdl) { - ble_gatts_rw_authorize_reply_params_t reply = {.type = BLE_GATTS_AUTHORIZE_TYPE_READ}; - reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; - sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); + ble_gatts_rw_authorize_reply_params_t reply = {.type = BLE_GATTS_AUTHORIZE_TYPE_READ}; + reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; + sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); } /** * client is starting read, pull the bytes from our API class */ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt_read_t *request) { - if (request->offset == 0) - { - // If the read is long, we will get multiple authorize invocations - we only populate data on the first - size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); - // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue - // or make empty if the queue is empty - fromRadio.write(fromRadioBytes, numBytes); - } - else - { - // LOG_INFO("Ignoring successor read\n"); - } - authorizeRead(conn_hdl); + if (request->offset == 0) { + // If the read is long, we will get multiple authorize invocations - we only populate data on the first + size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); + // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue + // or make empty if the queue is empty + fromRadio.write(fromRadioBytes, numBytes); + } else { + // LOG_INFO("Ignoring successor read\n"); + } + authorizeRead(conn_hdl); } void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { - LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); + LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); + bluetoothPhoneAPI->handleToRadio(data, len); } void setupMeshService(void) { - bluetoothPhoneAPI = new BluetoothPhoneAPI(); - meshBleService.begin(); - // Note: You must call .begin() on the BLEService before calling .begin() on - // any characteristic(s) within that service definition.. Calling .begin() on - // a BLECharacteristic will cause it to be added to the last BLEService that - // was 'begin()'ed! - auto secMode = - config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN ? SECMODE_OPEN : SECMODE_ENC_NO_MITM; - fromNum.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ); - fromNum.setPermission(secMode, SECMODE_NO_ACCESS); // FIXME, secure this!!! - fromNum.setFixedLen( - 0); // Variable len (either 0 or 4) FIXME consider changing protocol so it is fixed 4 byte len, where 0 means empty - fromNum.setMaxLen(4); - fromNum.setCccdWriteCallback(onCccd); // Optionally capture CCCD updates - // We don't yet need to hook the fromNum auth callback - // fromNum.setReadAuthorizeCallback(fromNumAuthorizeCb); - fromNum.write32(0); // Provide default fromNum of 0 - fromNum.begin(); + bluetoothPhoneAPI = new BluetoothPhoneAPI(); + meshBleService.begin(); + // Note: You must call .begin() on the BLEService before calling .begin() on + // any characteristic(s) within that service definition.. Calling .begin() on + // a BLECharacteristic will cause it to be added to the last BLEService that + // was 'begin()'ed! + auto secMode = + config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN ? SECMODE_OPEN : SECMODE_ENC_NO_MITM; + fromNum.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ); + fromNum.setPermission(secMode, SECMODE_NO_ACCESS); // FIXME, secure this!!! + fromNum.setFixedLen( + 0); // Variable len (either 0 or 4) FIXME consider changing protocol so it is fixed 4 byte len, where 0 means empty + fromNum.setMaxLen(4); + fromNum.setCccdWriteCallback(onCccd); // Optionally capture CCCD updates + // We don't yet need to hook the fromNum auth callback + // fromNum.setReadAuthorizeCallback(fromNumAuthorizeCb); + fromNum.write32(0); // Provide default fromNum of 0 + fromNum.begin(); - fromRadio.setProperties(CHR_PROPS_READ); - fromRadio.setPermission(secMode, SECMODE_NO_ACCESS); - fromRadio.setMaxLen(sizeof(fromRadioBytes)); - fromRadio.setReadAuthorizeCallback( - onFromRadioAuthorize, - false); // We don't call this callback via the adafruit queue, because we can safely run in the BLE context - fromRadio.setBuffer(fromRadioBytes, sizeof(fromRadioBytes)); // we preallocate our fromradio buffer so we won't waste space - // for two copies - fromRadio.begin(); + fromRadio.setProperties(CHR_PROPS_READ); + fromRadio.setPermission(secMode, SECMODE_NO_ACCESS); + fromRadio.setMaxLen(sizeof(fromRadioBytes)); + fromRadio.setReadAuthorizeCallback( + onFromRadioAuthorize, + false); // We don't call this callback via the adafruit queue, because we can safely run in the BLE context + fromRadio.setBuffer(fromRadioBytes, sizeof(fromRadioBytes)); // we preallocate our fromradio buffer so we won't waste space + // for two copies + fromRadio.begin(); - toRadio.setProperties(CHR_PROPS_WRITE); - toRadio.setPermission(secMode, secMode); // FIXME secure this! - toRadio.setFixedLen(0); - toRadio.setMaxLen(512); - toRadio.setBuffer(toRadioBytes, sizeof(toRadioBytes)); - // We don't call this callback via the adafruit queue, because we can safely run in the BLE context - toRadio.setWriteCallback(onToRadioWrite, false); - toRadio.begin(); + toRadio.setProperties(CHR_PROPS_WRITE); + toRadio.setPermission(secMode, secMode); // FIXME secure this! + toRadio.setFixedLen(0); + toRadio.setMaxLen(512); + toRadio.setBuffer(toRadioBytes, sizeof(toRadioBytes)); + // We don't call this callback via the adafruit queue, because we can safely run in the BLE context + toRadio.setWriteCallback(onToRadioWrite, false); + toRadio.begin(); - logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | CHR_PROPS_READ); - logRadio.setPermission(secMode, SECMODE_NO_ACCESS); - logRadio.setMaxLen(512); - logRadio.setCccdWriteCallback(onCccd); - logRadio.write32(0); - logRadio.begin(); + logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | CHR_PROPS_READ); + logRadio.setPermission(secMode, SECMODE_NO_ACCESS); + logRadio.setMaxLen(512); + logRadio.setCccdWriteCallback(onCccd); + logRadio.write32(0); + logRadio.begin(); } static uint32_t configuredPasskey; void NRF52Bluetooth::shutdown() { - // Shutdown bluetooth for minimum power draw - LOG_INFO("Disable NRF52 bluetooth\n"); - uint8_t connection_num = Bluefruit.connected(); - if (connection_num) - { - for (uint8_t i = 0; i < connection_num; i++) - { - LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); - Bluefruit.disconnect(i); - } - delay(100); // wait for ondisconnect; - } - Bluefruit.Advertising.stop(); + // Shutdown bluetooth for minimum power draw + LOG_INFO("Disable NRF52 bluetooth\n"); + uint8_t connection_num = Bluefruit.connected(); + if (connection_num) { + for (uint8_t i = 0; i < connection_num; i++) { + LOG_INFO("NRF52 bluetooth disconnecting handle %d\n", i); + Bluefruit.disconnect(i); + } + delay(100); // wait for ondisconnect; + } + Bluefruit.Advertising.stop(); } void NRF52Bluetooth::startDisabled() { - // Setup Bluetooth - nrf52Bluetooth->setup(); - // Shutdown bluetooth for minimum power draw - Bluefruit.Advertising.stop(); - Bluefruit.setTxPower(-40); // Minimum power - LOG_INFO("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); + // Setup Bluetooth + nrf52Bluetooth->setup(); + // Shutdown bluetooth for minimum power draw + Bluefruit.Advertising.stop(); + Bluefruit.setTxPower(-40); // Minimum power + LOG_INFO("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n"); } bool NRF52Bluetooth::isConnected() { - return Bluefruit.connected(connectionHandle); + return Bluefruit.connected(connectionHandle); } int NRF52Bluetooth::getRssi() { - return 0; // FIXME figure out where to source this + return 0; // FIXME figure out where to source this } void NRF52Bluetooth::setup() { - // Initialise the Bluefruit module - LOG_INFO("Initialize the Bluefruit nRF52 module\n"); - Bluefruit.autoConnLed(false); - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.begin(); - // Clear existing data. - Bluefruit.Advertising.stop(); - Bluefruit.Advertising.clearData(); - Bluefruit.ScanResponse.clearData(); - if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) - { - configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN - ? config.bluetooth.fixed_pin - : random(100000, 999999); - auto pinString = std::to_string(configuredPasskey); - LOG_INFO("Bluetooth pin set to '%i'\n", configuredPasskey); - Bluefruit.Security.setPIN(pinString.c_str()); - Bluefruit.Security.setIOCaps(true, false, false); - Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey); - Bluefruit.Security.setPairCompleteCallback(NRF52Bluetooth::onPairingCompleted); - Bluefruit.Security.setSecuredCallback(NRF52Bluetooth::onConnectionSecured); - meshBleService.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); - } - else - { - Bluefruit.Security.setIOCaps(false, false, false); - meshBleService.setPermission(SECMODE_OPEN, SECMODE_OPEN); - } - // Set the advertised device name (keep it short!) - Bluefruit.setName(getDeviceName()); - // Set the connect/disconnect callback handlers - Bluefruit.Periph.setConnectCallback(onConnect); - Bluefruit.Periph.setDisconnectCallback(onDisconnect); + // Initialise the Bluefruit module + LOG_INFO("Initialize the Bluefruit nRF52 module\n"); + Bluefruit.autoConnLed(false); + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.begin(); + // Clear existing data. + Bluefruit.Advertising.stop(); + Bluefruit.Advertising.clearData(); + Bluefruit.ScanResponse.clearData(); + if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { + configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN + ? config.bluetooth.fixed_pin + : random(100000, 999999); + auto pinString = std::to_string(configuredPasskey); + LOG_INFO("Bluetooth pin set to '%i'\n", configuredPasskey); + Bluefruit.Security.setPIN(pinString.c_str()); + Bluefruit.Security.setIOCaps(true, false, false); + Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey); + Bluefruit.Security.setPairCompleteCallback(NRF52Bluetooth::onPairingCompleted); + Bluefruit.Security.setSecuredCallback(NRF52Bluetooth::onConnectionSecured); + meshBleService.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + } else { + Bluefruit.Security.setIOCaps(false, false, false); + meshBleService.setPermission(SECMODE_OPEN, SECMODE_OPEN); + } + // Set the advertised device name (keep it short!) + Bluefruit.setName(getDeviceName()); + // Set the connect/disconnect callback handlers + Bluefruit.Periph.setConnectCallback(onConnect); + Bluefruit.Periph.setDisconnectCallback(onDisconnect); #ifndef BLE_DFU_SECURE - bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); - bledfu.begin(); // Install the DFU helper + bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + bledfu.begin(); // Install the DFU helper #else - bledfusecure.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // add by WayenWeng - bledfusecure.begin(); // Install the DFU helper + bledfusecure.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // add by WayenWeng + bledfusecure.begin(); // Install the DFU helper #endif - // Configure and Start the Device Information Service - LOG_INFO("Configuring the Device Information Service\n"); - bledis.setModel(optstr(HW_VERSION)); - bledis.setFirmwareRev(optstr(APP_VERSION)); - bledis.begin(); - // Start the BLE Battery Service and set it to 100% - LOG_INFO("Configuring the Battery Service\n"); - blebas.begin(); - blebas.write(0); // Unknown battery level for now - // Setup the Heart Rate Monitor service using - // BLEService and BLECharacteristic classes - LOG_INFO("Configuring the Mesh bluetooth service\n"); - setupMeshService(); - // Setup the advertising packet(s) - LOG_INFO("Setting up the advertising payload(s)\n"); - startAdv(); - LOG_INFO("Advertising\n"); + // Configure and Start the Device Information Service + LOG_INFO("Configuring the Device Information Service\n"); + bledis.setModel(optstr(HW_VERSION)); + bledis.setFirmwareRev(optstr(APP_VERSION)); + bledis.begin(); + // Start the BLE Battery Service and set it to 100% + LOG_INFO("Configuring the Battery Service\n"); + blebas.begin(); + blebas.write(0); // Unknown battery level for now + // Setup the Heart Rate Monitor service using + // BLEService and BLECharacteristic classes + LOG_INFO("Configuring the Mesh bluetooth service\n"); + setupMeshService(); + // Setup the advertising packet(s) + LOG_INFO("Setting up the advertising payload(s)\n"); + startAdv(); + LOG_INFO("Advertising\n"); } void NRF52Bluetooth::resumeAdvertising() { - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); } /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level) { - blebas.write(level); + blebas.write(level); } void NRF52Bluetooth::clearBonds() { - LOG_INFO("Clearing bluetooth bonds!\n"); - bond_print_list(BLE_GAP_ROLE_PERIPH); - bond_print_list(BLE_GAP_ROLE_CENTRAL); - Bluefruit.Periph.clearBonds(); - Bluefruit.Central.clearBonds(); + LOG_INFO("Clearing bluetooth bonds!\n"); + bond_print_list(BLE_GAP_ROLE_PERIPH); + bond_print_list(BLE_GAP_ROLE_CENTRAL); + Bluefruit.Periph.clearBonds(); + Bluefruit.Central.clearBonds(); } void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle) { - LOG_INFO("BLE connection secured\n"); + LOG_INFO("BLE connection secured\n"); } bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { - LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); - powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); #if !defined(MESHTASTIC_EXCLUDE_SCREEN) - screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void - { + screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { char btPIN[16] = "888888"; snprintf(btPIN, sizeof(btPIN), "%06u", configuredPasskey); int x_offset = display->width() / 2; @@ -347,35 +334,34 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke String deviceName = "Name: "; deviceName.concat(getDeviceName()); y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; - display->drawString(x_offset + x, y_offset + y, deviceName); }); + display->drawString(x_offset + x, y_offset + y, deviceName); + }); #endif - if (match_request) - { - uint32_t start_time = millis(); - while (millis() < start_time + 30000) - { - if (!Bluefruit.connected(conn_handle)) - break; - } - } - LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); - return true; + if (match_request) { + uint32_t start_time = millis(); + while (millis() < start_time + 30000) { + if (!Bluefruit.connected(conn_handle)) + break; + } + } + LOG_INFO("BLE passkey pairing: match_request=%i\n", match_request); + return true; } void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) { - if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) - LOG_INFO("BLE pairing success\n"); - else - LOG_INFO("BLE pairing failed\n"); - screen->endAlert(); + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) + LOG_INFO("BLE pairing success\n"); + else + LOG_INFO("BLE pairing failed\n"); + screen->endAlert(); } void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length) { - if (!isConnected() || length > 512) - return; - if (logRadio.indicateEnabled()) - logRadio.indicate(logMessage, (uint16_t)length); - else - logRadio.notify(logMessage, (uint16_t)length); + if (!isConnected() || length > 512) + return; + if (logRadio.indicateEnabled()) + logRadio.indicate(logMessage, (uint16_t)length); + else + logRadio.notify(logMessage, (uint16_t)length); } \ No newline at end of file From 9f3a1c121404960bdf917082d5ba68712101a4c7 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Thu, 12 Sep 2024 19:12:57 +0200 Subject: [PATCH 10/11] Trunk fmt --- src/AccelerometerThread.h | 2 +- src/mqtt/MQTT.cpp | 11 +- src/platform/nrf52/NRF52Bluetooth.cpp | 12 +- .../MeshPacketSerializer_nRF52.cpp | 694 ++++++++---------- 4 files changed, 329 insertions(+), 390 deletions(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 37e7aab0d..7c133d9ab 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -95,7 +95,7 @@ class AccelerometerThread : public concurrency::OSThread return 500; } #if defined(RAK_4631) -#if !defined (MESHTASTIC_EXCLUDE_SCREEN) +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { sBmx160SensorData_t magAccel; sBmx160SensorData_t gAccel; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a4921e684..fa8e2745c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -378,8 +378,9 @@ void MQTT::sendSubscriptions() hasDownlink = true; std::string topic = cryptTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topic.c_str()); - pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### + pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? +#if !defined(ARCH_NRF52) || \ + defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJSON ### if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/+"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); @@ -480,7 +481,8 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || \ + defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet); @@ -562,7 +564,8 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); -#if !defined(ARCH_NRF52) || defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### +#if !defined(ARCH_NRF52) || \ + defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 817583821..2e9dbc1f8 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -19,7 +19,7 @@ static BLEBas blebas; // BAS (Battery Service) helper class instance #ifndef BLE_DFU_SECURE static BLEDfu bledfu; // DFU software update helper service #else -static BLEDfuSecure bledfusecure; // DFU software update helper service +static BLEDfuSecure bledfusecure; // DFU software update helper service #endif // This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in @@ -114,8 +114,8 @@ void startAdv(void) */ Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X } // Just ack that the caller is allowed to read static void authorizeRead(uint16_t conn_hdl) @@ -172,7 +172,7 @@ void setupMeshService(void) fromRadio.setMaxLen(sizeof(fromRadioBytes)); fromRadio.setReadAuthorizeCallback( onFromRadioAuthorize, - false); // We don't call this callback via the adafruit queue, because we can safely run in the BLE context + false); // We don't call this callback via the adafruit queue, because we can safely run in the BLE context fromRadio.setBuffer(fromRadioBytes, sizeof(fromRadioBytes)); // we preallocate our fromradio buffer so we won't waste space // for two copies fromRadio.begin(); @@ -262,7 +262,7 @@ void NRF52Bluetooth::setup() bledfu.begin(); // Install the DFU helper #else bledfusecure.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // add by WayenWeng - bledfusecure.begin(); // Install the DFU helper + bledfusecure.begin(); // Install the DFU helper #endif // Configure and Start the Device Information Service LOG_INFO("Configuring the Device Information Service\n"); @@ -286,7 +286,7 @@ void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); } /// Given a level between 0-100, update the BLE attribute diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 8c58fba27..cd3aa1630 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -1,417 +1,353 @@ #ifdef NRF52_USE_JSON #warning 'Using nRF52 Serializer' -#include "MeshPacketSerializer.h" #include "ArduinoJson.h" +#include "MeshPacketSerializer.h" #include "NodeDB.h" #include "mesh/generated/meshtastic/mqtt.pb.h" +#include "mesh/generated/meshtastic/remote_hardware.pb.h" #include "mesh/generated/meshtastic/telemetry.pb.h" #include "modules/RoutingModule.h" #include #include -#include "mesh/generated/meshtastic/remote_hardware.pb.h" StaticJsonDocument<1024> jsonObj; StaticJsonDocument<1024> arrayObj; std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, bool shouldLog) { - // the created jsonObj is immutable after creation, so - // we need to do the heavy lifting before assembling it. - std::string msgType; - jsonObj.clear(); - arrayObj.clear(); + // the created jsonObj is immutable after creation, so + // we need to do the heavy lifting before assembling it. + std::string msgType; + jsonObj.clear(); + arrayObj.clear(); - if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) - { - switch (mp->decoded.portnum) - { - case meshtastic_PortNum_TEXT_MESSAGE_APP: - { - msgType = "text"; - // convert bytes to string - if (shouldLog) - LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + switch (mp->decoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: { + msgType = "text"; + // convert bytes to string + if (shouldLog) + LOG_DEBUG("got text message of size %u\n", mp->decoded.payload.size); - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - // check if this is a JSON payload - StaticJsonDocument<512> text_doc; - DeserializationError error = deserializeJson(text_doc, payloadStr); - if (error) - { - // if it isn't, then we need to create a json object - // with the string as the value - if (shouldLog) - LOG_INFO("text message payload is of type plaintext\n"); - jsonObj["payload"]["text"] = payloadStr; - } - else - { - // if it is, then we can just use the json object - if (shouldLog) - LOG_INFO("text message payload is of type json\n"); - jsonObj["payload"] = text_doc; - } - break; - } - case meshtastic_PortNum_TELEMETRY_APP: - { - msgType = "telemetry"; - meshtastic_Telemetry scratch; - meshtastic_Telemetry *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) - { - decoded = &scratch; - if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) - { - jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; - jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; - jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; - jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; - jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; - } - else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) - { - jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; - jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; - jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; - jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; - jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; - jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; - jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; - jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; - jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; - jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; - jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; - jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; - jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; - } - else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) - { - jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; - jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; - jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; - jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; - jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; - jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; - } - else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) - { - jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; - jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; - jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; - jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; - jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; - jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; - } - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for telemetry message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NODEINFO_APP: - { - msgType = "nodeinfo"; - meshtastic_User scratch; - meshtastic_User *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) - { - decoded = &scratch; - jsonObj["payload"]["id"] = decoded->id; - jsonObj["payload"]["longname"] = decoded->long_name; - jsonObj["payload"]["shortname"] = decoded->short_name; - jsonObj["payload"]["hardware"] = decoded->hw_model; - jsonObj["payload"]["role"] = (int)decoded->role; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_POSITION_APP: - { - msgType = "position"; - meshtastic_Position scratch; - meshtastic_Position *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) - { - decoded = &scratch; - if ((int)decoded->time) - { - jsonObj["payload"]["time"] = (unsigned int)decoded->time; - } - if ((int)decoded->timestamp) - { - jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; - } - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - if ((int)decoded->altitude) - { - jsonObj["payload"]["altitude"] = (int)decoded->altitude; - } - if ((int)decoded->ground_speed) - { - jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; - } - if (int(decoded->ground_track)) - { - jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; - } - if (int(decoded->sats_in_view)) - { - jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; - } - if ((int)decoded->PDOP) - { - jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; - } - if ((int)decoded->HDOP) - { - jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; - } - if ((int)decoded->VDOP) - { - jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; - } - if ((int)decoded->precision_bits) - { - jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; - } - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_WAYPOINT_APP: - { - msgType = "position"; - meshtastic_Waypoint scratch; - meshtastic_Waypoint *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) - { - decoded = &scratch; - jsonObj["payload"]["id"] = (unsigned int)decoded->id; - jsonObj["payload"]["name"] = decoded->name; - jsonObj["payload"]["description"] = decoded->description; - jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; - jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; - jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; - jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for position message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_NEIGHBORINFO_APP: - { - msgType = "neighborinfo"; - meshtastic_NeighborInfo scratch; - meshtastic_NeighborInfo *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, - &scratch)) - { - decoded = &scratch; - jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; - jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; - jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; - jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + // check if this is a JSON payload + StaticJsonDocument<512> text_doc; + DeserializationError error = deserializeJson(text_doc, payloadStr); + if (error) { + // if it isn't, then we need to create a json object + // with the string as the value + if (shouldLog) + LOG_INFO("text message payload is of type plaintext\n"); + jsonObj["payload"]["text"] = payloadStr; + } else { + // if it is, then we can just use the json object + if (shouldLog) + LOG_INFO("text message payload is of type json\n"); + jsonObj["payload"] = text_doc; + } + break; + } + case meshtastic_PortNum_TELEMETRY_APP: { + msgType = "telemetry"; + meshtastic_Telemetry scratch; + meshtastic_Telemetry *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { + decoded = &scratch; + if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { + jsonObj["payload"]["battery_level"] = (unsigned int)decoded->variant.device_metrics.battery_level; + jsonObj["payload"]["voltage"] = decoded->variant.device_metrics.voltage; + jsonObj["payload"]["channel_utilization"] = decoded->variant.device_metrics.channel_utilization; + jsonObj["payload"]["air_util_tx"] = decoded->variant.device_metrics.air_util_tx; + jsonObj["payload"]["uptime_seconds"] = (unsigned int)decoded->variant.device_metrics.uptime_seconds; + } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { + jsonObj["payload"]["temperature"] = decoded->variant.environment_metrics.temperature; + jsonObj["payload"]["relative_humidity"] = decoded->variant.environment_metrics.relative_humidity; + jsonObj["payload"]["barometric_pressure"] = decoded->variant.environment_metrics.barometric_pressure; + jsonObj["payload"]["gas_resistance"] = decoded->variant.environment_metrics.gas_resistance; + jsonObj["payload"]["voltage"] = decoded->variant.environment_metrics.voltage; + jsonObj["payload"]["current"] = decoded->variant.environment_metrics.current; + jsonObj["payload"]["lux"] = decoded->variant.environment_metrics.lux; + jsonObj["payload"]["white_lux"] = decoded->variant.environment_metrics.white_lux; + jsonObj["payload"]["iaq"] = (uint)decoded->variant.environment_metrics.iaq; + jsonObj["payload"]["wind_speed"] = decoded->variant.environment_metrics.wind_speed; + jsonObj["payload"]["wind_direction"] = (uint)decoded->variant.environment_metrics.wind_direction; + jsonObj["payload"]["wind_gust"] = decoded->variant.environment_metrics.wind_gust; + jsonObj["payload"]["wind_lull"] = decoded->variant.environment_metrics.wind_lull; + } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { + jsonObj["payload"]["pm10"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_standard; + jsonObj["payload"]["pm25"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_standard; + jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; + jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; + jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; + jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; + } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { + jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; + jsonObj["payload"]["current_ch1"] = decoded->variant.power_metrics.ch1_current; + jsonObj["payload"]["voltage_ch2"] = decoded->variant.power_metrics.ch2_voltage; + jsonObj["payload"]["current_ch2"] = decoded->variant.power_metrics.ch2_current; + jsonObj["payload"]["voltage_ch3"] = decoded->variant.power_metrics.ch3_voltage; + jsonObj["payload"]["current_ch3"] = decoded->variant.power_metrics.ch3_current; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for telemetry message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NODEINFO_APP: { + msgType = "nodeinfo"; + meshtastic_User scratch; + meshtastic_User *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_User_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = decoded->id; + jsonObj["payload"]["longname"] = decoded->long_name; + jsonObj["payload"]["shortname"] = decoded->short_name; + jsonObj["payload"]["hardware"] = decoded->hw_model; + jsonObj["payload"]["role"] = (int)decoded->role; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for nodeinfo message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_POSITION_APP: { + msgType = "position"; + meshtastic_Position scratch; + meshtastic_Position *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { + decoded = &scratch; + if ((int)decoded->time) { + jsonObj["payload"]["time"] = (unsigned int)decoded->time; + } + if ((int)decoded->timestamp) { + jsonObj["payload"]["timestamp"] = (unsigned int)decoded->timestamp; + } + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + if ((int)decoded->altitude) { + jsonObj["payload"]["altitude"] = (int)decoded->altitude; + } + if ((int)decoded->ground_speed) { + jsonObj["payload"]["ground_speed"] = (unsigned int)decoded->ground_speed; + } + if (int(decoded->ground_track)) { + jsonObj["payload"]["ground_track"] = (unsigned int)decoded->ground_track; + } + if (int(decoded->sats_in_view)) { + jsonObj["payload"]["sats_in_view"] = (unsigned int)decoded->sats_in_view; + } + if ((int)decoded->PDOP) { + jsonObj["payload"]["PDOP"] = (int)decoded->PDOP; + } + if ((int)decoded->HDOP) { + jsonObj["payload"]["HDOP"] = (int)decoded->HDOP; + } + if ((int)decoded->VDOP) { + jsonObj["payload"]["VDOP"] = (int)decoded->VDOP; + } + if ((int)decoded->precision_bits) { + jsonObj["payload"]["precision_bits"] = (int)decoded->precision_bits; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_WAYPOINT_APP: { + msgType = "position"; + meshtastic_Waypoint scratch; + meshtastic_Waypoint *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { + decoded = &scratch; + jsonObj["payload"]["id"] = (unsigned int)decoded->id; + jsonObj["payload"]["name"] = decoded->name; + jsonObj["payload"]["description"] = decoded->description; + jsonObj["payload"]["expire"] = (unsigned int)decoded->expire; + jsonObj["payload"]["locked_to"] = (unsigned int)decoded->locked_to; + jsonObj["payload"]["latitude_i"] = (int)decoded->latitude_i; + jsonObj["payload"]["longitude_i"] = (int)decoded->longitude_i; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for position message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_NEIGHBORINFO_APP: { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) { + decoded = &scratch; + jsonObj["payload"]["node_id"] = (unsigned int)decoded->node_id; + jsonObj["payload"]["node_broadcast_interval_secs"] = (unsigned int)decoded->node_broadcast_interval_secs; + jsonObj["payload"]["last_sent_by_id"] = (unsigned int)decoded->last_sent_by_id; + jsonObj["payload"]["neighbors_count"] = decoded->neighbors_count; - JsonObject neighbors_obj = arrayObj.to(); - JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); - JsonObject neighbors_0 = neighbors.createNestedObject(); + JsonObject neighbors_obj = arrayObj.to(); + JsonArray neighbors = neighbors_obj.createNestedArray("neighbors"); + JsonObject neighbors_0 = neighbors.createNestedObject(); - for (uint8_t i = 0; i < decoded->neighbors_count; i++) - { - neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; - neighbors_0["snr"] = (int)decoded->neighbors[i].snr; - neighbors[i + 1] = neighbors_0; - neighbors_0.clear(); - } - neighbors.remove(0); - jsonObj["payload"]["neighbors"] = neighbors; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); - return ""; - } - break; - } - case meshtastic_PortNum_TRACEROUTE_APP: - { - if (mp->decoded.request_id) - { // Only report the traceroute response - msgType = "traceroute"; - meshtastic_RouteDiscovery scratch; - meshtastic_RouteDiscovery *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, - &scratch)) - { - decoded = &scratch; - JsonArray route = arrayObj.createNestedArray("route"); + for (uint8_t i = 0; i < decoded->neighbors_count; i++) { + neighbors_0["node_id"] = (unsigned int)decoded->neighbors[i].node_id; + neighbors_0["snr"] = (int)decoded->neighbors[i].snr; + neighbors[i + 1] = neighbors_0; + neighbors_0.clear(); + } + neighbors.remove(0); + jsonObj["payload"]["neighbors"] = neighbors; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + return ""; + } + break; + } + case meshtastic_PortNum_TRACEROUTE_APP: { + if (mp->decoded.request_id) { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) { + decoded = &scratch; + JsonArray route = arrayObj.createNestedArray("route"); - auto addToRoute = [](JsonArray *route, NodeNum num) - { - char long_name[40] = "Unknown"; - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); - bool name_known = node ? node->has_user : false; - if (name_known) - memcpy(long_name, node->user.long_name, sizeof(long_name)); - route->add(long_name); - }; + auto addToRoute = [](JsonArray *route, NodeNum num) { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->add(long_name); + }; - addToRoute(&route, mp->to); // route.add(mp->to); - for (uint8_t i = 0; i < decoded->route_count; i++) - { - addToRoute(&route, decoded->route[i]); // route.add(decoded->route[i]); - } - addToRoute(&route, mp->from); // route.add(mp->from); // Ended at the original destination (source of response) + addToRoute(&route, mp->to); // route.add(mp->to); + for (uint8_t i = 0; i < decoded->route_count; i++) { + addToRoute(&route, decoded->route[i]); // route.add(decoded->route[i]); + } + addToRoute(&route, + mp->from); // route.add(mp->from); // Ended at the original destination (source of response) - jsonObj["payload"]["route"] = route; - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for traceroute message!\n"); - return ""; - } - } - else - { - LOG_WARN("Traceroute response not reported"); - return ""; - } - break; - } - case meshtastic_PortNum_DETECTION_SENSOR_APP: - { - msgType = "detection"; - char payloadStr[(mp->decoded.payload.size) + 1]; - memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); - payloadStr[mp->decoded.payload.size] = 0; // null terminated string - jsonObj["payload"]["text"] = payloadStr; - break; - } - case meshtastic_PortNum_REMOTE_HARDWARE_APP: - { - meshtastic_HardwareMessage scratch; - meshtastic_HardwareMessage *decoded = NULL; - memset(&scratch, 0, sizeof(scratch)); - if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, - &scratch)) - { - decoded = &scratch; - if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) - { - msgType = "gpios_changed"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - } - else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) - { - msgType = "gpios_read_reply"; - jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; - jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; - } - } - else if (shouldLog) - { - LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); - return ""; - } - break; - } - // add more packet types here if needed - default: - LOG_WARN("Unsupported packet type %d\n", mp->decoded.portnum); - return ""; - break; - } - } - else if (shouldLog) - { - LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); - return ""; - } + jsonObj["payload"]["route"] = route; + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + return ""; + } + } else { + LOG_WARN("Traceroute response not reported"); + return ""; + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + jsonObj["payload"]["text"] = payloadStr; + break; + } + case meshtastic_PortNum_REMOTE_HARDWARE_APP: { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { + msgType = "gpios_changed"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { + msgType = "gpios_read_reply"; + jsonObj["payload"]["gpio_value"] = (unsigned int)decoded->gpio_value; + jsonObj["payload"]["gpio_mask"] = (unsigned int)decoded->gpio_mask; + } + } else if (shouldLog) { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + return ""; + } + break; + } + // add more packet types here if needed + default: + LOG_WARN("Unsupported packet type %d\n", mp->decoded.portnum); + return ""; + break; + } + } else if (shouldLog) { + LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n"); + return ""; + } - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["type"] = msgType.c_str(); - jsonObj["sender"] = owner.id; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) - { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["type"] = msgType.c_str(); + jsonObj["sender"] = owner.id; + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } - // serialize and write it to the stream + // serialize and write it to the stream - // Serial.printf("serialized json message: \r\n"); - // serializeJson(jsonObj, Serial); - // Serial.println(""); + // Serial.printf("serialized json message: \r\n"); + // serializeJson(jsonObj, Serial); + // Serial.println(""); - std::string jsonStr = ""; - serializeJson(jsonObj, jsonStr); + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); - if (shouldLog) - LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); + if (shouldLog) + LOG_INFO("serialized json message: %s\n", jsonStr.c_str()); - return jsonStr; + return jsonStr; } std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPacket *mp) { - jsonObj.clear(); - jsonObj["id"] = (unsigned int)mp->id; - jsonObj["time_ms"] = (double)millis(); - jsonObj["timestamp"] = (unsigned int)mp->rx_time; - jsonObj["to"] = (unsigned int)mp->to; - jsonObj["from"] = (unsigned int)mp->from; - jsonObj["channel"] = (unsigned int)mp->channel; - jsonObj["want_ack"] = mp->want_ack; + jsonObj.clear(); + jsonObj["id"] = (unsigned int)mp->id; + jsonObj["time_ms"] = (double)millis(); + jsonObj["timestamp"] = (unsigned int)mp->rx_time; + jsonObj["to"] = (unsigned int)mp->to; + jsonObj["from"] = (unsigned int)mp->from; + jsonObj["channel"] = (unsigned int)mp->channel; + jsonObj["want_ack"] = mp->want_ack; - if (mp->rx_rssi != 0) - jsonObj["rssi"] = (int)mp->rx_rssi; - if (mp->rx_snr != 0) - jsonObj["snr"] = (float)mp->rx_snr; - if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) - { - jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); - jsonObj["hop_start"] = (unsigned int)(mp->hop_start); - } - jsonObj["size"] = (unsigned int)mp->encrypted.size; - auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); - jsonObj["bytes"] = encryptedStr.c_str(); + if (mp->rx_rssi != 0) + jsonObj["rssi"] = (int)mp->rx_rssi; + if (mp->rx_snr != 0) + jsonObj["snr"] = (float)mp->rx_snr; + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) { + jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit); + jsonObj["hop_start"] = (unsigned int)(mp->hop_start); + } + jsonObj["size"] = (unsigned int)mp->encrypted.size; + auto encryptedStr = bytesToHex(mp->encrypted.bytes, mp->encrypted.size); + jsonObj["bytes"] = encryptedStr.c_str(); - // serialize and write it to the stream - std::string jsonStr = ""; - serializeJson(jsonObj, jsonStr); + // serialize and write it to the stream + std::string jsonStr = ""; + serializeJson(jsonObj, jsonStr); - return jsonStr; + return jsonStr; } #endif \ No newline at end of file From cd480846e93efc2d06da8f84a5028082bbd093ac Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Thu, 12 Sep 2024 19:52:36 +0200 Subject: [PATCH 11/11] Remove accelerometer lib --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1847bd113..167d8710b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -161,5 +161,4 @@ lib_deps = mprograms/QMC5883LCompass@^1.2.0 - https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file + https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee \ No newline at end of file