* 'main' of https://github.com/xJARiD/MeshCore-EastMesh:
  docs: updated release notes for waev
  feat(mqtt): add Waev broker

# Conflicts:
#	release-notes.yml
This commit is contained in:
prabathbr
2026-06-26 12:21:21 +10:00
8 changed files with 86 additions and 13 deletions
+1
View File
@@ -149,6 +149,7 @@ uv run --group docs zensical build
- curated broker support for: - curated broker support for:
- `eastmesh-au` - `eastmesh-au`
- `meshmapper` - `meshmapper`
- `waev`
- `letsmesh-eu` (retired) - `letsmesh-eu` (retired)
- `letsmesh-us` (retired) - `letsmesh-us` (retired)
- WSS transport at `/mqtt` - WSS transport at `/mqtt`
+3
View File
@@ -67,6 +67,8 @@ No-argument `get` commands must be entered exactly as shown.
- `set mqtt.eastmesh-au on|off` - `set mqtt.eastmesh-au on|off`
- `get mqtt.meshmapper` - `get mqtt.meshmapper`
- `set mqtt.meshmapper on|off` - `set mqtt.meshmapper on|off`
- `get mqtt.waev`
- `set mqtt.waev on|off`
- `get mqtt.letsmesh-eu` (retired) - `get mqtt.letsmesh-eu` (retired)
- `set mqtt.letsmesh-eu on|off` (retired) - `set mqtt.letsmesh-eu on|off` (retired)
- `get mqtt.letsmesh-us` (retired) - `get mqtt.letsmesh-us` (retired)
@@ -89,6 +91,7 @@ Notes:
- new observer installs default `mqtt.iata` to `UNSET` - new observer installs default `mqtt.iata` to `UNSET`
- a maximum of two MQTT brokers can be enabled at once - a maximum of two MQTT brokers can be enabled at once
- `meshmapper` is the curated global broker (`wss://mqtt.meshmapper.net:443/mqtt`); like `eastmesh-au` it uses WSS, verified TLS, and MeshCore JWT auth with the broker host as the token audience - `meshmapper` is the curated global broker (`wss://mqtt.meshmapper.net:443/mqtt`); like `eastmesh-au` it uses WSS, verified TLS, and MeshCore JWT auth with the broker host as the token audience
- `waev` is a curated broker (`wss://mqtt.waev.app:443/mqtt`); like `eastmesh-au` it uses WSS, verified TLS, and MeshCore JWT auth with the broker host as the token audience, but enforces a 1-hour auth token lifetime (the firmware mints a shorter-lived token for it automatically)
- `letsmesh-eu` and `letsmesh-us` are retired (LetsMesh is no longer maintained) and off by default; any saved selections are cleared once on upgrade, though the endpoints stay selectable for legacy use - `letsmesh-eu` and `letsmesh-us` are retired (LetsMesh is no longer maintained) and off by default; any saved selections are cleared once on upgrade, though the endpoints stay selectable for legacy use
- if `mqtt.iata` is `UNSET`, enabled MQTT brokers will not connect - if `mqtt.iata` is `UNSET`, enabled MQTT brokers will not connect
- custom MQTT uses the configured username and password, not JWT authentication - custom MQTT uses the configured username and password, not JWT authentication
+8
View File
@@ -2454,6 +2454,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x01) ? "on" : "off"); sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x01) ? "on" : "off");
} else if (strcmp(command, "get mqtt.meshmapper") == 0) { } else if (strcmp(command, "get mqtt.meshmapper") == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x10) ? "on" : "off"); sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x10) ? "on" : "off");
} else if (strcmp(command, "get mqtt.waev") == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x20) ? "on" : "off");
} else if (strcmp(command, "get mqtt.letsmesh-eu") == 0 || strcmp(command, "get mqtt.letsmesh.eu") == 0) { } else if (strcmp(command, "get mqtt.letsmesh-eu") == 0 || strcmp(command, "get mqtt.letsmesh.eu") == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x02) ? "on" : "off"); sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x02) ? "on" : "off");
} else if (strcmp(command, "get mqtt.letsmesh-us") == 0 || strcmp(command, "get mqtt.letsmesh.us") == 0) { } else if (strcmp(command, "get mqtt.letsmesh-us") == 0 || strcmp(command, "get mqtt.letsmesh.us") == 0) {
@@ -2512,6 +2514,12 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
} else { } else {
strcpy(reply, "Err - max 2 mqtt brokers"); strcpy(reply, "Err - max 2 mqtt brokers");
} }
} else if (memcmp(command, "set mqtt.waev ", 14) == 0) {
if (mqtt.setEndpointEnabled(0x20, memcmp(&command[14], "on", 2) == 0)) {
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - max 2 mqtt brokers");
}
} else if (memcmp(command, "set mqtt.letsmesh-eu ", 21) == 0 || memcmp(command, "set mqtt.letsmesh.eu ", 21) == 0) { } else if (memcmp(command, "set mqtt.letsmesh-eu ", 21) == 0 || memcmp(command, "set mqtt.letsmesh.eu ", 21) == 0) {
if (mqtt.setEndpointEnabled(0x02, memcmp(&command[21], "on", 2) == 0)) { if (mqtt.setEndpointEnabled(0x02, memcmp(&command[21], "on", 2) == 0)) {
strcpy(reply, "OK"); strcpy(reply, "OK");
+49
View File
@@ -242,6 +242,30 @@ releases:
- "`get mqtt.status` output changed from per-broker fields to `p:`/`s:` broker slots; anything parsing the previous format must be updated." - "`get mqtt.status` output changed from per-broker fields to `p:`/`s:` broker slots; anything parsing the previous format must be updated."
- "Saved LetsMesh EU/US broker selections are cleared once on upgrade; re-enable a broker such as MeshMapper if a second uplink is required." - "Saved LetsMesh EU/US broker selections are cleared once on upgrade; re-enable a broker such as MeshMapper if a second uplink is required."
- track: observer-eastmesh
version: "2026.6.5"
tag: "observer-eastmesh-v2026.6.5"
date: "2026-06-26"
previous_version: "2026.6.4"
summary: "Adds the Waev curated MQTT broker and makes the Custom MQTT settings panel full width when Custom is selected."
changes:
- type: added
area: mqtt
text: "Added the `waev` curated broker (`wss://mqtt.waev.app:443/mqtt`) using WSS, verified TLS, and MeshCore JWT authentication with the broker host as the token audience."
- type: added
area: cli
text: "Added `get mqtt.waev` and `set mqtt.waev on|off` to toggle the Waev broker."
- type: added
area: mqtt
text: "Added per-broker JWT token lifetimes so Waev receives a 1-hour token to match its shorter maximum token lifetime."
- type: fixed
area: web
text: "Made the Custom MQTT settings panel span the full width of the MQTT Servers card when Custom is selected."
- type: docs
area: docs
text: "Documented the Waev broker in the README and custom CLI guides."
breaking_changes: []
- track: observer-eastmesh-bridge-espnow - track: observer-eastmesh-bridge-espnow
version: "2026.5.1" version: "2026.5.1"
tag: "observer-eastmesh-bridge-espnow-v2026.5.1" tag: "observer-eastmesh-bridge-espnow-v2026.5.1"
@@ -395,6 +419,7 @@ releases:
- "`get mqtt.status` output changed from per-broker fields to `p:`/`s:` broker slots; anything parsing the previous format must be updated." - "`get mqtt.status` output changed from per-broker fields to `p:`/`s:` broker slots; anything parsing the previous format must be updated."
- "Saved LetsMesh EU/US broker selections are cleared once on upgrade; re-enable a broker such as MeshMapper if a second uplink is required." - "Saved LetsMesh EU/US broker selections are cleared once on upgrade; re-enable a broker such as MeshMapper if a second uplink is required."
<<<<<<< HEAD
- track: observer-eastmesh-bridge-mqtt - track: observer-eastmesh-bridge-mqtt
version: "2026.7.0" version: "2026.7.0"
tag: "observer-eastmesh-bridge-mqtt-v2026.7.0" tag: "observer-eastmesh-bridge-mqtt-v2026.7.0"
@@ -417,4 +442,28 @@ releases:
- type: added - type: added
area: boards area: boards
text: "Added `Xiao_S3_WIO_repeater_observer_mqtt_bridge` as the first supported observer MQTT bridge target." text: "Added `Xiao_S3_WIO_repeater_observer_mqtt_bridge` as the first supported observer MQTT bridge target."
=======
- track: observer-eastmesh-bridge-espnow
version: "2026.6.5"
tag: "observer-eastmesh-bridge-espnow-v2026.6.5"
date: "2026-06-26"
previous_version: "2026.6.4"
summary: "Adds the Waev curated MQTT broker and makes the Custom MQTT settings panel full width in Observer ESP-NOW bridge builds."
changes:
- type: added
area: mqtt
text: "Added the `waev` curated broker (`wss://mqtt.waev.app:443/mqtt`) using WSS, verified TLS, and MeshCore JWT authentication with the broker host as the token audience."
- type: added
area: cli
text: "Added `get mqtt.waev` and `set mqtt.waev on|off` to toggle the Waev broker."
- type: added
area: mqtt
text: "Added per-broker JWT token lifetimes so Waev receives a 1-hour token to match its shorter maximum token lifetime."
- type: fixed
area: web
text: "Made the Custom MQTT settings panel span the full width of the MQTT Servers card when Custom is selected."
- type: docs
area: docs
text: "Documented the Waev broker in the README and custom CLI guides."
>>>>>>> cdf2cc6fa283615a04c488ca3ad324a33b72966d
breaking_changes: [] breaking_changes: []
+1 -1
View File
@@ -80,7 +80,7 @@ bool MQTTPrefsStore::load(FILESYSTEM* fs, MQTTPrefs& prefs) {
prefs.custom_transport = 0; prefs.custom_transport = 0;
} }
prefs.status_interval_ms = kFixedStatusIntervalMs; prefs.status_interval_ms = kFixedStatusIntervalMs;
prefs.enabled_mask &= 0x1F; prefs.enabled_mask &= 0x3F;
if (!prefs.brokers_migrated) { if (!prefs.brokers_migrated) {
// LetsMesh is retired. Clear any saved EU/US selections once so dead brokers // LetsMesh is retired. Clear any saved EU/US selections once so dead brokers
// stop retrying; the endpoints remain re-selectable afterwards. // stop retrying; the endpoints remain re-selectable afterwards.
+14 -9
View File
@@ -128,14 +128,16 @@ void logMqttMemorySnapshot(const char*, const char* = nullptr) {
} }
const MQTTUplink::BrokerSpec MQTTUplink::kBrokerSpecs[kBrokerCount] = { const MQTTUplink::BrokerSpec MQTTUplink::kBrokerSpecs[kBrokerCount] = {
{"eastmesh-au", "eastmesh-au", "mqtt2.eastmesh.au", "wss://mqtt2.eastmesh.au:443/mqtt", kEastmeshBit, false}, {"eastmesh-au", "eastmesh-au", "mqtt2.eastmesh.au", "wss://mqtt2.eastmesh.au:443/mqtt", kEastmeshBit, false, 0},
{"meshmapper", "meshmapper", "mqtt.meshmapper.net", "wss://mqtt.meshmapper.net:443/mqtt", kMeshmapperBit, false}, {"meshmapper", "meshmapper", "mqtt.meshmapper.net", "wss://mqtt.meshmapper.net:443/mqtt", kMeshmapperBit, false, 0},
// waev enforces a shorter max token lifetime than the curated default; match its documented config.
{"waev", "waev", "mqtt.waev.app", "wss://mqtt.waev.app:443/mqtt", kWaevBit, false, 3600},
// Retired: LetsMesh is no longer maintained. Kept selectable for legacy nodes; new nodes should use meshmapper. // Retired: LetsMesh is no longer maintained. Kept selectable for legacy nodes; new nodes should use meshmapper.
{"letsmesh-eu", "letsmesh-eu", "mqtt-eu-v1.letsmesh.net", "wss://mqtt-eu-v1.letsmesh.net:443/mqtt", {"letsmesh-eu", "letsmesh-eu", "mqtt-eu-v1.letsmesh.net", "wss://mqtt-eu-v1.letsmesh.net:443/mqtt",
kLetsmeshEuBit, false}, kLetsmeshEuBit, false, 0},
{"letsmesh-us", "letsmesh-us", "mqtt-us-v1.letsmesh.net", "wss://mqtt-us-v1.letsmesh.net:443/mqtt", {"letsmesh-us", "letsmesh-us", "mqtt-us-v1.letsmesh.net", "wss://mqtt-us-v1.letsmesh.net:443/mqtt",
kLetsmeshUsBit, false}, kLetsmeshUsBit, false, 0},
{"custom", "custom", nullptr, nullptr, kCustomBit, true}, {"custom", "custom", nullptr, nullptr, kCustomBit, true, 0},
}; };
MQTTUplink::MQTTUplink(mesh::RTCClock& rtc, mesh::LocalIdentity& identity) MQTTUplink::MQTTUplink(mesh::RTCClock& rtc, mesh::LocalIdentity& identity)
@@ -202,7 +204,8 @@ const char* MQTTUplink::brokerCaCert(const BrokerSpec& spec) {
if (spec.bit == kEastmeshBit || spec.bit == kMeshmapperBit) { if (spec.bit == kEastmeshBit || spec.bit == kMeshmapperBit) {
return mqtt_ca_certs::kEastmeshIsrgRootX1Pem; // Let's Encrypt ISRG Root X1 return mqtt_ca_certs::kEastmeshIsrgRootX1Pem; // Let's Encrypt ISRG Root X1
} }
return mqtt_ca_certs::kLetsmeshWe1Pem; // waev (mqtt.waev.app) presents a Google Trust Services WE1 chain, same issuer as LetsMesh.
return mqtt_ca_certs::kLetsmeshWe1Pem; // Google Trust Services WE1
} }
const char* MQTTUplink::brokerHost(const BrokerState& broker) const { const char* MQTTUplink::brokerHost(const BrokerState& broker) const {
@@ -572,7 +575,8 @@ bool MQTTUplink::refreshToken(BrokerState& broker) {
} }
} }
time_t expires_at = now + kTokenLifetimeSecs; time_t ttl = broker.spec->token_ttl_secs != 0 ? static_cast<time_t>(broker.spec->token_ttl_secs) : kTokenLifetimeSecs;
time_t expires_at = now + ttl;
const char* owner = _prefs.owner_public_key[0] ? _prefs.owner_public_key : nullptr; const char* owner = _prefs.owner_public_key[0] ? _prefs.owner_public_key : nullptr;
const char* email = _prefs.owner_email[0] ? _prefs.owner_email : nullptr; const char* email = _prefs.owner_email[0] ? _prefs.owner_email : nullptr;
if (!JWTHelper::createAuthToken(*_identity, brokerHost(broker), now, expires_at, broker.token, kBrokerTokenSize, if (!JWTHelper::createAuthToken(*_identity, brokerHost(broker), now, expires_at, broker.token, kBrokerTokenSize,
@@ -583,8 +587,9 @@ bool MQTTUplink::refreshToken(BrokerState& broker) {
return false; return false;
} }
broker.token_expires_at = expires_at; broker.token_expires_at = expires_at;
MQTT_LOG("%s token ready exp=%lu owner=%s email=%s", broker.spec->label, MQTT_LOG("%s token ready exp=%lu ttl=%ld owner=%s email=%s", broker.spec->label,
static_cast<unsigned long>(expires_at), owner != nullptr ? "yes" : "no", email != nullptr ? "yes" : "no"); static_cast<unsigned long>(expires_at), static_cast<long>(ttl),
owner != nullptr ? "yes" : "no", email != nullptr ? "yes" : "no");
return true; return true;
} }
+6 -2
View File
@@ -89,6 +89,9 @@ private:
const char* uri; const char* uri;
uint8_t bit; uint8_t bit;
bool custom; bool custom;
// Per-broker JWT lifetime in seconds; 0 = use the default kTokenLifetimeSecs. waev enforces a
// shorter max token lifetime than the curated default, so it gets its own (matches its config).
uint32_t token_ttl_secs;
}; };
struct BrokerState { struct BrokerState {
@@ -131,9 +134,10 @@ private:
static constexpr uint8_t kLetsmeshUsBit = 0x04; static constexpr uint8_t kLetsmeshUsBit = 0x04;
static constexpr uint8_t kCustomBit = 0x08; static constexpr uint8_t kCustomBit = 0x08;
static constexpr uint8_t kMeshmapperBit = 0x10; static constexpr uint8_t kMeshmapperBit = 0x10;
static constexpr uint8_t kBrokerMask = 0x1F; static constexpr uint8_t kWaevBit = 0x20;
static constexpr uint8_t kBrokerMask = 0x3F;
static constexpr uint8_t kMaxEnabledBrokers = 2; static constexpr uint8_t kMaxEnabledBrokers = 2;
static constexpr size_t kBrokerCount = 5; static constexpr size_t kBrokerCount = 6;
static const BrokerSpec kBrokerSpecs[kBrokerCount]; static const BrokerSpec kBrokerSpecs[kBrokerCount];
static bool isUnsetIataValue(const char* iata); static bool isUnsetIataValue(const char* iata);
static const char* brokerCaCert(const BrokerSpec& spec); static const char* brokerCaCert(const BrokerSpec& spec);
+4 -1
View File
@@ -977,12 +977,13 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
</div> </div>
<input id="mqttEastmeshAu" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true"> <input id="mqttEastmeshAu" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true">
<input id="mqttMeshmapper" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true"> <input id="mqttMeshmapper" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true">
<input id="mqttWaev" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true">
<input id="mqttLetsmeshEu" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true"> <input id="mqttLetsmeshEu" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true">
<input id="mqttLetsmeshUs" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true"> <input id="mqttLetsmeshUs" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true">
<input id="mqttCustom" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true"> <input id="mqttCustom" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true">
<div class="broker-group wide" id="mqttCustomConfig" style="display:none"> <div class="broker-group wide" id="mqttCustomConfig" style="display:none">
<div class="broker-group-title">Custom MQTT settings</div> <div class="broker-group-title">Custom MQTT settings</div>
<div class="broker-grid"> <div class="broker-grid single">
<div class="broker-card"> <div class="broker-card">
<div class="field-card"> <div class="field-card">
<label class="label" for="mqttCustomEndpoint">Host:Port</label> <label class="label" for="mqttCustomEndpoint">Host:Port</label>
@@ -2732,6 +2733,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
const MQTT_BROKERS = [ const MQTT_BROKERS = [
{ key:"eastmesh-au", input:"mqttEastmeshAu", label:"EastMesh AU", set:"set mqtt.eastmesh-au" }, { key:"eastmesh-au", input:"mqttEastmeshAu", label:"EastMesh AU", set:"set mqtt.eastmesh-au" },
{ key:"meshmapper", input:"mqttMeshmapper", label:"MeshMapper", set:"set mqtt.meshmapper" }, { key:"meshmapper", input:"mqttMeshmapper", label:"MeshMapper", set:"set mqtt.meshmapper" },
{ key:"waev", input:"mqttWaev", label:"Waev", set:"set mqtt.waev" },
{ key:"custom", input:"mqttCustom", label:"Custom", set:"set mqtt.custom", custom:true }, { key:"custom", input:"mqttCustom", label:"Custom", set:"set mqtt.custom", custom:true },
{ key:"letsmesh-eu", input:"mqttLetsmeshEu", label:"LetsMesh EU (retired)", set:"set mqtt.letsmesh-eu" }, { key:"letsmesh-eu", input:"mqttLetsmeshEu", label:"LetsMesh EU (retired)", set:"set mqtt.letsmesh-eu" },
{ key:"letsmesh-us", input:"mqttLetsmeshUs", label:"LetsMesh US (retired)", set:"set mqtt.letsmesh-us" } { key:"letsmesh-us", input:"mqttLetsmeshUs", label:"LetsMesh US (retired)", set:"set mqtt.letsmesh-us" }
@@ -3261,6 +3263,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
() => loadField("get mqtt.email", "mqttEmail", null, quiet), () => loadField("get mqtt.email", "mqttEmail", null, quiet),
() => loadBrokerState("get mqtt.eastmesh-au", "mqttEastmeshAu", quiet), () => loadBrokerState("get mqtt.eastmesh-au", "mqttEastmeshAu", quiet),
() => loadBrokerState("get mqtt.meshmapper", "mqttMeshmapper", quiet), () => loadBrokerState("get mqtt.meshmapper", "mqttMeshmapper", quiet),
() => loadBrokerState("get mqtt.waev", "mqttWaev", quiet),
() => loadBrokerState("get mqtt.letsmesh-eu", "mqttLetsmeshEu", quiet), () => loadBrokerState("get mqtt.letsmesh-eu", "mqttLetsmeshEu", quiet),
() => loadBrokerState("get mqtt.letsmesh-us", "mqttLetsmeshUs", quiet), () => loadBrokerState("get mqtt.letsmesh-us", "mqttLetsmeshUs", quiet),
() => loadBrokerState("get mqtt.custom", "mqttCustom", quiet), () => loadBrokerState("get mqtt.custom", "mqttCustom", quiet),