diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index d6a756becd..e7285cef1d 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -37,6 +37,7 @@ build_flags = -DLIBPAX_ARDUINO -DLIBPAX_WIFI -DLIBPAX_BLE + -DHAS_UDP_MULTICAST=1 ;-DDEBUG_HEAP lib_deps = diff --git a/src/main.cpp b/src/main.cpp index c2b20b1c10..5afbc19286 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,6 +114,11 @@ AccelerometerThread *accelerometerThread = nullptr; AudioThread *audioThread = nullptr; #endif +#ifdef HAS_UDP_MULTICAST +#include "mesh/udp/UdpMulticastThread.h" +UdpMulticastThread *udpThread = nullptr; +#endif + #if defined(TCXO_OPTIONAL) float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down. #endif @@ -783,6 +788,11 @@ void setup() LOG_DEBUG("Start audio thread"); audioThread = new AudioThread(); #endif + +#ifdef HAS_UDP_MULTICAST + LOG_DEBUG("Start multicast thread"); + udpThread = new UdpMulticastThread(); +#endif service = new MeshService(); service->init(); @@ -1278,4 +1288,4 @@ void loop() mainDelay.delay(delayMsec); } } -#endif +#endif \ No newline at end of file diff --git a/src/main.h b/src/main.h index b3f58ae4b3..f8213cd75d 100644 --- a/src/main.h +++ b/src/main.h @@ -49,6 +49,11 @@ extern Adafruit_DRV2605 drv; extern AudioThread *audioThread; #endif +#ifdef HAS_UDP_MULTICAST +#include "mesh/udp/UdpMulticastThread.h" +extern UdpMulticastThread *udpThread; +#endif + // Global Screen singleton. extern graphics::Screen *screen; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8e084c99df..45679e3f6f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -299,6 +299,9 @@ NodeDB::NodeDB() resetRadioConfig(); // If bogus settings got saved, then fix them // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes); + // Uncomment below to always enable UDP broadcasts + // config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST; + // If we are setup to broadcast on the default channel, ensure that the telemetry intervals are coerced to the minimum value // of 30 minutes or more if (channels.isDefaultChannel(channels.getPrimaryIndex())) { diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index f55e7cc5a4..4bc0a1e039 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -274,6 +274,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) abortSendAndNak(encodeResult, p); return encodeResult; // FIXME - this isn't a valid ErrorCode } +#if HAS_UDP_MULTICAST + if (udpThread && isFromUs(p) && + config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST) { + udpThread->onSend(const_cast(p)); + } +#endif #if !MESHTASTIC_EXCLUDE_MQTT // Only publish to MQTT if we're the original transmitter of the packet if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) { @@ -624,6 +630,12 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet if ((decoded || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled && !isFromUs(p) && mqtt) mqtt->onSend(*p_encrypted, *p, p->channel); +#endif +#if HAS_UDP_MULTICAST + if ((decoded || p_encrypted->pki_encrypted) && udpThread && + config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST) { + udpThread->onSend(const_cast(p_encrypted)); + } #endif } diff --git a/src/mesh/udp/UdpMulticastThread.h b/src/mesh/udp/UdpMulticastThread.h new file mode 100644 index 0000000000..9128d3b5c8 --- /dev/null +++ b/src/mesh/udp/UdpMulticastThread.h @@ -0,0 +1,70 @@ +#pragma once +#if HAS_UDP_MULTICAST +#include "configuration.h" +#include "main.h" +#include "mesh/Router.h" + +#include +#include + +#define UDP_MULTICAST_DEFAUL_PORT 4403 // Default port for UDP multicast is same as TCP api server +#define UDP_MULTICAST_THREAD_INTERVAL_MS 15000 + +class UdpMulticastThread : public concurrency::OSThread +{ + public: + UdpMulticastThread() : OSThread("UdpMulticast") { udpIpAddress = IPAddress(224, 0, 0, 69); } + + void start() + { + if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT)) { + LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str()); + udp.onPacket([this](AsyncUDPPacket packet) { onReceive(packet); }); + } else { + LOG_DEBUG("Failed to listen on UDP"); + } + } + + void onReceive(AsyncUDPPacket packet) + { + size_t packetLength = packet.length(); + LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength); + meshtastic_MeshPacket mp; + uint8_t bytes[meshtastic_MeshPacket_size]; // Allocate buffer for the data + size_t packetSize = packet.readBytes(bytes, packet.length()); + LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetSize); + bool isPacketDecoded = pb_decode_from_bytes(bytes, packetLength, &meshtastic_MeshPacket_msg, &mp); + if (isPacketDecoded && router) { + UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp); + // Unset received SNR/RSSI + p->rx_snr = 0; + p->rx_rssi = 0; + router->enqueueReceivedMessage(p.release()); + } + } + + bool onSend(const meshtastic_MeshPacket *mp) + { + if (!mp || WiFi.status() != WL_CONNECTED) { + return false; + } + LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id); + uint8_t buffer[meshtastic_MeshPacket_size]; + size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp); + udp.broadcastTo(buffer, encodedLength, UDP_MULTICAST_DEFAUL_PORT); + return true; + } + + protected: + int32_t runOnce() override + { + canSleep = true; + // TODO: Implement nodeinfo broadcast + return UDP_MULTICAST_THREAD_INTERVAL_MS; + } + + private: + IPAddress udpIpAddress; + AsyncUDP udp; +}; +#endif // ARCH_ESP32 \ No newline at end of file diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index dcfcdc0471..34a70d08cc 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -112,6 +112,12 @@ static void onNetworkConnected() APStartupComplete = true; } +#if HAS_UDP_MULTICAST + if (udpThread) { + udpThread->start(); + } +#endif + // FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected' #ifndef MESHTASTIC_EXCLUDE_MQTT if (mqtt) @@ -437,4 +443,4 @@ uint8_t getWifiDisconnectReason() { return wifiDisconnectReason; } -#endif +#endif \ No newline at end of file