Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions MQTT_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,54 @@ get mqtt.status

---

## Ethernet Observer (RAK4631 + RAK13800)

Besides the ESP32/WiFi observers there is a wired Ethernet variant for the nRF52-based
**RAK4631** with a **RAK13800** (WIZnet W5100S) module in the WisBlock IO slot.

**Build target:** `RAK_4631_repeater_observer_mqtt_eth`

```bash
pio run -e RAK_4631_repeater_observer_mqtt_eth -t upload
```

**What is the same as the WiFi observers**
- Same slot/preset system and CLI (`set mqttN.preset`, `set mqtt.iata`, `get mqtt.status`, `get mqttN.diag`, ...)
- Same presets, including `wss://` + Ed25519 JWT (default `dutchmeshcore-1` / `dutchmeshcore-2`)
- Same topics and JSON payloads (status / packets / raw)

**What is different**
- No WiFi settings needed: the board uses DHCP on the Ethernet port. `wifi.ssid` / `wifi.pwd` are ignored.
- `get eth.status` (also `get wifi.status`) shows link, IP, MAC and uptime.
- Max **2 active MQTT slots** (W5100S has 4 sockets; each TLS slot uses ~19 KB RAM for BearSSL buffers).
- MQTT publishes are QoS 0 (status messages too); retained flag follows the preset.
- TLS: BearSSL, TLS 1.2 (ECDHE + AES-GCM / ChaCha20). Trust anchors are the CA certificates of the built-in
presets (GTS Root R4, ISRG Root X1); custom `mqtts://` / `wss://` brokers must chain to one of those.
- SNMP is not available in this build.

**Hardware / pins** (same as Meshtastic `rak4631_eth_gw`, overridable via build flags):

| Signal | RAK4631 pin | Build flag |
|--------|-------------|------------|
| SPI CS | 26 (WB_SPI_CS) | `ETH_PIN_CS` |
| SPI SCK / MISO / MOSI | 3 / 29 / 30 (IO slot SPI, own SPIM2 instance) | `ETH_PIN_SCK` / `ETH_PIN_MISO` / `ETH_PIN_MOSI` |
| Reset | 21 (WB_IO3) | `ETH_PIN_RESET` |
| Power (3V3_S) | 34 (WB_IO2) | `ETH_PIN_POWER` |

**Implementation**
- `src/helpers/bridges/MQTTBridgeEthernet.{h,cpp}` — same `MQTTBridge` class/API, selected by `MQTTBridge.h` when `ETHERNET_ENABLED` is defined
- `src/helpers/net/BearTLSClient.{h,cpp}` — BearSSL TLS client on top of any Arduino `Client` (full 16 KB input record buffer)
- `src/helpers/net/WSClient.{h,cpp}` — WebSocket client (`Sec-WebSocket-Protocol: mqtt`) as an Arduino `Client`
- Stack per slot: `PubSubClient -> WSClient -> BearTLSClient -> EthernetClient`
- All network I/O runs in its own FreeRTOS task; the W5100S is never accessed from the mesh loop. NTP time is
written to the RTC from the mesh loop task (I2C safety).
- Uses upstream's `ETHERNET_ENABLED` flag, so `RAK4631Board.cpp` powers the IO slot (WB_IO2) from an early
constructor, before the framework starts (required for PoE boot).
- Upstream's Ethernet CLI (`helpers/nrf52/EthernetCLI.h`, telnet on port 23) also keys off `ETHERNET_ENABLED`.
It must not run in the MQTT build: it would initialise the W5100S a second time from another task, and the
4 hardware sockets are already used by 2 MQTT slots, NTP and DHCP. When merging upstream, guard its blocks in
`examples/simple_repeater/main.cpp` with `#if defined(ETHERNET_ENABLED) && !defined(WITH_MQTT_BRIDGE)`.

## Overview

