Removed EastMesh MQTT broker support.

It's intended for Eastern Australia repeaters only.  Using original EastMesh
firmware is prefered in this case.
This commit is contained in:
Valentin V. Bartenev
2026-04-28 00:52:26 +03:00
vanhempi 64cebbc750
commit 46b0df7a4b
8 muutettua tiedostoa jossa 64 lisäystä ja 413 poistoa
+9 -81
Näytä tiedosto
@@ -1,64 +1,25 @@
# MeshCore-EastMesh-MeshCoreTel
# MeshCoreTel Firmware
This is a fork of MeshCore-EastMesh firmware with MeshCoreTel broker support.
Original firmware (credits to Jared Dohrman, Scott Powell / Ripple Radios and the MeshCore contributors):
- <https://github.com/xJARiD/MeshCore-EastMesh>
This is a fork of MeshCore-EastMesh firmware with MeshCoreTel broker support instead of EastMesh.
MeshCoreTel:
- <https://meshcoretel.ru/>
EastMesh firmware builds for MeshCore, with MQTT repeaters, WiFi companions, and simple release downloads for supported boards.
Original firmware (credits to Jared Dohrman, Scott Powell / Ripple Radios and the MeshCore contributors):
This repository keeps the upstream MeshCore firmware intact and adds an EastMesh layer on top for packaging, release automation, WiFi-enabled companion builds, and MQTT-enabled repeater builds.
- <https://github.com/xJARiD/MeshCore-EastMesh>
Additinally, the MeshCoreTel broker added in this fork.
MeshCoreTel firmware builds for MeshCore, with MQTT repeaters, WiFi companions, and simple release downloads for supported boards.
## What This Repo Adds
This repository keeps the upstream MeshCore firmware intact and adds an MeshCoreTel layer on top for packaging, release automation, WiFi-enabled companion builds, and MQTT-enabled repeater builds.
- `*_repeater_mqtt` firmware targets with:
- native WiFi
- MQTT over WSS with JWT auth or over plain TCP (for MeshCoreTel support)
- optional local HTTPS config panel on supported ESP32 targets
- `*_companion_radio_wifi` firmware targets for WiFi-connected companion devices
- EastMesh-specific release workflows and versioning on top of upstream MeshCore releases
- docs and release guidance for EastMesh users instead of the full upstream MeshCore docs set
## Status
I've tested it only with my Heltec V4.
## Key Files
- [`build.sh`](./build.sh)
- main local build wrapper
- injects `FIRMWARE_VERSION`, `CLIENT_VERSION`, and EastMesh release metadata
- [`pyproject.toml`](./pyproject.toml)
- Python tooling and docs dependencies
- [`platformio.ini`](./platformio.ini)
- root PlatformIO config and ESP32 helper scripts
- [`variants/eastmesh_mqtt/platformio.ini`](./variants/eastmesh_mqtt/platformio.ini)
- shared EastMesh MQTT repeater env definitions
- [`examples/simple_repeater/MyMesh.cpp`](./examples/simple_repeater/MyMesh.cpp)
- repeater CLI wiring, MQTT command surface, and web allowlist integration
- [`src/helpers/mqtt/MQTTUplink.cpp`](./src/helpers/mqtt/MQTTUplink.cpp)
- MQTT uplink implementation, HTTPS web panel, WSS/JWT handling, and repeater WiFi control
- [`examples/companion_radio`](./examples/companion_radio)
- companion firmware implementation
- [`RELEASE.md`](./RELEASE.md)
- tag formats and release workflow behavior
- [`docs/`](./docs)
- EastMesh-focused docs published to GitHub Pages
## Key EastMesh Features
## Key MeshCoreTel Features
### MQTT Repeater Additions
- hardcoded broker support for:
- `meshcoretel`
- `eastmesh-au`
- `letsmesh-eu`
- `letsmesh-us`
- WSS transport at `/mqtt`
@@ -105,46 +66,13 @@ These rescue commands are only available after entering `CLI Rescue`:
- `set wifi.pwd <password>`
- `set wifi.powersaving none|min|max`
## Active GitHub Workflows
## Status
- `.github/workflows/build-companion-wifi-firmwares.yml`
- `.github/workflows/build-repeater-mqtt-firmwares.yml`
- `.github/workflows/pr-build-check.yml`
- `.github/workflows/push-build-check.yml`
- `.github/workflows/github-pages.yml`
The current release workflows intentionally focus only on:
- `companion-wifi`
- `repeater-mqtt`
## Release Tags
Current release tags are:
```bash
git tag companion-wifi-v1.14.1
git tag repeater-mqtt-eastmesh-v1.0.1
```
Companion WiFi uses the upstream MeshCore version in the tag.
Repeater MQTT uses:
- `OFFICIAL_MESHCORE_VERSION` from GitHub Actions variables as `FIRMWARE_VERSION`
- the EastMesh tag version as `EASTMESH_VERSION`
See [RELEASE.md](./RELEASE.md) for the full release flow.
Tested with my Heltec V4 OLED.
## Documentation
Published docs site:
- <https://xjarid.github.io/MeshCore-EastMesh/>
Current docs pages:
- [Home](./docs/index.md)
- [Download and Flash Releases](./docs/releases.md)
- [Build Locally With uv](./docs/local-builds.md)
- [Custom CLI Commands](./docs/custom-cli.md)
+3 -7
Näytä tiedosto
@@ -1,6 +1,6 @@
# Custom CLI Commands
This page covers the EastMesh-specific CLI commands added in this repository.
This page covers the MeshCoreTel-specific CLI commands added in this repository.
It does not try to repeat the full upstream MeshCore CLI surface.
@@ -38,11 +38,8 @@ These commands are available on `*_repeater_mqtt` firmware targets.
### MQTT Endpoints
- `set mqtt.meshcoretel on|off`
- `get mqtt.meshcoretel`
- `set mqtt.letsmesh-us on|off`
- `get mqtt.eastmesh-au`
- `set mqtt.eastmesh-au on|off`
- `set mqtt.meshcoretel on|off`
- `get mqtt.letsmesh-eu`
- `set mqtt.letsmesh-eu on|off`
- `get mqtt.letsmesh-us`
@@ -52,11 +49,10 @@ Notes:
- new repeater MQTT installs default `mqtt.iata` to `UNSET`
- `letsmesh-eu` and `letsmesh-us` remain off by default unless already configured in saved prefs
- if `mqtt.iata` is `UNSET`, `meshcoretel`, `eastmesh-au`, `letsmesh-eu`, and `letsmesh-us` will not connect even if they are toggled on
- if `mqtt.iata` is `UNSET`, `meshcoretel`, `letsmesh-eu`, and `letsmesh-us` will not connect even if they are toggled on
Legacy dotted aliases are also accepted:
- `mqtt.eastmesh.au`
- `mqtt.letsmesh.eu`
- `mqtt.letsmesh.us`
+2 -10
Näytä tiedosto
@@ -2302,14 +2302,12 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
sprintf(reply, "> %s", mqtt.isRawEnabled() ? "on" : "off");
} else if (memcmp(command, "get mqtt.tx", 11) == 0) {
sprintf(reply, "> %s", mqtt.isTxEnabled() ? "on" : "off");
} else if (memcmp(command, "get mqtt.eastmesh-au", 20) == 0 || memcmp(command, "get mqtt.eastmesh.au", 20) == 0) {
} else if (memcmp(command, "get mqtt.meshcoretel", 21) == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x01) ? "on" : "off");
} else if (memcmp(command, "get mqtt.letsmesh-eu", 21) == 0 || memcmp(command, "get mqtt.letsmesh.eu", 21) == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x02) ? "on" : "off");
} else if (memcmp(command, "get mqtt.letsmesh-us", 21) == 0 || memcmp(command, "get mqtt.letsmesh.us", 21) == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x04) ? "on" : "off");
} else if (memcmp(command, "get mqtt.meshcoretel", 20) == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x08) ? "on" : "off");
} else if (memcmp(command, "set mqtt.tx ", 12) == 0) {
mqtt.setTxEnabled(memcmp(&command[12], "on", 2) == 0);
strcpy(reply, "OK");
@@ -2340,7 +2338,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
} else if (memcmp(command, "set mqtt.status ", 16) == 0) {
mqtt.setStatusEnabled(memcmp(&command[16], "on", 2) == 0);
strcpy(reply, "OK");
} else if (memcmp(command, "set mqtt.eastmesh-au ", 21) == 0 || memcmp(command, "set mqtt.eastmesh.au ", 21) == 0) {
} else if (memcmp(command, "set mqtt.meshcoretel ", 21) == 0) {
if (mqtt.setEndpointEnabled(0x01, memcmp(&command[21], "on", 2) == 0)) {
strcpy(reply, "OK");
} else {
@@ -2358,12 +2356,6 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
} else {
strcpy(reply, "Err - max 2 mqtt brokers");
}
} else if (memcmp(command, "set mqtt.meshcoretel ", 21) == 0) {
if (mqtt.setEndpointEnabled(0x08, memcmp(&command[21], "on", 2) == 0)) {
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - max 2 mqtt brokers");
}
#endif
} else{
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
-86
Näytä tiedosto
@@ -2,41 +2,6 @@
namespace mqtt_ca_certs {
// EastMesh currently presents a Let's Encrypt E7-issued certificate.
static const char kEastmeshIsrgRootX1Pem[] =
R"PEM(-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)PEM";
// LetsMesh currently presents a certificate issued by Google Trust Services WE1.
static const char kLetsmeshWe1Pem[] =
R"PEM(-----BEGIN CERTIFICATE-----
@@ -58,55 +23,4 @@ sQIwJonMaAFi54mrfhfoFNZEfuNMSQ6/bIBiNLiyoX46FohQvKeIoJ99cx7sUkFN
-----END CERTIFICATE-----
)PEM";
static const char kCombinedPem[] =
R"PEM(-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICnzCCAiWgAwIBAgIQf/MZd5csIkp2FV0TttaF4zAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIwMTQw
MDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZp
Y2VzMQwwCgYDVQQDEwNXRTEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARvzTr+
Z1dHTCEDhUDCR127WEcPQMFcF4XGGTfn1XzthkubgdnXGhOlCgP4mMTG6J7/EFmP
LCaY9eYmJbsPAvpWo4H+MIH7MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggr
BgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU
kHeSNWfE/6jMqeZ72YB5e8yT+TgwHwYDVR0jBBgwFoAUgEzW63T/STaj1dj8tT7F
avCUHYwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAChhhodHRwOi8vaS5wa2ku
Z29vZy9yNC5jcnQwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2MucGtpLmdvb2cv
ci9yNC5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwCgYIKoZIzj0EAwMDaAAwZQIx
AOcCq1HW90OVznX+0RGU1cxAQXomvtgM8zItPZCuFQ8jSBJSjz5keROv9aYsAm5V
sQIwJonMaAFi54mrfhfoFNZEfuNMSQ6/bIBiNLiyoX46FohQvKeIoJ99cx7sUkFN
7uJW
-----END CERTIFICATE-----
)PEM";
} // namespace mqtt_ca_certs
+1 -1
Näytä tiedosto
@@ -67,7 +67,7 @@ bool MQTTPrefsStore::load(FILESYSTEM* fs, MQTTPrefs& prefs) {
StrHelper::strncpy(prefs.iata, MQTT_DEFAULT_IATA, sizeof(prefs.iata));
}
prefs.status_interval_ms = kFixedStatusIntervalMs;
prefs.enabled_mask &= 0x0F;
prefs.enabled_mask &= 0x07;
return true;
}
+14 -16
Näytä tiedosto
@@ -17,15 +17,15 @@
#include <time.h>
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.15.0 (VBart EastMesh)"
#define FIRMWARE_VERSION "v1.15.0 (VBart MeshCoreTel)"
#endif
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "20 Mar 2026"
#define FIRMWARE_BUILD_DATE "27 Apr 2026"
#endif
#ifndef CLIENT_VERSION
#define CLIENT_VERSION "eastmesh-repeater-mqtt"
#define CLIENT_VERSION "meshcoretel-repeater-mqtt"
#endif
#ifndef MQTT_DEBUG
@@ -95,15 +95,13 @@ void logMqttMemorySnapshot(const char*, const char* = nullptr) {
}
const MQTTUplink::BrokerSpec MQTTUplink::kBrokerSpecs[4] = {
{"eastmesh-au", "eastmesh-au", "mqtt2.eastmesh.au", "wss://mqtt2.eastmesh.au:443/mqtt",
kEastmeshBit, false, nullptr, nullptr},
const MQTTUplink::BrokerSpec MQTTUplink::kBrokerSpecs[3] = {
{"meshcoretel", "meshcoretel", "meshcoretel.ru", "mqtt://meshcoretel.ru:1883",
kMeshcoretelBit, true, "meshcore", "meshcore"},
{"letsmesh-eu", "letsmesh-eu", "mqtt-eu-v1.letsmesh.net", "wss://mqtt-eu-v1.letsmesh.net:443/mqtt",
kLetsmeshEuBit, false, nullptr, nullptr},
{"letsmesh-us", "letsmesh-us", "mqtt-us-v1.letsmesh.net", "wss://mqtt-us-v1.letsmesh.net:443/mqtt",
kLetsmeshUsBit, false, nullptr, nullptr},
{"meshcoretel", "meshcoretel", "meshcoretel.ru", "mqtt://meshcoretel.ru:1883",
kMeshcoretelBit, true, "meshcore", "meshcore"},
};
MQTTUplink::MQTTUplink(mesh::RTCClock& rtc, mesh::LocalIdentity& identity)
@@ -112,7 +110,7 @@ MQTTUplink::MQTTUplink(mesh::RTCClock& rtc, mesh::LocalIdentity& identity)
{
memset(_device_id, 0, sizeof(_device_id));
MQTTPrefsStore::setDefaults(_prefs);
for (size_t i = 0; i < 4; ++i) {
for (size_t i = 0; i < 3; ++i) {
memset(&_brokers[i], 0, sizeof(_brokers[i]));
_brokers[i].spec = &kBrokerSpecs[i];
}
@@ -128,7 +126,7 @@ bool MQTTUplink::savePrefs() {
}
bool MQTTUplink::hasEnabledBroker() const {
return (_prefs.enabled_mask & 0x0F) != 0;
return (_prefs.enabled_mask & 0x07) != 0;
}
bool MQTTUplink::isUnsetIataValue(const char* iata) {
@@ -676,7 +674,7 @@ void MQTTUplink::ensureBroker(BrokerState& broker, bool allow_new_connect) {
cfg.broker.address.port = 443;
cfg.broker.address.transport = MQTT_TRANSPORT_OVER_WSS;
cfg.broker.address.path = "/mqtt";
cfg.broker.verification.certificate = mqtt_ca_certs::kCombinedPem;
cfg.broker.verification.certificate = mqtt_ca_certs::kLetsmeshWe1Pem;
cfg.credentials.authentication.password = broker.token;
}
cfg.credentials.username = broker.username;
@@ -700,7 +698,7 @@ void MQTTUplink::ensureBroker(BrokerState& broker, bool allow_new_connect) {
cfg.port = 443;
cfg.transport = MQTT_TRANSPORT_OVER_WSS;
cfg.path = "/mqtt";
cfg.cert_pem = mqtt_ca_certs::kCombinedPem;
cfg.cert_pem = mqtt_ca_certs::kLetsmeshWe1Pem;
cfg.password = broker.token;
}
cfg.username = broker.username;
@@ -741,7 +739,7 @@ void MQTTUplink::ensureBroker(BrokerState& broker, bool allow_new_connect) {
void MQTTUplink::begin(FILESYSTEM* fs) {
_fs = fs;
MQTTPrefsStore::load(_fs, _prefs);
uint8_t normalized_mask = normalizeEnabledMask(_prefs.enabled_mask & 0x0F);
uint8_t normalized_mask = normalizeEnabledMask(_prefs.enabled_mask & 0x07);
if (normalized_mask != _prefs.enabled_mask) {
_prefs.enabled_mask = normalized_mask;
savePrefs();
@@ -885,16 +883,16 @@ void MQTTUplink::formatStatusReply(char* reply, size_t reply_size) const {
return "retry";
};
snprintf(reply, reply_size, "> wifi:%s ntp:%s iata:%s meshcoretel:%s eastmesh-au:%s letsmesh-eu:%s letsmesh-us:%s status:%s tx:%s",
snprintf(reply, reply_size, "> wifi:%s ntp:%s iata:%s meshcoretel:%s letsmesh-eu:%s letsmesh-us:%s status:%s tx:%s",
(_network != nullptr && _network->isWifiConnected()) ? "up" : "down",
(_network != nullptr && _network->hasTimeSync()) ? "up" : "wait",
_prefs.iata,
broker_state(kMeshcoretelBit), broker_state(kEastmeshBit), broker_state(kLetsmeshEuBit), broker_state(kLetsmeshUsBit),
broker_state(kMeshcoretelBit), broker_state(kLetsmeshEuBit), broker_state(kLetsmeshUsBit),
_prefs.status_enabled ? "on" : "off", _prefs.tx_enabled ? "on" : "off");
}
bool MQTTUplink::setEndpointEnabled(uint8_t bit, bool enabled) {
uint8_t next_mask = _prefs.enabled_mask & 0x0F;
uint8_t next_mask = _prefs.enabled_mask & 0x07;
if (enabled) {
next_mask = normalizeEnabledMask(next_mask | bit);
if ((next_mask & bit) == 0) {
+3 -4
Näytä tiedosto
@@ -110,15 +110,14 @@ private:
NetworkStateProvider* _network;
#if defined(ESP_PLATFORM)
static constexpr uint8_t kEastmeshBit = 0x01;
static constexpr uint8_t kMeshcoretelBit = 0x01;
static constexpr uint8_t kLetsmeshEuBit = 0x02;
static constexpr uint8_t kLetsmeshUsBit = 0x04;
static constexpr uint8_t kMeshcoretelBit = 0x08;
static constexpr uint8_t kMaxEnabledBrokers = 2;
static const BrokerSpec kBrokerSpecs[4];
static const BrokerSpec kBrokerSpecs[3];
static bool isUnsetIataValue(const char* iata);
BrokerState _brokers[4];
BrokerState _brokers[3];
static void handleMqttEvent(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data);
bool hasEnabledBroker() const;
+32 -208
Näytä tiedosto
@@ -854,71 +854,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
<option value="VSG">VSG - Lugansk</option>
<option value="VVO">VVO - Vladivostok</option>
</optgroup>
<optgroup label="France">
<option value="CHR">CHR - Chateauroux</option>
</optgroup>
<optgroup label="ACT">
<option value="CBR">CBR - Canberra</option>
</optgroup>
<optgroup label="New South Wales">
<option value="ABX">ABX - Albury</option>
<option value="ARM">ARM - Armidale</option>
<option value="BHQ">BHQ - Broken Hill</option>
<option value="BNK">BNK - Ballina</option>
<option value="CFS">CFS - Coffs Harbour</option>
<option value="DBO">DBO - Dubbo</option>
<option value="GFF">GFF - Griffith</option>
<option value="GFN">GFN - Grafton</option>
<option value="LDH">LDH - Lord Howe Island</option>
<option value="LSY">LSY - Lismore</option>
<option value="MIM">MIM - Merimbula</option>
<option value="MRZ">MRZ - Moree</option>
<option value="MYA">MYA - Moruya</option>
<option value="NTL">NTL - Newcastle</option>
<option value="OAG">OAG - Orange</option>
<option value="PQQ">PQQ - Port Macquarie</option>
<option value="SYD">SYD - Sydney</option>
<option value="WGA">WGA - Wagga Wagga</option>
</optgroup>
<optgroup label="Queensland">
<option value="ABM">ABM - Bamaga</option>
<option value="BNE">BNE - Brisbane</option>
<option value="CNS">CNS - Cairns</option>
<option value="HTI">HTI - Hamilton Island</option>
<option value="HVB">HVB - Hervey Bay</option>
<option value="ISA">ISA - Mount Isa</option>
<option value="LRE">LRE - Longreach</option>
<option value="MCY">MCY - Sunshine Coast</option>
<option value="MKY">MKY - Mackay</option>
<option value="OOL">OOL - Gold Coast</option>
<option value="PPP">PPP - Proserpine</option>
<option value="ROK">ROK - Rockhampton</option>
<option value="TSV">TSV - Townsville</option>
<option value="WEI">WEI - Weipa</option>
<option value="WTB">WTB - Toowoomba Wellcamp</option>
</optgroup>
<optgroup label="South Australia">
<option value="ADL">ADL - Adelaide</option>
<option value="KGC">KGC - Kingscote</option>
<option value="MGB">MGB - Mount Gambier</option>
<option value="PLO">PLO - Port Lincoln</option>
<option value="WYA">WYA - Whyalla</option>
</optgroup>
<optgroup label="Tasmania">
<option value="BWT">BWT - Burnie</option>
<option value="DPO">DPO - Devonport</option>
<option value="FLS">FLS - Flinders Island</option>
<option value="HBA">HBA - Hobart</option>
<option value="KNS">KNS - King Island</option>
<option value="LST">LST - Launceston</option>
</optgroup>
<optgroup label="Victoria">
<option value="AVV">AVV - Avalon</option>
<option value="GEX">GEX - Geelong West</option>
<option value="MEB">MEB - Essendon Fields</option>
<option value="MEL">MEL - Melbourne</option>
<option value="MQL">MQL - Mildura</option>
</optgroup>
</select>
<button class="iconbtn" data-load-cmd="get mqtt.iata" data-load-input="mqttIata" title="Refresh MQTT IATA">&#8635;</button>
<button class="savebtn" data-prefix="set mqtt.iata " data-input="mqttIata">Save</button>
@@ -942,15 +877,15 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
</div>
<div class="field-card">
<label class="label">MQTT Servers</label>
<div class="broker-stack" style="grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr)">
<div class="broker-stack">
<div class="broker-group">
<div class="broker-group-title">Meshcoretel</div>
<div class="broker-group-title">MeshCoreTel</div>
<div class="broker-grid single">
<div class="broker-card">
<div class="broker-mode">
<div class="broker-row">
<div class="broker-copy">
<div class="broker-title">Meshcoretel RU</div>
<div class="broker-title">MeshCoreTel RU</div>
<div class="broker-state" id="mqttMeshcoretelState">Off</div>
</div>
</div>
@@ -966,29 +901,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
</div>
</div>
</div>
<div class="broker-group">
<div class="broker-group-title">EastMesh</div>
<div class="broker-grid single">
<div class="broker-card">
<div class="broker-mode">
<div class="broker-row">
<div class="broker-copy">
<div class="broker-title">EastMesh AU</div>
<div class="broker-state" id="mqttEastmeshAuState">Off</div>
</div>
</div>
<div class="mode-slider">
<input id="mqttEastmeshMode" type="range" min="0" max="1" step="1" value="0" aria-label="EastMesh mode">
<div class="mode-labels two" aria-hidden="true">
<div class="mode-label" data-eastmesh-label="off">Off</div>
<div class="mode-label" data-eastmesh-label="on">On</div>
</div>
</div>
<input id="mqttEastmeshAu" class="visually-hidden" type="checkbox" tabindex="-1" aria-hidden="true">
</div>
</div>
</div>
</div>
<div class="broker-group">
<div class="broker-group-title">LetsMesh</div>
<div class="broker-grid single">
@@ -1731,12 +1643,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
}
const renderNeighbourId = (neighbour) => {
const shortId = escapeHtml(neighbour.id || "--");
const fullId = typeof neighbour.full_id === "string" ? neighbour.full_id.trim() : "";
if (!/^[0-9A-Fa-f]{64}$/.test(fullId)) {
return shortId;
}
const href = "https://core.eastmesh.au/#/nodes/" + encodeURIComponent(fullId.toLowerCase());
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${shortId}</a>`;
return shortId;
};
const rows = neighbours.map((neighbour) => `<tr>
<td>${renderNeighbourId(neighbour)}</td>
@@ -2126,7 +2033,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
}
if (inlineWarning) {
inlineWarning.textContent = showWarning
? "MQTT IATA is unset. Set it before enabling any MQTT brokers."
? "MQTT IATA is unset. Set it before enabling MeshCoreTel or LetsMesh brokers."
: "";
}
}
@@ -2257,15 +2164,15 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
if (usOn) return "us";
return "off";
}
function refreshEastmeshModeUi() {
const input = document.getElementById("mqttEastmeshAu");
function refreshMeshcoretelModeUi() {
const input = document.getElementById("mqttMeshcoretel");
const enabled = !!(input && input.checked);
const slider = document.getElementById("mqttEastmeshMode");
const slider = document.getElementById("mqttMeshcoretelMode");
if (slider) {
slider.value = enabled ? "1" : "0";
}
document.querySelectorAll("[data-eastmesh-label]").forEach((label) => {
label.classList.toggle("active", label.dataset.eastmeshLabel === (enabled ? "on" : "off"));
document.querySelectorAll("[data-meshcoretel-label]").forEach((label) => {
label.classList.toggle("active", label.dataset.meshcoretelLabel === (enabled ? "on" : "off"));
});
}
function getLetsmeshModeIndex(mode) {
@@ -2273,27 +2180,15 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
return Object.prototype.hasOwnProperty.call(order, mode) ? order[mode] : 0;
}
function clampLetsmeshModeIndex(index) {
const eastmesh = document.getElementById("mqttEastmeshAu");
const eastmeshEnabled = !!(eastmesh && eastmesh.checked);
const meshcoretel = document.getElementById("mqttMeshcoretel");
const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked);
const bounded = Math.max(0, Math.min(3, index));
if ((eastmeshEnabled || meshcoretelEnabled) && bounded === 3) {
return 1;
}
if (eastmeshEnabled && meshcoretelEnabled && bounded > 0) {
return 0;
}
return bounded;
return meshcoretelEnabled && bounded === 3 ? 1 : bounded;
}
function refreshLetsmeshModeUi() {
const mode = getLetsmeshMode();
const eastmesh = document.getElementById("mqttEastmeshAu");
const eastmeshEnabled = !!(eastmesh && eastmesh.checked);
const meshcoretel = document.getElementById("mqttMeshcoretel");
const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked);
const bothBlocked = eastmeshEnabled || meshcoretelEnabled;
const allBlocked = eastmeshEnabled && meshcoretelEnabled;
const state = document.getElementById("mqttLetsmeshModeState");
const labels = { off:"Off", eu:"EU", us:"US", both:"Both" };
if (state) {
@@ -2304,15 +2199,11 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
if (slider) {
slider.max = "3";
slider.value = String(clampLetsmeshModeIndex(getLetsmeshModeIndex(mode)));
slider.disabled = allBlocked;
}
document.querySelectorAll("[data-letsmesh-label]").forEach((label) => {
const labelMode = label.dataset.letsmeshLabel;
label.classList.toggle("active", labelMode === mode);
label.classList.toggle("disabled",
(bothBlocked && labelMode === "both") ||
(allBlocked && labelMode !== "off")
);
label.classList.toggle("disabled", meshcoretelEnabled && labelMode === "both");
});
}
function setBrokerToggle(inputId, state) {
@@ -2327,11 +2218,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
}
if (inputId === "mqttLetsmeshEu" || inputId === "mqttLetsmeshUs") {
refreshLetsmeshModeUi();
} else if (inputId === "mqttEastmeshAu") {
refreshEastmeshModeUi();
refreshLetsmeshModeUi();
} else if (inputId === "mqttMeshcoretel") {
refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
}
}
async function loadBrokerState(cmd, inputId, options = {}) {
@@ -2449,54 +2338,34 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
if (mqttIataSelect) {
mqttIataSelect.addEventListener("change", refreshMqttIataWarning);
}
async function setEastmeshMode(enabled) {
if (enabled) {
if (getLetsmeshMode() === "both") {
await setLetsmeshMode("eu");
}
const meshcoretel = document.getElementById("mqttMeshcoretel");
if (meshcoretel && meshcoretel.checked && getLetsmeshMode() !== "off") {
await setLetsmeshMode("off");
}
async function setMeshcoretelMode(enabled) {
if (enabled && getLetsmeshMode() === "both") {
await setLetsmeshMode("eu");
}
const result = await runCommand(enabled ? "set mqtt.eastmesh-au on" : "set mqtt.eastmesh-au off");
const result = await runCommand(enabled ? "set mqtt.meshcoretel on" : "set mqtt.meshcoretel off");
if (!result.ok) {
refreshEastmeshModeUi();
refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
return;
}
setBrokerToggle("mqttEastmeshAu", enabled ? "on" : "off");
refreshEastmeshModeUi();
refreshLetsmeshModeUi();
setBrokerToggle("mqttMeshcoretel", enabled ? "on" : "off");
refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
}
const eastmeshModeSlider = document.getElementById("mqttEastmeshMode");
if (eastmeshModeSlider) {
eastmeshModeSlider.addEventListener("input", () => {
eastmeshModeSlider.value = (Number.parseInt(eastmeshModeSlider.value, 10) || 0) >= 1 ? "1" : "0";
const meshcoretelModeSlider = document.getElementById("mqttMeshcoretelMode");
if (meshcoretelModeSlider) {
meshcoretelModeSlider.addEventListener("input", () => {
meshcoretelModeSlider.value = (Number.parseInt(meshcoretelModeSlider.value, 10) || 0) >= 1 ? "1" : "0";
});
eastmeshModeSlider.addEventListener("change", () => {
setEastmeshMode((Number.parseInt(eastmeshModeSlider.value, 10) || 0) >= 1);
meshcoretelModeSlider.addEventListener("change", () => {
setMeshcoretelMode((Number.parseInt(meshcoretelModeSlider.value, 10) || 0) >= 1);
});
}
async function setLetsmeshMode(mode) {
const eastmesh = document.getElementById("mqttEastmeshAu");
const eastmeshEnabled = !!(eastmesh && eastmesh.checked);
const meshcoretel = document.getElementById("mqttMeshcoretel");
const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked);
if (mode === "both") {
if (eastmeshEnabled) {
refreshLetsmeshModeUi();
return;
}
if (meshcoretelEnabled) {
refreshLetsmeshModeUi();
return;
}
}
if (mode !== "off" && meshcoretelEnabled && eastmeshEnabled) {
refreshLetsmeshModeUi();
return;
if (mode === "both" && meshcoretel && meshcoretel.checked) {
refreshLetsmeshModeUi();
return;
}
const desired = {
eu: mode === "eu" || mode === "both",
@@ -2530,49 +2399,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
setLetsmeshMode(modes[index] || "off");
});
}
function refreshMeshcoretelModeUi() {
const input = document.getElementById("mqttMeshcoretel");
const enabled = !!(input && input.checked);
const slider = document.getElementById("mqttMeshcoretelMode");
if (slider) slider.value = enabled ? "1" : "0";
const state = document.getElementById("mqttMeshcoretelState");
if (state) {
state.textContent = enabled ? "On" : "Off";
state.classList.toggle("on", enabled);
}
document.querySelectorAll("[data-meshcoretel-label]").forEach((label) => {
label.classList.toggle("active", label.dataset.meshcoretelLabel === (enabled ? "on" : "off"));
});
}
async function setMeshcoretelMode(enabled) {
if (enabled) {
if (getLetsmeshMode() === "both") {
await setLetsmeshMode("eu");
}
const eastmesh = document.getElementById("mqttEastmeshAu");
if (eastmesh && eastmesh.checked && getLetsmeshMode() !== "off") {
await setLetsmeshMode("off");
}
}
const result = await runCommand(enabled ? "set mqtt.meshcoretel on" : "set mqtt.meshcoretel off");
if (!result.ok) {
refreshMeshcoretelModeUi();
return;
}
setBrokerToggle("mqttMeshcoretel", enabled ? "on" : "off");
refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
refreshEastmeshModeUi();
}
const meshcoretelModeSlider = document.getElementById("mqttMeshcoretelMode");
if (meshcoretelModeSlider) {
meshcoretelModeSlider.addEventListener("input", () => {
meshcoretelModeSlider.value = (Number.parseInt(meshcoretelModeSlider.value, 10) || 0) >= 1 ? "1" : "0";
});
meshcoretelModeSlider.addEventListener("change", () => {
setMeshcoretelMode((Number.parseInt(meshcoretelModeSlider.value, 10) || 0) >= 1);
});
}
document.getElementById("saveOwnerInfo").onclick = () => {
const value = document.getElementById("ownerInfo").value.replace(/\n/g, "|");
runCommand("set owner.info " + value);
@@ -2699,10 +2525,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
() => loadField("get mqtt.iata", "mqttIata", null, quiet),
() => loadField("get mqtt.owner", "mqttOwner", null, quiet),
() => loadField("get mqtt.email", "mqttEmail", null, quiet),
() => loadBrokerState("get mqtt.eastmesh-au", "mqttEastmeshAu", quiet),
() => loadBrokerState("get mqtt.meshcoretel", "mqttMeshcoretel", quiet),
() => loadBrokerState("get mqtt.letsmesh-eu", "mqttLetsmeshEu", quiet),
() => loadBrokerState("get mqtt.letsmesh-us", "mqttLetsmeshUs", quiet),
() => loadBrokerState("get mqtt.meshcoretel", "mqttMeshcoretel", quiet)
() => loadBrokerState("get mqtt.letsmesh-us", "mqttLetsmeshUs", quiet)
]);
statusEl.textContent = "Ready";
} catch (error) {
@@ -2713,9 +2538,8 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
}
loadRadioPresets();
}
refreshEastmeshModeUi();
refreshLetsmeshModeUi();
refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
initApp();
</script>
</body>