The MQTT bridge implementation provides:
Expand Down
4 changes: 4 additions & 0 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,10 @@ void MyMesh::loop() {
#ifdef WITH_BRIDGE
// bridge.loop() is now handled by FreeRTOS task on Core 0 - no need to call it here
#endif
#if defined(WITH_MQTT_BRIDGE) && defined(ETHERNET_ENABLED)
// Ethernet bridge: network I/O runs in its own task; loop() only applies NTP time to the RTC
if (bridge) bridge->loop();
#endif

if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
mesh::Packet *pkt = createSelfAdvert();
Expand Down
16 changes: 14 additions & 2 deletions examples/simple_repeater/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#define USER_BTN_PRESSED LOW
#endif

#ifdef WITH_MQTT_BRIDGE
#if defined(WITH_MQTT_BRIDGE) && defined(ETHERNET_ENABLED)
#include <helpers/bridges/MQTTBridge.h>
#elif defined(WITH_MQTT_BRIDGE)
#include <WiFi.h>
#endif

Expand Down Expand Up @@ -95,7 +97,17 @@ void UITask::renderCurrScreen() {
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
_display->print(tmp);

#ifdef WITH_MQTT_BRIDGE
#if defined(WITH_MQTT_BRIDGE) && defined(ETHERNET_ENABLED)
// Display Ethernet IP address (cached by the bridge task; never touches the W5100S here)
uint8_t eth_ip[4];
MQTTBridge::getLocalIP(eth_ip);
if (eth_ip[0] != 0) {
_display->setCursor(0, 40);
_display->setColor(DisplayDriver::LIGHT);
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", eth_ip[0], eth_ip[1], eth_ip[2], eth_ip[3]);
_display->print(tmp);
}
#elif defined(WITH_MQTT_BRIDGE)
// Display IP address for MQTT bridge devices
if (WiFi.status() == WL_CONNECTED) {
IPAddress ip = WiFi.localIP();
Expand Down
2 changes: 2 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ build_flags = ${arduino_base.build_flags}
lib_deps =
${arduino_base.lib_deps}
https://github.com/oltaco/CustomLFS#0.2.2
; PsychicMqttClient (lib/) is ESP-IDF only; the LDF would otherwise pick it up via MQTTBridge.h
lib_ignore = PsychicMqttClient
; ----------------- RP2040 ---------------------

[rp2040_base]
Expand Down
13 changes: 10 additions & 3 deletions src/helpers/AlertReporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
#define ALERT_DEBUG_PRINTLN(...) do {} while (0)
#endif

// Uplink name used in the network fault alert texts
#ifdef ETHERNET_ENABLED
#define NET_ALERT_LABEL "Ethernet"
#else
#define NET_ALERT_LABEL "WiFi"
#endif

AlertReporter::AlertReporter()
: _prefs(nullptr), _mesh(nullptr), _callbacks(nullptr),
#ifdef WITH_MQTT_BRIDGE
Expand Down Expand Up @@ -236,9 +243,9 @@ void AlertReporter::onLoop(unsigned long now_ms) {
uint8_t reason = MQTTBridge::getLastWifiDisconnectReason();
char text[80];
if (reason != 0) {
snprintf(text, sizeof(text), "WiFi down %s (reason %u)", age, (unsigned)reason);
snprintf(text, sizeof(text), NET_ALERT_LABEL " down %s (reason %u)", age, (unsigned)reason);
} else {
snprintf(text, sizeof(text), "WiFi down %s", age);
snprintf(text, sizeof(text), NET_ALERT_LABEL " down %s", age);
}
if (sendChannel(text)) {
_wifi.state = FIRING;
Expand All @@ -253,7 +260,7 @@ void AlertReporter::onLoop(unsigned long now_ms) {
char age[16];
formatAge(total, age, sizeof(age));
char text[80];
snprintf(text, sizeof(text), "WiFi recovered after %s", age);
snprintf(text, sizeof(text), NET_ALERT_LABEL " recovered after %s", age);
sendChannel(text);
_wifi.state = OK;
}
Expand Down
12 changes: 12 additions & 0 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,13 +778,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
strcpy(reply, "ERR: clock cannot go backwards");
}
} else if (memcmp(command, "memory", 6) == 0) {
#ifdef ESP_PLATFORM
sprintf(reply, "Free: %d, Min: %d, Max: %d, Queue: %d, IntFree: %d, IntMax: %d, PSRAM: %d/%d",
ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(),
_callbacks->getQueueSize(),
(int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
(int)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
(int)heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
(int)heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
#elif defined(NRF52_PLATFORM)
sprintf(reply, "Heap: %d/%d used, Queue: %d",
dbgHeapUsed(), dbgHeapTotal(), _callbacks->getQueueSize());
#else
sprintf(reply, "Queue: %d", _callbacks->getQueueSize());
#endif
} else if (memcmp(command, "tls.bundletest ", 15) == 0) {
#ifdef ESP_PLATFORM
if (WiFi.status() != WL_CONNECTED) {
Expand Down Expand Up @@ -2070,6 +2077,10 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", _prefs->wifi_ssid);
} else if (memcmp(config, "wifi.pwd", 8) == 0) {
sprintf(reply, "> %s", _prefs->wifi_password);
#ifdef ETHERNET_ENABLED
} else if (memcmp(config, "eth.status", 10) == 0 || memcmp(config, "wifi.status", 11) == 0) {
MQTTBridge::formatNetStatusReply(reply, 160);
#else
} else if (memcmp(config, "wifi.status", 11) == 0) {
wl_status_t status = WiFi.status();
const char* status_str;
Expand Down Expand Up @@ -2123,6 +2134,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s (code: %d)", status_str, status);
#endif
}
#endif // ETHERNET_ENABLED
} else if (memcmp(config, "wifi.powersave", 14) == 0) {
uint8_t ps = _prefs->wifi_power_save;
const char* ps_name = (ps == 1) ? "none" : (ps == 2) ? "max" : "min";
Expand Down
53 changes: 31 additions & 22 deletions src/helpers/JWTHelper.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#ifdef WITH_MQTT_BRIDGE

#include "JWTHelper.h"
#include <ArduinoJson.h>
#include <SHA256.h>
#include <string.h>
#include <time.h>
#include "ed_25519.h"
#include "mbedtls/base64.h"

// Base64 URL encoding table (without padding)
static const char base64url_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
Expand Down Expand Up @@ -111,29 +113,35 @@ size_t JWTHelper::base64UrlEncode(const uint8_t* input, size_t inputLen, char* o
if (!input || !output || outputSize == 0) {
return 0;
}

size_t outlen = 0;
int ret = mbedtls_base64_encode((unsigned char*)output, outputSize - 1, &outlen, input, inputLen);

if (ret != 0) {

// Unpadded base64url output length (portable; no mbedTLS dependency so nRF52 builds can use it)
size_t outlen = (inputLen / 3) * 4 + ((inputLen % 3) ? (inputLen % 3) + 1 : 0);
if (outlen >= outputSize) {
return 0;
}

// Convert to base64 URL format in-place (replace + with -, / with _, remove padding =)
for (size_t i = 0; i < outlen; i++) {
if (output[i] == '+') {
output[i] = '-';
} else if (output[i] == '/') {
output[i] = '_';
}
}

// Remove padding '=' characters
while (outlen > 0 && output[outlen-1] == '=') {
outlen--;
}
output[outlen] = '\0';
return outlen;

size_t o = 0;
size_t i = 0;
while (i + 2 < inputLen) {
uint32_t v = ((uint32_t)input[i] << 16) | ((uint32_t)input[i + 1] << 8) | input[i + 2];
output[o++] = base64url_chars[(v >> 18) & 0x3F];
output[o++] = base64url_chars[(v >> 12) & 0x3F];
output[o++] = base64url_chars[(v >> 6) & 0x3F];
output[o++] = base64url_chars[v & 0x3F];
i += 3;
}
if (inputLen - i == 1) {
uint32_t v = (uint32_t)input[i] << 16;
output[o++] = base64url_chars[(v >> 18) & 0x3F];
output[o++] = base64url_chars[(v >> 12) & 0x3F];
} else if (inputLen - i == 2) {
uint32_t v = ((uint32_t)input[i] << 16) | ((uint32_t)input[i + 1] << 8);
output[o++] = base64url_chars[(v >> 18) & 0x3F];
output[o++] = base64url_chars[(v >> 12) & 0x3F];
output[o++] = base64url_chars[(v >> 6) & 0x3F];
}
output[o] = '\0';
return o;
}

size_t JWTHelper::createHeader(char* output, size_t outputSize) {
Expand Down Expand Up @@ -196,3 +204,4 @@ size_t JWTHelper::createPayload(
return base64UrlEncode((uint8_t*)jsonBuffer, len, output, outputSize);
}

#endif // WITH_MQTT_BRIDGE
6 changes: 5 additions & 1 deletion src/helpers/MQTTMessageBuilder.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#ifdef WITH_MQTT_BRIDGE

#include "MQTTMessageBuilder.h"
#include <ArduinoJson.h>
#include <cstring>
Expand Down Expand Up @@ -450,4 +452,6 @@ void MQTTMessageBuilder::packetToHex(mesh::Packet* packet, char* hex, size_t hex

// Convert serialized packet to hex
bytesToHex(raw_buf, raw_len, hex, hex_size);
}
}

#endif // WITH_MQTT_BRIDGE
7 changes: 7 additions & 0 deletions src/helpers/bridges/MQTTBridge.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#pragma once

#if defined(WITH_MQTT_BRIDGE) && defined(ETHERNET_ENABLED)
// Wired Ethernet (W5100S) variant with the same class name and API (nRF52 / RAK4631 + RAK13800)
#include "MQTTBridgeEthernet.h"
#else

#include "MeshCore.h"
#include "helpers/bridges/BridgeBase.h"
#include <PsychicMqttClient.h>
Expand Down Expand Up @@ -424,3 +429,5 @@ class MQTTBridge : public BridgeBase {
};

#endif

#endif // WITH_MQTT_BRIDGE && ETHERNET_ENABLED
Loading